HarmonyOS NEXT中自定义广告的四种模式-根据场景选择
https://i-blog.csdnimg.cn/img_convert/00de9e2104734365090cc17743dde478.png一、应用场景
app冷启动/热启动-有广告需求,就打开广告页,没有的话就去登录或者主页
华为有广告业务,但是我们不用
自定义一个ad模块,但是根据不同的业务场景和需求,可以使用不同的打开广告方式
[*]应用启动时,有广告则直接进入广告页,定时竣事或点击跳过,跳转到首页
[*]打开子窗口,然后通过销毁子窗口方式关闭广告
[*]封装window窗口广告模式,关闭时直接调用封装的关闭方法
[*]采用与页面解耦的模式实现广告,关闭时候直接调用封装的关闭方法
总结处有附完备代码
二、新建一个关于广告类的数据模子
1.创建广告类
这个广告模子其实在那里创建不影响,只要导出,在需要用到的地方可以正常导入就行,由于本项目有har包,就直接创建到har包里-basic- viewmodels/advert.ets
export class AdvertClass {
showAd: boolean = false // 是否展示广告
isFull: boolean = true // 是否全屏
adTime: number = 5 // 倒计时数据
adUrl?: string = "" // 要跳转的连接
adImg?: ResourceStr = "" // 图片连接
}
// 全屏广告 半屏广告 2.本地存储广告用来读取和设置广告
这里用的方式不同于首选项方式,而是使用AppStorage和持久化本地存储的方式,这种方式更加简单。
当然无论是首选项的方式还是什么都只是将广告信息存储,都不影响后面任何模式的使用。
utils/setting.ets
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")
}
// 首选项方式,每次使用前记得给context赋值
// 负责首选项的读取
// 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 class UserSetting{
// 初始化仓库
initUserSetting(){
PersistentStorage.persistProp<AdvertClass>(USER_SETTING_AD,defaultAd)
}
// 设置用户广告
setUserAd(ad: AdvertClass) {
AppStorage.setOrCreate<AdvertClass>(USER_SETTING_AD, ad)
}
// 获取用户广告
getUserAd(){
return AppStorage.get<AdvertClass>(USER_SETTING_AD)
}
}
export const userSetting = new UserSetting() // 导出一个单例 这里初始化本地仓库的代码不能放到要在EntryAbility中的onWindowStageCreate中,官网的意思最早也是UI实例初始化后才能持久化。
PersistentStorage:持久化存储UI状态
PersistentStorage和UI实例相关联,持久化操纵需要在UI实例初始化成功后(即loadContent传入的回调被调用时)才可以被调用,早于该时机调用会导致持久化失败。
EntryAbility.ets
onWindowStageCreate(){
// ...省略
// 初始化广告仓库
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
// 初始化广告仓库
userSetting.initUserSetting()
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
// ...省略
} 这里还要设置常量USER_SETTING_AD,但由于用的不是首选项,所以只要广告名,不需要仓库名
constants/index.ets
export const USER_SETTING_AD = 'fast_driver_setting_ad' // 用来存储用户设置广告首选项的key 假如你使用的是har包存放静态资源,末了统一在basic/Index.ets统一导出所有资源,但是这样导出有个bug,在使用的地方导入,会有代码提示,但是会报错说找不到导出的资源
basic/Index.ets可能报错
export * from 'ets/utils'
export * from 'ets/constants'
export * from 'ets/viewmodels' 假如报错请补全路径
basic/Index.ets
export * from './src/main/ets/utils'
export * from './src/main/ets/constants'
export * from './src/main/ets/viewmodels' 假如是在har包中定义的数据,记得在entry中导入你har包的数据.
entry/oh-package.json5
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"basic":"file:../basic"
}
} 3.新建一个广告页面Start
Start页面中,读取本地存储的广告,展示广告内容,实现倒计时逻辑,倒计时竣事或者点击跳过都会调用toMain()方法。
toMain()方法其实就是你关闭广告的方式,选择不同的广告模式,就要修改不同的toMain()方法
前三种模式都会用到Start页面,所以选择不同方式打开广告,就要用不同方式去关闭广告。
pages/Start/Start.ets
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() {
// 关闭广告的方式
打开方式不同,关闭方式也就不同
}
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%')
}
} 4.初始化广告信息
由于广告随时有切换的可能,但是我们又不能每次换个广告就去改源码,所以其实是通过发请求的方式,去获取广告的信息,但你有可能在启动时就需要打开广告页,所以依然是在EntryAbility中的onWindowStageCreate方法中就请求回广告信息,并存入本地存储,供广告页面使用。
不过这里我们模拟发送请求就好了,到时候有真实接口再真的请求数据
EntryAbility.ets
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
// 假装向云端获取广告数据的请求
const ad = await new Promise<AdvertClass>((resolve, reject) => {
setTimeout(() => {
resolve({
showAd: true,
isFull: true,
adTime: 3,
adImg: $r("app.media.start")
})
}, 500)
})
userSetting.setUserAd(ad); // 云端的广告设置到本地
} 统统预备就绪,接下来就时选择不同打开广告方式了。
方式一:应用启动时,有广告则直接进入广告页
这种方式是直接判断有无广告,然后通过windowStage.loadContent()直接打开广告页或者首页,
不过在应用启动时打开广告比力实用,不得当在使用中途打开广告。
EntryAbility.ets
//在保存好ad广告信息后
//1. 判断是否需要展示广告
const path = ad.showAd ? 'pages/Start/Start' : 'pages/Index'
// 根据抽取的路径加载对应path, 如果要用这种模式,将默认路径 'pages/Index' 替换为path
// 使用这种只适用于刚打开应用时,因为他的关闭方式其实是直接跳转到首页(固定的)
windowStage.loadContent(path, (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
// 初始化广告仓库
userSetting.initUserSetting()
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
}); 当然使用方式一就要在广告Start页面修改实用他的toMain()方法,点击跳过或者倒计时竣事,直接跳转到首页
Start.ets
toMain() {
// 1. 方式一打开广告,关闭是直接跳转到主页
router.replaceUrl({
url: 'pages/Index'
})
} 方式二:打开子窗口,然后通过销毁子窗口方式关闭广告
这种方式相当于在不影响加载主页的同时,另外打开了一个页面,覆盖在主页上,到时候关闭直接露出主页
EntryAbility.ets
// 2. 略微有点意思的模式:创建二级窗口,并展示广告
// 用这种方式就在 'pages/Start/Start' 中将关闭广告的方法改为 2
if (ad.showAd) {
const win = await windowStage.createSubWindow("ad_win") // 二级窗口的实际对象
await win.showWindow() // 展示二级窗口
win.setUIContent("pages/Start/Start") // 二级窗口放的还是Start广告页
}
// 这里还是需要正常打开首页的
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
// 初始化广告仓库
userSetting.initUserSetting()
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
}); 当然,在广告页Start中的toMain(),也要将关闭方式修改
Start.ets
toMain() {
// 2.使用子窗口模式关闭广告
clearInterval(this.timer) // 先清理一下定时器
this.closeWin()
}
// 这个关闭方法传入刚才打开的子窗口名称即可
closeWin () {
window.findWindow("ad_win").destroyWindow()
} 方式三:封装window窗口广告模式
这种方式是打开一个宽高自定义的窗口,然后把广告页面放在窗口中,通过封装类导出实例对象,调用showAd和closeAd方法来打开关闭广告。
假如不好理解可以先不用管内里具体的API都是什么,就看showAd方法和closeAd方法,和导出的实例,用实例调用打开和关闭的方法,看方法需要什么参数给他传什么参数就行了。
https://i-blog.csdnimg.cn/img_convert/04ff98edcd1de830cb4f6ad546e6c93a.png
utils/ad_manager.ets
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// showAd方法返回了一个name可以接受存下来,用于关闭广告
}
return ""
}
// 关闭广告
async closeAd(name?: string) {// 传入刚才存的name关闭具体小窗,不穿则关闭所有小窗
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).destroyWindow()
index++
}
this.winNames = [] // 清空数组
}
}
}
export const adManger = new AdManager() 在EntryAbility.ets中,判断是否需要广告,然后打开小窗口。在别的地方使用也可。
// 3.使用封装window窗口广告模式
// 用这种方式就在 'pages/Start/Start' 中将关闭广告的方法改为 3:adManger.closeAd()
if(ad.showAd){
adManger.context = this.context
adManger.showAd('pages/Start/Start', 320, 240)// 设置小窗宽高
// 如果你在这里接收了一个name,那么也要存起来,在关闭时使用。
// 不过因为暂时只打开了一个小窗,不存关闭所有也是相同的效果
} 在广告页Start中修改关闭方法toMain()
toMain() {
// 3.使用封装window窗口广告模式
clearInterval(this.timer) // 先清理一下定时器
adManger.closeAd() //这里如果不传入name就会关闭所有的小窗
} 方式四:采用与页面解耦的模式实现广告
这种方式相当于弹出一个雷同消息提示的弹框(dialog),也是通过封装类导出实例,实现打开和关闭广告的方法。
这个种没有实现倒计时,需要用户手动去点击关闭按钮或者使用返回键关闭广告。
但是这种方式需要给广告类再增加一个dialogName属性,当然叫什么无所谓,就是后面用于关闭弹窗。
这种方式用的不是Start页面,而是自定义的弹框builder,所以不用去Start中修改关闭方式
https://i-blog.csdnimg.cn/img_convert/c5a54b454c5edbb8241b9ff21f76b480.png
advert.ets
export class AdvertClass {
showAd: boolean = false // 是否展示广告
isFull: boolean = true // 是否全屏
adTime: number = 5 // 倒计时数据
adUrl?: string = "" // 要跳转的连接
adImg?: ResourceStr = "" // 图片连接
dialogName?: string = "" // 新增的name属性
}
封装ad_manager_final工具
这里由于使用的不是Start页面,所以不用去Start做相应的关闭逻辑
utils/ad_manager_final.ets
import { ComponentContent, promptAction, window } from '@kit.ArkUI'
import { util } from '@kit.ArkTS'
import { AdvertClass } from 'basic'
// 展示广告的结构最重要写的代码
@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)
}
.position({// 不加定位会看不到关闭按钮
right: 20,
top: 20
})
.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() 在EntryAbility.ets中,判断是否需要广告,打开广告。在别的地方使用也可。
// 4.采用与页面解耦的模式实现广告
// 这种模式是单独打开一个全屏模态框,而不是打开 'pages/Start/Start' 页面,关闭按钮也在 'pages/Start/Start' 页面中
if (ad.showAd) {
adManagerFinal.context = this.context
adManagerFinal.showAd(ad) // 展示广告
} 这种方式是单独拉起一个雷同dialog弹框,所以样式是重新定义的,不需要去Start页面修改toMain()方法
总结:
[*]四种方式各有不同,需要在不同业务场景下公道选择。
[*]第一种和第二种方式更得当在应用刚打开时的广告展示
[*]第三种和第四种由于是封装导出的实例,更得当在全局使用。
[*]前三种都是使用了Start页面,第四种是雷同dialog弹框,他们的关闭方式各不相同。
附:完备的EntryAbility.ets和Start.ets
EntryAbility种的1、2、3分别对应Start中toMain()的1、2、3方法
EntryAbility.ets
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';import { hilog } from '@kit.PerformanceAnalysisKit';import { window } from '@kit.ArkUI';import { AdvertClass, userSetting } from 'basic';import { BusinessError } from '@kit.BasicServicesKit';import { adManagerFinal, adManger } from '../utils';const DOMAIN = 0x0000;export default class EntryAbility extends UIAbility {onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');}onDestroy(): void { hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');}async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> { // Main window is created, set main page for this ability hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); // 冒充向云端获取广告数据的请求 const ad = await new Promise<AdvertClass>((resolve, reject) => { setTimeout(() => { resolve({ showAd: true, isFull: true, adTime: 3, adImg: $r("app.media.start") }) }, 500) }) userSetting.setUserAd(ad); // 云端的广告设置到本地 // 2. 略微有点意思的模式:创建二级窗口,并展示广告 // 用这种方式就在 'pages/Start/Start' 中将关闭广告的方法改为 2 // if (ad.showAd) { // const win = await windowStage.createSubWindow("ad_win") // 二级窗口的实际对象 // await win.showWindow() // 展示二级窗口 // win.setUIContent("pages/Start/Start") // } // //1. 判断是否需要展示广告 // const path = ad.showAd ? 'pages/Start/Start' : 'pages/Index' // 根据抽取的路径加载对应path, 假如要用这种模式,将默认路径 'pages/Index' 替换为path // 使用这种只实用于刚打开应用时,由于他的关闭方式其实是直接跳转到首页(固定的) windowStage.loadContent('pages/Index', (err) => { if (err.code) { hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); return; } // 初始化广告仓库 userSetting.initUserSetting() hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); }); // 3.使用封装window窗口广告模式 // 用这种方式就在 'pages/Start/Start' 中将关闭广告的方法改为 3:adManger.closeAd() // if(ad.showAd){ // adManger.context = this.context // adManger.showAd('pages/Start/Start', 320, 240) // } // 4.采用与页面解耦的模式实现广告
// 这种模式是单独打开一个全屏模态框,而不是打开 'pages/Start/Start' 页面,关闭按钮也在 'pages/Start/Start' 页面中
if (ad.showAd) {
adManagerFinal.context = this.context
adManagerFinal.showAd(ad) // 展示广告
}}onWindowStageDestroy(): void { // Main window is destroyed, release UI related resources hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground(): void { // Ability has brought to foreground hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');}onBackground(): void { // Ability has back to background hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');}} Start.ets
import { AdvertClass, userSetting } from 'basic'
import { router, window } from '@kit.ArkUI'
import { adManagerFinal, adManger } from '../../utils'
@Entry
@Component
struct Start {
// 需要广告对象
@State
ad: AdvertClass = new AdvertClass()
timer: number = -1 // 用来记录定时器的标记
// @State
// ad: Partial<AdvertClass> = {}
aboutToAppear(): void {
console.log('123123123')
// 获取首选项的广告数据给到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)
}
}
closeWin () {
window.findWindow("ad_win").destroyWindow()
}
// 去主页的方法
toMain() {
// 1. 方式一打开广告,关闭是直接跳转到主页
// router.replaceUrl({
// url: 'pages/Index'
// })
// 2.方式二:使用子窗口模式关闭广告
// this.closeWin()
// 3.方式三:使用封装window窗口广告模式
// 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%')
}
}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]