Harmony OS搭建广告展示页

时间:2024-11-04 18:26:52

有些app需要广告页,有的不需要,我们需要搞个配置。

1. 通过首选项配置存储我们的常用配置,比如是否需要广告页、广告页的路由地址、点击广告页跳转的链接以及广告页倒计时。

1.1 新建一个关于广告页的数据模型。

export class AdvertClass {
  showAd: boolean = false // 是否展示广告
  isFull: boolean = true // 是否全屏
  adTime: number = 5 // 倒计时数据
  adUrl?: string = "" // 要跳转的连接
  adImg?: ResourceStr = "" // 图片连接
}

1.2 新建一个关于读取首选项的类,用于读取和设置首选项的广告设置

import { preferences } from '@kit.ArkData'
import { USER_SETTING, USER_SETTING_AD } from '../constants'
import { AdvertClass } from '../viewmodels'

export const defaultAd: AdvertClass = {
  showAd: true,
  isFull: true,
  adTime: 3,
  adImg: $r("app.media.start")
}

// 负责首选项的读取
export class UserSetting {
  context?: Context
  // 获取仓库
  getStore () {
   return preferences.getPreferencesSync(this.context || getContext(), {
      name: USER_SETTING
    })
  }
  // 设置用户广告
 async setUserAd(ad: AdvertClass) {
    const store = this.getStore()
    store.putSync(USER_SETTING_AD, ad)
    await store.flush()  // 让外界能够控制自己的流程
  }
  // 获取用户广告
  getUserAd() {
    const store = this.getStore()
    return store.getSync(USER_SETTING_AD, defaultAd) as AdvertClass
  }
}

export const userSetting = new UserSetting() // 导出一个单例

上面还用到了两个常量,我们需要在constants目录下定义一个文件专门用来记录setting

export const USER_SETTING = 'fast_driver_setting' // 用来存储用户设置的首选项的key
export const USER_SETTING_AD = 'fast_driver_setting_ad' // 用来存储用户设置广告首选项的key

1.3 在ability中判断

async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    userSetting.context = this.context // 首选项赋值上下文
    // 发起一个向云端获取广告数据的请求
    const ad = await new Promise<AdvertClass>((resolve, reject) => {
      setTimeout(() => {
        resolve(cloudAd)
      }, 500)
    })
    await userSetting.setUserAd(ad) // 将广告设置到首选项
    // 此时应该判断是不是展示广告?
    if (ad.showAd) {
     windowStage.loadContent('pages/Start/Start');
    } else {
      windowStage.loadContent('pages/Index');
    }
  }

1.4 模拟一个请求,给一个默认广告,写入首选项-正常加载主页

实现start页的页面结构及倒计时逻辑

import { AdvertClass, userSetting } from 'basic'
import { router } from '@kit.ArkUI'

@Entry
@Component
struct Start {
  // 需要广告对象
  @State
  ad: AdvertClass = new AdvertClass()
  timer: number = -1 // 用来记录定时器的标记

  // @State
  // ad: Partial<AdvertClass> = {}
  aboutToAppear(): void {
    // 获取首选项的广告数据给到ad
    this.getAdInfo()
  }

  getAdInfo() {
    // 首选项的读取
    this.ad = userSetting.getUserAd()
    // 开启倒计时了
    if (this.ad.showAd) {
      // 如果真的展示广告要开始倒计时
      this.timer = setInterval(() => {
        if (this.ad.adTime === 0) {
          clearInterval(this.timer)
          this.toMain()
          return // return一定要写
        }
        this.ad.adTime--
      }, 1000)
    }
  }

  // 去主页的方法
  toMain() {
    router.replaceUrl({
      url: 'pages/Index'
    })

  }

  aboutToDisappear(): void {
    clearInterval(this.timer)
  }

  build() {
    RelativeContainer() {
      if (this.ad.showAd) {
        Image(this.ad.adImg)
          .width("100%")
          .height("100%")
          .objectFit(ImageFit.Cover)
        Text(`${this.ad.adTime}秒跳过`)
          .padding({
            left: 10,
            right: 10
          })
          .alignRules({
            right: {
              anchor: '__container__',
              align: HorizontalAlign.End
            },
            top: {
              anchor: '__container__',
              align: VerticalAlign.Top
            }
          })
          .borderRadius(15)
          .height(30)
          .fontSize(14)
          .backgroundColor($r("app.color.background_page"))
          .margin({
            right: 20,
            top: 20
          })
          .onClick(() => {
            // 此时跳过广告
            // 跳转到主页
            this.toMain()
          })
      }

    }
    .height('100%')
    .width('100%')
  }
}

