HarmonyOS Next应用开发实战:广告的使用介绍及避坑指南

2024-12-18 16:38:22
131次阅读
0个评论

广告的使用,本该是挺简单的。只是首次接触时,有点懵,不知道怎么用。其实记住两点就行了,一个是广告请求,可以看做是给你提供好了API接口,按照规范传参就行了。一个是广告展示,不同类型广告对应的有组件可用。最后,至于布局嘛,则跟其他普通组件类似。记住这些就已经掌握了广告。

本文先对广告服务就行介绍 ,后续对广告接口进行一层封装,便于使用。

避坑指南

1.模拟器中不支持广告。别纠结于为啥白屏。

2.测试广告位不稳定,别纠结于真机有时候也总返回 21800003

Failed to request ad, code is: 21800003errorMsg is: Failed to load the ad request

测试建议:先在真机跑跑demo,先看看官方的测试广告位是否正常。

广告服务简介

广告服务(Ads Kit)是华为终端平台提供的一种流量变现服务,帮助开发者解决流量变现的难题。通过Ads Kit,开发者可以在自己的应用中展示精美的、高价值的广告内容,从而获得广告收益。同时,Ads Kit也为广告主提供了广告服务,帮助他们进行个性化的广告投放和广告转化渠道跟踪。

Ads Kit支持多种广告形式,包括横幅广告、原生广告、激励广告、插屏广告、开屏广告和贴片广告。此外,Ads Kit还提供了广告标识符和转化跟踪能力,方便广告平台和广告主进行个性化广告投放和广告转化渠道跟踪。

广告类型介绍

横幅广告

横幅广告(Banner广告)是一种在应用顶部、中部或底部占据一个位置的矩形图片广告。广告内容每隔一段时间会自动刷新。

原生广告

原生广告是与应用内容融为一体的广告,通过“和谐”的内容呈现广告信息,展示形式包含图片和视频,支持开发者自由定制界面。

开屏广告

开屏广告是一种在应用启动时且在应用主界面显示之前需要被展示的广告。开屏广告分为全屏开屏广告和半屏开屏广告。

广告使用指南

横幅广告示例

以下是一个横幅广告的示例代码:

import { advertising, AutoAdComponent, identifier } from '@kit.AdsKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Index {
  @State adParam: advertising.AdRequestParams = {
    adType: 8, // 广告类型:横幅广告
    adId: 'testw6vs28auh3', // 测试专用的广告位ID
    adWidth: 360, // 广告位宽
    adHeight: 57, // 广告位高
    oaid: '' // 开放匿名设备标识符
  };
  private adOptions: advertising.AdOptions = {
    allowMobileTraffic: 0, // 是否允许流量下载
    tagForChildProtection: -1, // 是否面向儿童
    tagForUnderAgeOfPromise: -1, // 是否面向未达到法定承诺年龄的用户
    adContentClassification: 'A' // 广告内容分级上限
  };
  private displayOptions: advertising.AdDisplayOptions = {
    refreshTime: 30000 // 广告轮播时间间隔
  }
  private ratio: number = 1;
  private adWidth: number = -1;
  private adHeight: number = -1;
  @State visibilityState: Visibility = Visibility.Visible;

  aboutToAppear() {
    identifier.getOAID().then((data) => {
      this.adParam.oaid = data;
    }).catch((error: BusinessError) => {
      hilog.error(0x0000, 'testTag', '%{public}s', `Failed to get adsIdentifierInfo, code: ${error.code}, message: ${error.message}`);
    });

    if (this.adParam?.adWidth && this.adParam?.adWidth > 0) {
      this.adWidth = this.adParam?.adWidth;
    }
    if (this.adParam?.adHeight && this.adParam?.adHeight > 0) {
      this.adHeight = this.adParam?.adHeight;
    }
    if (this.adWidth > 0 && this.adHeight > 0) {
      this.ratio = this.adWidth / this.adHeight;
    }
  }

  build() {
    if (this.adParam.oaid) {
      Stack({ alignContent: Alignment.Bottom }) {
        this.buildBannerView()
      }
    }
  }

  @Builder
  buildBannerView() {
    Row() {
      AutoAdComponent({
        adParam: this.adParam,
        adOptions: this.adOptions,
        displayOptions: this.displayOptions,
        interactionListener: {
          onStatusChanged: (status: string, ad: advertising.Advertisement, data: string) => {
            hilog.info(0x0000, 'testTag', '%{public}s', `status is ${status}`);
            switch (status) {
              case AdStatus.AD_OPEN:
                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdOpen');
                break;
              case AdStatus.AD_CLICKED:
                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdClick');
                break;
              case AdStatus.AD_CLOSED:
                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdClose');
                this.visibilityState = Visibility.None;
                break;
              case AdStatus.AD_LOAD:
                hilog.info(0x0000, 'testTag', '%{public}s', 'Status is onAdLoad');
                break;
              case AdStatus.AD_FAIL:
                hilog.error(0x0000, 'testTag', '%{public}s', 'Status is onAdFail');
                this.visibilityState = Visibility.None;
                break;
            }
          }
        }
      })
    }
    .width('100%')
    .aspectRatio(this.ratio)
    .visibility(this.visibilityState)
  }
}