1.5 使用子窗口模式加载广告

我们可以使用windowStage的createSubWindow来实现当前页面上创建一个窗口

 if (result.showAd) {
      const win = await windowStage.createSubWindow("ad_window")
      await win.showWindow()
          win.setUIContent("pages/Start/Start")
    }

2.  window窗口广告模式

封装window窗口广告模式

import { display, window } from '@kit.ArkUI'
import { util } from '@kit.ArkTS'

export class AdManager {
  context?: Context // 是给ability使用的
  private winNames: string [] = []

  // 展示广告 采用windows窗口的创建和销毁的方式
  async showAd(url: string, width?: number, height?: number) {
    if (url) {
      let name = `win_${util.generateRandomUUID()}`
      const win = await window.createWindow({
        name,
        windowType: window.WindowType.TYPE_DIALOG,
        ctx: this.context || getContext()
      })
      if (width && width >= 320 && height && height >= 240) {
        const screen = display.getDefaultDisplaySync()
        let mainWidth = vp2px(width)
        let mainHeight = vp2px(height)
        win.resizeAsync(mainWidth, mainHeight)
        win.moveWindowToAsync((screen.width - mainWidth) / 2, (screen.height - mainHeight) / 2)
      }
      await win.showWindow() // 展示窗口
      win.setUIContent(url) // 设置地址
      this.winNames.push(name)
      return name
    }
    return ""
  }

  // 关闭广告
  async closeAd(name?: string) {
    if (name) {
      window.findWindow(name).destroyWindow()
      this.winNames = this.winNames.filter(item => item !== name) //清空数组内容
    } else {
      // 不传就认为 想关闭所有
      let index = 0
      while (index < this.winNames.length) {
        await window.findWindow(this.winNames[index]).destroyWindow()
        index++
      }
      this.winNames = [] // 清空数组
    }

  }
}

export const adManger = new AdManager()

Start页面代码

import { adManger, AdvertClass, userSetting } from 'basic'
import { router, window } from '@kit.ArkUI'

@Entry
@Component
struct Start {
  // 需要广告对象
  @State
  ad: AdvertClass = new AdvertClass()
  timer: number = -1 // 用来记录定时器的标记

  // @State
  // ad: Partial<AdvertClass> = {}
  aboutToAppear(): void {
    // 获取首选项的广告数据给到ad
    this.getAdInfo()
  }

  getAdInfo() {
    // 首选项的读取
    this.ad = userSetting.getUserAd()
    // 开启倒计时了
    if (this.ad.showAd) {
      // 如果真的展示广告要开始倒计时
      this.timer = setInterval(() => {
        if (this.ad.adTime === 0) {
          clearInterval(this.timer)
          this.toMain()
          return // return一定要写
        }
        this.ad.adTime--
      }, 1000)
    }
  }

  // 去主页的方法
  toMain() {
    // router.replaceUrl({
    //   url: 'pages/Index'
    // })
    // 销毁当前的窗口
    clearInterval(this.timer) // 先清理一下定时器
    adManger.closeAd()
  }

  aboutToDisappear(): void {
    clearInterval(this.timer)
  }

  build() {
    RelativeContainer() {
      if (this.ad.showAd) {
        Image(this.ad.adImg)
          .width("100%")
          .height("100%")
          .objectFit(ImageFit.Cover)
        Text(`${this.ad.adTime}秒跳过`)
          .padding({
            left: 10,
            right: 10
          })
          .alignRules({
            right: {
              anchor: '__container__',
              align: HorizontalAlign.End
            },
            top: {
              anchor: '__container__',
              align: VerticalAlign.Top
            }
          })
          .borderRadius(15)
          .height(30)
          .fontSize(14)
          .backgroundColor($r("app.color.background_page"))
          .margin({
            right: 20,
            top: 20
          })
          .onClick(() => {
            // 此时跳过广告
            // 跳转到主页
            this.toMain()
          })
      }

    }
    .height('100%')
    .width('100%')
  }
}

EntryAbility完整代码

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { adManagerFinal, adManger, AdvertClass, userSetting } from 'basic';

// 云端广告
const cloudAd: AdvertClass = {
  showAd: true,
  isFull: false,
  adTime: 100,
  adImg: $r("app.media.start")
}

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    userSetting.context = this.context // 首选项赋值上下文
    // 发起一个向云端获取广告数据的请求
    const ad = await new Promise<AdvertClass>((resolve, reject) => {
      setTimeout(() => {
        resolve(cloudAd)
      }, 500)
    })
    await userSetting.setUserAd(ad) // 将广告设置到首选项
    // 此时应该判断是不是展示广告?
    // 1. 第一种页面方式
    // if (ad.showAd) {
    //   windowStage.loadContent('pages/Start/Start');
    // } else {
    //   windowStage.loadContent('pages/Index');
    // }
    // 2. 第二种window窗口模式
    // if (ad.showAd) {
    //   const win = await windowStage.createSubWindow("ad_win") // 二级窗口的实际对象
    //   await win.showWindow() // 展示二级窗口
    //   win.setUIContent("pages/Start/Start")
    // }

    await windowStage.loadContent('pages/Index'); // 必须等到有了UIContext才可以使用
    if (ad.showAd) {
      adManagerFinal.context = this.context
      // adManger.context = this.context
      // await adManger.showAd("pages/Start/Start", 330, 440)
      adManagerFinal.showAd(ad) // 展示广告
    }
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {

    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

3.  还可采用与页面解耦的方式实现广告

import { AdvertClass } from '../viewmodels'
import { ComponentContent, promptAction, window } from '@kit.ArkUI'
import { util } from '@kit.ArkTS'

// 展示广告的结构最重要写的代码
@Builder
function AdBuilder(ad: AdvertClass) {
  Column() {
    Image(ad.adImg)
      .width("100%")
      .height("100%")
      .objectFit(ImageFit.Cover)
      .borderRadius(10)

    Row() {
      Image($r("app.media.ic_btn_close"))
        .width(14)
        .aspectRatio(1)
        .fillColor("#ccc")
    }
    .width(30)
    .aspectRatio(1)
    .justifyContent(FlexAlign.Center)
    .borderRadius(15)
    .border({
      color: '#ff343232',
      width: 2
    })
    .margin({
      top: 40
    })
    .onClick(() => {
      if (ad.dialogName) {
        adManagerFinal.closeAd(ad.dialogName) //  ? name从哪里进来
      }

    })

  }
  .width(ad.isFull ? "100%" : "80%")
  .height(ad.isFull ? "100%" : "50%")
}

export class AdManagerFinal {
  context?: Context
  // 所有的弹窗都放到这个map中 通过name来标识
  private map: Map<string, ComponentContent<AdvertClass>> = new Map()

  // 实际上需要广告
  async showAd(ad: AdvertClass) {
    // 按照文档实现
    // UIContext上下文必须得等到页面初始化之后才可以进行获取
    // 生成一个name
    let name = `dialog_${util.generateRandomUUID()}`
    // 通过当前的主窗口来获取
    const mainWin = await window.getLastWindow(this.context || getContext())
    let uiContext = mainWin.getUIContext() // 拿到UIContext
    let promptAction = uiContext.getPromptAction();
    ad.dialogName = name // 目的是将dialog的弹窗名称传递到builder中
    let contentNode = new ComponentContent(uiContext, wrapBuilder(AdBuilder), ad);
    let options: promptAction.BaseDialogOptions = {
      alignment: DialogAlignment.Center,
      autoCancel: false
    };
    this.map.set(name, contentNode) // 将key/value写入到map中
    promptAction.openCustomDialog(contentNode, options);


    // 一般半屏广告 是得用户手动点击才能关闭的 一般不会自动关闭
    // setTimeout(() => {
    //   promptAction.closeCustomDialog(contentNode)
    // }, 2000)


  }

  async closeAd(name: string) {
    if (name) {
      const mainWin = await window.getLastWindow(this.context || getContext())
      let uiContext = mainWin.getUIContext() // 拿到UIContext
      let promptAction = uiContext.getPromptAction();
      promptAction.closeCustomDialog(this.map.get(name))
      // 清理map
      this.map.delete(name) // 删除已经关闭的弹窗
    }
  }
}

export const adManagerFinal = new AdManagerFinal()