enum AdStatus {
  AD_LOAD = 'onAdLoad',
  AD_FAIL = 'onAdFail',
  AD_OPEN = 'onAdOpen',
  AD_CLICKED = 'onAdClick',
  AD_CLOSED = 'onAdClose',
  AD_REWARDED = 'onAdReward',
  AD_VIDEO_START = 'onVideoPlayBegin',
  AD_COMPLETED = 'onVideoPlayEnd'
}

原生广告示例

以下是一个原生广告的示例代码:

import { advertising, identifier } from '@kit.AdsKit';
import { router } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
export struct LoadAd {
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  private oaid: string = '';

  aboutToAppear() {
    identifier.getOAID().then((data: string) => {
      this.oaid = data;
    }).catch((error: BusinessError) => {
      hilog.error(0x0000, 'testTag', '%{public}s', `Failed to get adsIdentifierInfo, error code: ${error.code}, message: ${error.message}`);
    });
  }

  build() {
    Column() {
      Button("请求原生广告", { type: ButtonType.Normal, stateEffect: true }).onClick(() => {
        this.requestAd();
      })
    }
    .width('100%')
    .height('100%')
  }

  private requestAd(): void {
    const adDisplayOptions: advertising.AdDisplayOptions = {
      mute: false // 是否静音
    }
    const adOptions: advertising.AdOptions = {
      nonPersonalizedAd: 1, // 是否请求非个性化广告
      allowMobileTraffic: 0, // 是否允许流量下载
      tagForChildProtection: -1, // 是否面向儿童
      tagForUnderAgeOfPromise: -1, // 是否面向未达到法定承诺年龄的用户
      adContentClassification: 'A' // 广告内容分级上限
    }
    const nativeVideoAdReqParams: advertising.AdRequestParams = {
      adId: 'testu7m3hc4gvm', // 测试专用的广告位ID
      adType: 3, // 广告类型:原生广告
      adCount: 1, // 广告数量
      enableDirectReturnVideoAd: true, // 等所有广告素材下载完后再回调
      oaid: this.oaid
    }
    const adLoaderListener: advertising.AdLoadListener = {
      onAdLoadFailure: (errorCode: number, errorMsg: string) => {
        hilog.error(0x0000, 'testTag', '%{public}s', `Failed to request ad, message: ${errorMsg}, error code: ${errorCode}`);
      },
      onAdLoadSuccess: (ads: Array<advertising.Advertisement>) => {
        hilog.info(0x0000, 'testTag', '%{public}s', 'Succeeded in requesting ad');
        routePage('pages/NativeAdPage', ads, adDisplayOptions);
      }
    };
    const load: advertising.AdLoader = new advertising.AdLoader(this.context);
    load.loadAd(nativeVideoAdReqParams, adOptions, adLoaderListener);
  }
}

async function routePage(pageUri: string, ads: Array<advertising.Advertisement | null>, displayOptions: advertising.AdDisplayOptions) {
  let options: router.RouterOptions = {
    url: pageUri,
    params: {
      ads: ads,
      displayOptions: displayOptions
    }
  }
  try {
    router.pushUrl(options);
  } catch (error) {
    hilog.error(0x0000, 'testTag', '%{public}s', `Failed to routePage callback, code: ${error.code}, msg: ${error.message}`);
  }
}

开屏广告示例

以下是一个开屏广告的示例代码:

import { router, Prompt } from '@kit.ArkUI';
import { advertising, identifier } from '@kit.AdsKit';
import { common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
export struct Index {
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  private oaid: string = '';
  private isTimeOut: boolean = false;
  private timeOutDuration: number = 1 * 1000; // 超时时间
  private timeOutIndex: number = -1;
  private adDisplayOptions: advertising.AdDisplayOptions = {
    mute: false // 是否静音
  }
  private adOptions: advertising.AdOptions = {
    allowMobileTraffic: 0, // 是否允许流量下载
    tagForChildProtection: -1, // 是否面向儿童
    tagForUnderAgeOfPromise: -1, // 是否面向未达到法定承诺年龄的用户
    adContentClassification: 'A' // 广告内容分级上限
  }
  private splashVideoAdReqParams: advertising.AdRequestParams = {
    adId: 'testd7c5cewoj6', // 测试专用的广告位ID
    adType: AdType.SPLASH_AD, // 广告类型:开屏广告
    adCount: 1, // 广告数量
    oaid: this.oaid
  }
  private splashImageAdReqParams: advertising.AdRequestParams = {
    adId: 'testq6zq98hecj', // 测试专用的广告位ID
    adType: AdType.SPLASH_AD, // 广告类型:开屏广告
    adCount: 1, // 广告数量
    oaid: this.oaid
  }

  aboutToAppear() {
    identifier.getOAID().then((data: string) => {
      this.oaid = data;
    }).catch((error: BusinessError) => {
      hilog.error(0x0000, 'testTag', '%{public}s', `Failed to get adsIdentifierInfo, code: ${error.code}, message: ${error.message}`);
    });
  }

  build() {
    Column() {
      CustomButton({
        mText: 'splash full screen request', mOnClick: () => {
          this.requestAd(this.splashVideoAdReqParams, this.adOptions);
        }
      });

      CustomButton({
        mText: 'splash half screen request', mOnClick: () => {
          this.requestAd(this.splashImageAdReqParams, this.adOptions);
        }
      });
    }
    .width('100%')
    .height('100%')
  }

  private requestAd(adReqParams: advertising.AdRequestParams, adOptions: advertising.AdOptions): void {
    const adLoaderListener: advertising.AdLoadListener = {
      onAdLoadFailure: (errorCode: number, errorMsg: string) => {
        clearTimeout(this.timeOutIndex);
        if (this.isTimeOut) {
          return;
        }
        hilog.error(0x0000, 'testTag', '%{public}s', `Failed to request ad. errorCode is: ${errorCode}, errorMsg is: ${errorMsg}`);
        Prompt.showToast({
          message: `Failed to request ad, code is:  ${errorCode} , errorMsg is: ${errorMsg}`,
          duration: 1000
        });
      },
      onAdLoadSuccess: (ads: Array<advertising.Advertisement>) => {
        clearTimeout(this.timeOutIndex);
        if (this.isTimeOut) {
          return;
        }
        hilog.info(0x0000, 'testTag', '%{public}s', 'Succeeded in requesting ad!');
        if (ads[0].adType === AdType.SPLASH_AD) {
          if (ads[0]?.isFullScreen === true) {
            routePage('pages/SplashFullScreenAdPage', ads, this.adDisplayOptions);
          } else {
            routePage('pages/SplashHalfScreenAdPage', ads, this.adDisplayOptions);
          }
        } else {
          hilog.error(0x0000, 'testTag', '%{public}s', 'Error adType');
        }
      }
    };
    const load: advertising.AdLoader = new advertising.AdLoader(this.context);
    adReqParams.oaid = this.oaid;
    this.timeOutHandler();
    load.loadAd(adReqParams, adOptions, adLoaderListener);
  }

  private timeOutHandler(): void {
    this.isTimeOut = false;
    this.timeOutIndex = setTimeout(() => {
      this.isTimeOut = true;
      const options: router.RouterOptions = {
        url: 'pages/AdsServicePage',
      };
      router.pushUrl(options);
      hilog.error(0x0000, 'testTag', '%{public}s', 'load ad time out');
    }, this.timeOutDuration);
  }
}

async function routePage(pageUri: string, ads: Array<advertising.Advertisement | null>, displayOptions: advertising.AdDisplayOptions) {
  let options: router.RouterOptions = {
    url: pageUri,
    params: {
      ads: ads,
      displayOptions: displayOptions
    }
  }
  try {
    router.pushUrl(options);
  } catch (error) {
    hilog.error(0x0000, 'testTag', '%{public}s', `Failed to routePage callback, code: ${error.code}, msg: ${error.message}`);
  }
}

export enum AdType {
  SPLASH_AD = 1 // 开屏广告的类型
}

@Component
export struct CustomButton {
  private mText: string | Resource = '';
  private mHeight: number = 40;
  private mOnClick: (event?: ClickEvent) => void = (): void => { };

  build() {
    Column() {
      Button(this.mText)
        .backgroundColor('#d3d4d6')
        .fontSize(20)
        .fontColor('#000')
        .fontWeight(FontWeight.Normal)
        .align(Alignment.Center)
        .type(ButtonType.Capsule)
        .width('90%')
        .height(this.mHeight)
        .margin({ top: 10, bottom: 5 })
        .onClick(this.mOnClick);
    }
  }
}

广告接口封装

为了方便使用,可以将广告请求和展示的逻辑封装成一个工具类:

import { router } from '@kit.ArkUI';
import { advertising, identifier } from '@kit.AdsKit';
import { common, abilityAccessCtrl } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { Log } from './logutil';
 
/**
 * 广告类型
 */
export enum AdType {
  // 开屏广告的类型
  SPLASH_AD = 1,
  // 原生广告的类型
  NATIVE_AD = 3,
  //8:banner广告。
  BANNER_AD = 8
  //12:插屏广告。
  //60:贴片广告。
}
 
/**
 * 广告状态
 */
export enum AdStatus {
  AD_OPEN = "onAdOpen",
  AD_CLICKED = "onAdClick",
  AD_CLOSED = "onAdClose",
  AD_LOAD = "AD_LOAD",
  AD_FAIL = "AD_FAIL"
}
export default class AdUtil {
  /**
   * 设备oaid
   */
  public static oaid: string = '';
  /**
   * 广告展示参数
   */
  public adDisplayOptions: advertising.AdDisplayOptions = {
    // 是否静音,默认不静音
    mute: false,
    // 广告轮播的时间间隔,单位ms,取值范围[30000, 120000]
    refreshTime: 30000
  }
  /**
   * 原生广告配置
   */
  public adOptions: advertising.AdOptions = {
    // 是否允许流量下载  0不允许 1允许,不设置以广告主设置为准
    allowMobileTraffic: 1,
    // 是否希望根据 COPPA 的规定将您的内容视为面向儿童的内容: -1默认值,不确定 0不希望 1希望
    tagForChildProtection: -1,
    // 是否希望按适合未达到法定承诺年龄的欧洲经济区 (EEA) 用户的方式处理该广告请求: -1默认值,不确定 0不希望 1希望
    tagForUnderAgeOfPromise: -1,
    // 设置广告内容分级上限: W: 3+,所有受众 PI: 7+,家长指导 J:12+,青少年 A: 16+/18+,成人受众
    adContentClassification: 'A'
  }
  // Banner广告请求参数
  public bannerAdReqParams: advertising.AdRequestParams = {
    adId: 'x5txrf02lv',
    adType: AdType.BANNER_AD,
    adCount: 1,
    adWidth: 360,
    adHeight: 57
  }
  /**
   * 开屏视频广告请求参数
   */
  public splashVideoAdReqParams: advertising.AdRequestParams = {
    adId: 'x09l50xtco',
    adType: AdType.SPLASH_AD,
    adCount: 1
  }
  /**
   * 开屏图片广告请求参数
   */
  public splashImageAdReqParams: advertising.AdRequestParams = {
    adId: 'testq6zq98hecj',
    adType: AdType.SPLASH_AD,
    adCount: 1,
    oaid:AdUtil.oaid
  }
  /**
   * 原生视频广告请求参数
   */
  public nativeVideoAdReqParams: advertising.AdRequestParams = {
    adId: 't5q3phvauy',
    adType: AdType.NATIVE_AD,
    adCount: 1,
    // 原生广告自定义扩展参数。等所有广告素材下载完后再回调
    enableDirectReturnVideoAd: true
  }
  /**
   * 原生大图广告请求参数
   */
  public nativeBigImageAdReqParams: advertising.AdRequestParams = {
    adId: 't5q3phvauy',
    adType: AdType.NATIVE_AD,
    adCount: 1,
    // 原生广告自定义扩展参数。等所有广告素材下载完后再回调
    enableDirectReturnVideoAd: true
  }
  /**
   * 原生三图广告请求参数
   */
  public nativeThreeImageAdReqParams: advertising.AdRequestParams = {
    adId: 't5q3phvauy',
    adType: AdType.NATIVE_AD,
    adCount: 1,
    // 原生广告自定义扩展参数。等所有广告素材下载完后再回调
    enableDirectReturnVideoAd: true
  }
  /**
   * 原生小图广告请求参数
   */
  public nativeSmallImageAdReqParams: advertising.AdRequestParams = {
    adId: 't5q3phvauy',
    adType: AdType.NATIVE_AD,
    adCount: 1,
    // 原生广告自定义扩展参数。等所有广告素材下载完后再回调
    enableDirectReturnVideoAd: true
  }
 
  /**
   * 获取 OAID
   * @param context
   */
  public requestOAIDTrackingConsentPermissions(context: common.Context): void {
    // 进入页面时,向用户请求授权广告跨应用关联访问权限
    const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    try {
      atManager.requestPermissionsFromUser(context, ['ohos.permission.APP_TRACKING_CONSENT']).then((data) => {
        if (data.authResults[0] === 0) {
          Log.info('Succeeded in requesting permission');
          identifier.getOAID().then((data: string) => {
            AdUtil.oaid = data;
            Log.info('Succeeded in getting adsIdentifierInfo by promise : ' + data);
          }).catch((error: BusinessError) => {
            Log.error(`Failed to get AdsIdentifierInfo, message: ${error.message}`);
          })
        } else {
          Log.info('user rejected');
        }
      }).catch((err: BusinessError) => {
        Log.error(`Failed to request permission , error: ${err.code} ${err.message}`);
      })
    } catch (err) {
      Log.error(`catch err, code: ${err.code}, msg: ${err.message}`);
    }
  }
 
  public requestAd(context: common.Context, adReqParams: advertising.AdRequestParams, adOptions: advertising.AdOptions,
    routePage: string,errPage:string): void {
    // 给AdRequestParams设置oaid参数
    adReqParams.oaid = AdUtil.oaid;
    // 广告请求回调监听
    const adLoaderListener: advertising.AdLoadListener = {
      // 广告请求失败回调
      onAdLoadFailure: (errorCode: number, errorMsg: string) => {
        Log.error(`request ad errorCode is: ${errorCode}, errorMsg is: ${errorMsg}`);
        if (errPage != '') {
          this.routePage(errPage);
        }
      },
      // 广告请求成功回调
      onAdLoadSuccess: (ads: Array<advertising.Advertisement>) => {
        Log.info('succeeded in requesting ad!');
        // 保存请求到的广告内容用于展示
        if (canIUse('SystemCapability.Advertising.Ads')) {
          if (ads[0].adType === AdType.SPLASH_AD) {
            // 调用开屏广告展示页面
            if (ads[0]?.isFullScreen === true) {
              this.routePage('pages/SplashFullScreenAdPage', ads, this.adDisplayOptions);
            } else {
              this.routePage('pages/SplashHalfScreenAdPage', ads, this.adDisplayOptions);
            }
          } else {
            this.routePage(routePage, ads, this.adDisplayOptions);
            //hilog.error(0x0000, 'testTag', '%{public}s', 'Error adType');
          }
        }
      }
    };
    // 创建AdLoader广告对象
    const load: advertising.AdLoader = new advertising.AdLoader(context);
    // 调用广告请求接口
    Log.info('request ad!');
    load.loadAd(adReqParams, adOptions, adLoaderListener);
  }
 
  public routePage(pageUri: string, ads?: Array<advertising.Advertisement | null>,
    displayOptions?: advertising.AdDisplayOptions) {
    let options: router.RouterOptions = {
      url: pageUri,
      params: {
        ads: ads,
        displayOptions: displayOptions
      }
    }
    try {
      Log.info(`routePage  + ${pageUri}`);
      router.replaceUrl(options);
    } catch (error) {
      Log.error(`routePage fail callback, code: ${error.code}, msg: ${error.message}`);
    }
  }
}
 
export const adUtils = new AdUtil()

封装后使用

// 组件生命周期
  async aboutToAppear() {
    Log.info('StartPage aboutToAppear');
    let value:Object = await PreferencesUtils.getPreferences(Constant.KEY_IS_FIRST_OPEN);
    if(value === null || value === 'YES'){
      this.privacyDialogController?.open()
    }else{
      adUtils.requestOAIDTrackingConsentPermissions(this.context);
      adUtils.requestAd(this.context,adUtils.splashImageAdReqParams,adUtils.adDisplayOptions,'pages/SplashFullScreenAdPage','pages/Index')
     
    }
  }

团队介绍

本文由坚果派团队创作。坚果派团队由一群热爱HarmonyOS/OpenHarmony的开发者组成,拥有12个华为HDE认证开发者,以及来自多个领域的30多位拥有大量粉丝的博主。团队专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉的相关内容,目前团队成员分布在包括北京、上海、南京、深圳、广州、宁夏等多个城市。已开发鸿蒙原生应用和三方库共60+,欢迎各位开发者与我们交流探讨。

收藏00

登录 后评论。没有帐号? 注册 一个。