华为HarmonyOS打造开放、合规的广告生态 - 激励广告

场景介绍

激励广告是一种全屏幕的视频广告,用户可以选择点击观看,以换取相应奖励。

接口说明

接口名 描述
loadAd(adParam: AdRequestParams, adOptions: AdOptions, listener: AdLoadListener): void 请求单广告位广告,通过AdRequestParams、AdOptions进行广告请求参数设置,通过AdLoadListener监听广告请求回调。
showAd(ad: Advertisement, options: AdDisplayOptions, context?: common.UIAbilityContext): void 展示广告,通过AdDisplayOptions进行广告展示参数设置。

开发步骤

  1. 获取OAID。

    如果想要为用户更精准的推送广告,可以在请求参数AdRequestParams中添加oaid属性。

    如何获取OAID参见获取OAID信息。

    说明

    使用以下示例中提供的测试广告位必须先获取OAID信息。

  2. 请求单广告位广告。

    需要先创建一个AdLoader对象,通过AdLoader的loadAd方法请求广告,最后通过AdLoadListener,来监听广告的加载状态。
    请求广告关键参数如下所示:

    请求广告参数名 类型 必填 说明
    adType number 是 请求广告类型,激励广告类型为7。
    adId string 是 广告位ID。 * 如果仅调测广告,可使用测试广告位ID:testx9dtjwj8hp。 * 如果要接入正式广告,则需要申请正式的广告位ID。可在应用发布前进入流量变现官网,点击"开始变现",登录鲸鸿动能媒体服务平台进行申请,具体操作详情请参见展示位创建。
    oaid string 否 开放匿名设备标识符,用于精准推送广告。不填无法获取到个性化广告。

    示例代码如下所示:

    复制代码
    1. import { advertising, identifier } from '@kit.AdsKit';
    2. import { common } from '@kit.AbilityKit';
    3. import { hilog } from '@kit.PerformanceAnalysisKit';
    4. import { BusinessError } from '@kit.BasicServicesKit';
    5. @Entry
    6. @Component
    7. struct Index {
    8. private ads: Array<advertising.Advertisement> = \[\];
    9. private context = getContext(this) as common.UIAbilityContext;
    10. private oaid: string = '';
    11. aboutToAppear() {
    12. try {
    13. // 使用Promise回调方式获取OAID
    14. identifier.getOAID().then((data: string) => {
    15. this.oaid = data;
    16. hilog.info(0x0000, 'testTag', '%{public}s', 'Succeeded in getting adsIdentifierInfo by promise');
    17. }).catch((error: BusinessError) => {
    18. hilog.error(0x0000, 'testTag', '%{public}s', `Failed to get adsIdentifierInfo, message: ${error.message}`);
    19. })
    20. } catch (error) {
    21. hilog.error(0x0000, 'testTag', '%{public}s', `Catch err, code: {error.code}, message: {error.message}`);
    22. }
    23. }
    24. build() {
    25. Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
    26. Row() {
    27. Button('requestAd').onClick(() => {
    28. let load: advertising.AdLoader = new advertising.AdLoader(this.context);
    29. this.requestAd(load);
    30. }).width('45%')
    31. }
    32. }
    33. }
    34. private requestAd(adLoader: advertising.AdLoader): void {
    35. const adRequestParam: advertising.AdRequestParams = {
    36. // 广告类型:激励广告
    37. adType: 7,
    38. // 'testx9dtjwj8hp'为测试专用的广告位ID,App正式发布时需要改为正式的广告位ID
    39. adId: 'testx9dtjwj8hp',
    40. // 开放匿名设备标识符
    41. oaid: this.oaid
    42. };
    43. const adOption: advertising.AdOptions = {
    44. // 设置是否请求非个性化广告
    45. nonPersonalizedAd: 0,
    46. // 是否允许流量下载0:不允许,1:允许,不设置以广告主设置为准
    47. allowMobileTraffic: 0,
    48. // 是否希望根据 COPPA 的规定将您的内容视为面向儿童的内容: -1默认值,不确定 0不希望 1希望
    49. tagForChildProtection: -1,
    50. // 是否希望按适合未达到法定承诺年龄的欧洲经济区 (EEA) 用户的方式处理该广告请求: -1默认值,不确定 0不希望 1希望
    51. tagForUnderAgeOfPromise: -1,
    52. // 设置广告内容分级上限: W: 3+,所有受众 PI: 7+,家长指导 J:12+,青少年 A: 16+/18+,成人受众
    53. adContentClassification: 'A'
    54. };
    55. const adLoaderListener: advertising.AdLoadListener = {
    56. onAdLoadFailure: (errorCode: number, errorMsg: string) => {
    57. hilog.error(0x0000, 'testTag', '%{public}s',
    58. `Failed to request ad, message: {errorMsg}, error code: {errorCode}`);
    59. },
    60. onAdLoadSuccess: (ads: Array<advertising.Advertisement>) => {
    61. hilog.info(0x0000, 'testTag', '%{public}s', `Succeeded in requesting ad`);
    62. this.ads.push(...ads);
    63. },
    64. };
    65. adLoader.loadAd(adRequestParam, adOption, adLoaderListener);
    66. }
    67. }
  3. 事件订阅。

    开发者需要在App中订阅com.huawei.hms.pps.action.PPS_REWARD_STATUS_CHANGED事件来监听激励广告页面变化并接收奖励信息。示例代码中的订阅方法registerPPSReceiver()需要在每次展示广告前调用 。

    在订阅到公共事件后,可以从CommonEventData的parameters参数中使用"reward_ad_status"作为key值获取激励广告页面变化状态,使用"reward_ad_data"作为key值获取奖励信息,属性rewardType用来获取奖励物品的名称,rewardAmount用来获取奖励物品的数量。

    示例代码如下所示:

    复制代码
    1. import { commonEventManager, BusinessError } from '@kit.BasicServicesKit';
    2. import { hilog } from '@kit.PerformanceAnalysisKit';
    3. const KEY_REWARD_DATA = "reward_ad_data";
    4. const KEY_REWARD_STATUS = "reward_ad_status";
    5. export class RewardAdStatusHandler {
    6. // 用于保存创建成功的订阅者对象,后续使用其完成订阅及退订的动作
    7. private subscriber: commonEventManager.CommonEventSubscriber | null = null;
    8. // 订阅方法,需要在每次展示广告前调用
    9. public registerPPSReceiver(): void {
    10. if (this.subscriber) {
    11. this.unRegisterPPSReceiver();
    12. }
    13. // 订阅者信息
    14. const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
    15. events: 'com.huawei.hms.pps.action.PPS_REWARD_STATUS_CHANGED',
    16. // publisherBundleName被设置为"com.huawei.hms.adsservice",这意味着只有来自该包名的事件才会被订阅者接受和处理。
    17. // 如果没有明确声明publisherBundleName,那么订阅者可能会收到来自其它包名的伪造事件,从而导致安全性问题或误导。
    18. publisherBundleName: 'com.huawei.hms.adsservice'
    19. };
    20. // 创建订阅者回调
    21. commonEventManager.createSubscriber(subscribeInfo, (err: BusinessError, commonEventSubscriber:
    22. commonEventManager.CommonEventSubscriber) => {
    23. if (err) {
    24. hilog.error(0x0000, 'testTag', '%{public}s',
    25. `createSubscriber error, code: {err.code}, message: {err.message}`);
    26. return;
    27. }
    28. hilog.info(0x0000, 'testTag', '%{public}s', 'Succeeded in creating subscriber');
    29. this.subscriber = commonEventSubscriber;
    30. // 订阅公共事件回调
    31. if (!this.subscriber) {
    32. hilog.warn(0x0000, 'testTag', '%{public}s', 'Need create subscriber');
    33. return;
    34. }
    35. commonEventManager.subscribe(this.subscriber, (err: BusinessError, commonEventSubscriber:
    36. commonEventManager.CommonEventData) => {
    37. if (err) {
    38. hilog.error(0x0000, 'testTag', '%{public}s', `Subscribe error, code: {err.code}, message: {err.message}`);
    39. } else {
    40. hilog.info(0x0000, 'testTag', '%{public}s', 'Subscribe data');
    41. const status: string = commonEventSubscriber?.parameters?.KEY_REWARD_STATUS;
    42. switch (status) {
    43. case AdStatus.AD_OPEN:
    44. hilog.info(0x0000, 'testTag', '%{public}s', 'onAdOpen');
    45. break;
    46. case AdStatus.AD_CLICKED:
    47. hilog.info(0x0000, 'testTag', '%{public}s', 'onAdClick');
    48. break;
    49. case AdStatus.AD_CLOSED:
    50. hilog.info(0x0000, 'testTag', '%{public}s', 'onAdClose');
    51. this.unRegisterPPSReceiver();
    52. break;
    53. case AdStatus.AD_REWARDED:
    54. const rewardData: Record<string, string | number> = commonEventSubscriber?.parameters?.KEY_REWARD_DATA;
    55. const rewardType: string = rewardData?.rewardType as string;
    56. const rewardAmount: number = rewardData?.rewardAmount as number;
    57. hilog.info(0x0000, 'testTag', '%{public}s',
    58. `onAdReward, rewardType: {rewardType}, rewardAmount: {rewardAmount}`);
    59. break;
    60. case AdStatus.AD_VIDEO_START:
    61. hilog.info(0x0000, 'testTag', '%{public}s', 'onAdVideoStart');
    62. break;
    63. case AdStatus.AD_COMPLETED:
    64. hilog.info(0x0000, 'testTag', '%{public}s', 'onAdCompleted');
    65. break;
    66. default:
    67. break;
    68. }
    69. }
    70. });
    71. });
    72. }
    73. // 取消订阅
    74. public unRegisterPPSReceiver(): void {
    75. commonEventManager.unsubscribe(this.subscriber, (err: BusinessError) => {
    76. if (err) {
    77. hilog.error(0x0000, 'testTag', '%{public}s', `Unsubscribe error, code: {err.code}, message: {err.message}`);
    78. } else {
    79. hilog.info(0x0000, 'testTag', '%{public}s', 'Succeeded in unsubscribing');
    80. this.subscriber = null;
    81. }
    82. });
    83. }
    84. }
    85. enum AdStatus {
    86. AD_OPEN = 'onAdOpen',
    87. AD_CLICKED = 'onAdClick',
    88. AD_CLOSED = 'onAdClose',
    89. AD_REWARDED = 'onAdReward',
    90. AD_VIDEO_START = 'onVideoPlayBegin',
    91. AD_COMPLETED = 'onVideoPlayEnd'
    92. }
  4. 展示广告。

    ads为步骤2请求到的广告信息,调用showAd方法来展示广告。示例代码如下所示:

    复制代码
    1. import { advertising } from '@kit.AdsKit';
    2. import { common } from '@kit.AbilityKit';
    3. @Entry
    4. @Component
    5. struct Index {
    6. private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    7. // 步骤2中请求到的广告内容
    8. private ads: Array<advertising.Advertisement> = \[\];
    9. private displayOptions: advertising.AdDisplayOptions = {
    10. // 激励广告视频播放是否静音
    11. mute: true
    12. };
    13. build() {
    14. Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
    15. Row() {
    16. Button('showAd').onClick(() => {
    17. this.showAd();
    18. }).width('45%')
    19. }
    20. }
    21. }
    22. private showAd() {
    23. let load: advertising.AdLoader = new advertising.AdLoader(this.context);
    24. // 此处ads0表示请求到的第一个广告,用户根据实际情况选择
    25. advertising.showAd(this.ads0, this.displayOptions, this.context);
    26. }
    27. }

校验激励广告服务端验证回调

服务端验证回调是指鲸鸿动能平台发送给媒体服务器的网址请求,其中带有特定的查询参数,用来通知媒体服务器某位用户因为与激励视频广告互动而应予以奖励,从而规避欺骗的行为。

奖励用户

  • 在给用户发奖励时,要把握好用户体验和奖励验证之间的平衡。由于服务器端回调会存在延迟的情况,因此我们建议客户端立即奖励用户,同时在收到服务器端回调时对所有奖励进行验证。这种做法可确保奖励符合发放条件,同时提供良好的用户体验。
  • 对于某些应用而言,奖励是否达到发放条件非常重要,用户可适当接受延迟。这时,推荐做法是等待服务器端回调完成验证,再向用户发放奖励。

校验服务端验证回调

说明

App上架至华为应用市场(AppGallery)时间超过12小时才可以收到回调。

  1. 设置激励广告的奖励配置。 您在鲸鸿动能媒体服务平台上申请激励视频广告位时选择"媒体管理(点击媒体名)> 新增展示位 > 选择激励视频(点击下一步,进入编辑页面)",设置奖励类型和奖励数量,并点击"高级设置",设置服务器端验证的URL。如下图:

  2. (可选)设置自定义数据customData和userId。 ads为步骤2请求到的广告信息,调用showAd方法来展示广告。

    您在App中展示激励广告之前设置自定义数据customData和userId。示例代码如下所示:

    复制代码
    1. import { advertising } from '@kit.AdsKit';
    2. import { common } from '@kit.AbilityKit';
    3. @Entry
    4. @Component
    5. struct Index {
    6. private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    7. // 步骤2中请求到的广告内容
    8. private ads: Array<advertising.Advertisement> = \[\];
    9. private displayOptions: advertising.AdDisplayOptions = {
    10. // 激励广告视频播放是否静音
    11. mute: true,
    12. // 设置自定义数据
    13. customData: 'CUSTOM_DATA',
    14. // 设置自定义数据
    15. userId: '1234567'
    16. };
    17. build() {
    18. Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
    19. Row() {
    20. Button('showAd').onClick(() => {
    21. this.showAd();
    22. }).width('45%')
    23. }
    24. }
    25. }
    26. private showAd() {
    27. let load: advertising.AdLoader = new advertising.AdLoader(this.context);
    28. // 此处ads0表示请求到的第一个广告,用户根据实际情况选择
    29. advertising.showAd(this.ads0, this.displayOptions, this.context);
    30. }
    31. }

    说明

    如果没有设置customData和userId,不影响发放奖励事件上报但是服务端验证的参数中没有这两个字段。如果设置customData和userId,必须在展示广告之前设置并且URLEncode之后,长度不超过1024个字符,否则影响服务端验证。

  3. 获取要验证的内容。 用户观看完激励广告时,鲸鸿动能平台服务端会把需要验证的参数以及keyId和sign传给媒体提供的URL:https://www.example.com/feedback([即步骤1中配置的验证URL](https://developer.huawei.com/consumer/cn/doc/#li5211654185420 "即步骤1中配置的验证URL"))。请求体样例:

    复制代码
    1. {
    2. "adId" : "testx9dtjwj8hp",
    3. "data" : "CUSTOM_DATA",
    4. "keyId" : "12345678",
    5. "rewardAmount" : "10",
    6. "rewardName" : "金币",
    7. "sign" : "OA33u6mypnhE4hbmF32N/ibYi1uXt72nDDyYMwjDI6JXVVFKePZYo4F7Fuk2MaG......",
    8. "uniqueId" : "3361626337333932313435313430373438383561376265636130393939313166",
    9. "userId" : "1234567"
    10. }

    服务器端验证回调查询参数说明:

    参数名称 类型 是否必选 描述
    adId String 是 激励视频广告位ID
    data String 否 自定义数据字符串
    keyId String 是 验证回调的密钥
    rewardAmount String 否 奖励数量
    rewardName String 否 奖励奖品
    sign String 是 回调的签名
    uniqueId String 是 获奖事件生成的十六进制的标识符
    userId String 否 用户ID
  4. 组装验证参数 验证内容(除sign、keyId)格式顺序如下:

    adId={adId}&data={data}&rewardAmount={rewardAmount}&rewardName={rewardName}&uniqueId={uniqueId}&userId={userId}

    其中'{}'里面表示参数的值,且参数顺序不能变。如果参数为null或者空字符串,则URL中不拼接该参数。然后用SHA256计算散列值,得到paramContentData。示例代码如下所示:

    复制代码
    1. String adId = request.getParameter("adId");
    2. String data = request.getParameter("data");
    3. ...
    4. String userId = request.getParameter("userId");
    5. String param = "adId=" + adId + "&data=" + data + "&rewardAmount=" + rewardAmount + "&rewardName=" + rewardName + "&uniqueId=" + uniqueId + "&userId=" + userId;
    6. String sha256Value = Sha256Util.digest(param);
    7. byte\[\] paramContentData = sha256Value.getBytes(Charset.forName("UTF-8"));
  5. 获取公钥列表。 a. 在鲸鸿动能媒体服务平台上查看对应的帐户信息时选择"账户"。

    通过点击上图所示的"获取密钥"按钮弹出如下所示的弹框,获取"开发者ID"和"密钥"。

    b. 您根据应用分发区域不同,需要使用对应站点的接口URL去获取公钥列表,不同站点对应的接口URL如下所示:

    将body通过密钥进行HMAC-SHA256加密得到签名,替换到Authorization中,并设置"开发者ID"和Authorization到Header中。示例代码如下所示:

    复制代码
    1. String data = "";
    2. String url = "https://ppscrowd-dre.op.dbankcloud.com/action-lib-track/publickeys";
    3. String authorization = "Digest validTime=\"{0}\", response=\"{1}\"";
    4. // 开发者ID
    5. String userId = "YOUR_PUBLISHER_ID";
    6. // 密钥
    7. String key = "YOUR_KEY";
    8. HttpClient httpclient = HttpClients.createDefault();
    9. HttpGet request = new HttpGet();
    10. try {
    11. // 将body通过密钥进行HMAC-SHA256加密得到签名,替换到Authorization中
    12. String validTime = String.valueOf(System.currentTimeMillis());
    13. String body = validTime + ":/publickeys";
    14. byte\[\] keyBytes = Base64.decodeBase64(key);
    15. byte\[\] bodyBytes = body.getBytes(Charsets.UTF_8);
    16. Mac mac = Mac.getInstance("HmacSHA256");
    17. SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA256");
    18. mac.init(secretKey);
    19. byte\[\] signatureBytes = mac.doFinal(bodyBytes);
    20. String signature = (signatureBytes == null) ? null : Hex.encodeHexString(signatureBytes);
    21. authorization = MessageFormat.format(authorization, validTime, signature);
    22. // 设置开发者ID和Authorization到Header中
    23. request.setURI(new URI(url));
    24. request.setHeader("userId", userId);
    25. request.setHeader("Authorization", authorization);
    26. HttpResponse response = httpclient.execute(request);
    27. data = EntityUtils.toString(response.getEntity());
    28. } catch (Exception e) {
    29. System.out.println(e.getMessage());
    30. }

    返回data消息体(publicKey已匿名化):

    复制代码
    1. {
    2. "keys": [
    3. {
    4. "keyId":"12345678",
    5. "publicKey":"LS0tLS1*******************************************************"
    6. },
    7. {
    8. "keyId": "22345678",
    9. "publicKey":"LS0tLS1*******************************************************"
    10. }
    11. ]
    12. }

    返回消息结构体:

    参数名称 类型 是否必选 描述
    keys List<key> 是 返回公钥列表
    • key结构体:
    参数名称 类型 是否必选 描述
    keyId String 是 密钥ID
    publicKey String 是 公钥
  6. 执行验证。 a. 根据keyId从公钥列表中找到对应的base64编码后的publicKey。

    b. 将paramContentData、publicKey、sign和SHA256withRSA数字签名算法的入参,执行验证。

    示例代码如下所示:

    复制代码
    1. public static boolean verify(byte\[\] data, String publicKey, String sign, String signatureAlgorithm) {
    2. try {
    3. byte\[\] keyBytes = base64Decode(publicKey);
    4. X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
    5. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    6. PublicKey publicK = keyFactory.generatePublic(keySpec);
    7. Signature signature = Signature.getInstance(signatureAlgorithm);
    8. signature.initVerify(publicK);
    9. signature.update(data);
    10. return signature.verify(base64Decode(sign));
    11. } catch (InvalidKeyException | SignatureException | UnsupportedEncodingException | InvalidKeySpecException | NoSuchAlgorithmException e) {
    12. return false;
    13. }
    14. }
    15. private static byte\[\] base64Decode(String encoded) throws UnsupportedEncodingException {
    16. return Base64.decodeBase64(encoded.getBytes("UTF-8"));
    17. }
相关推荐
Flutter_OH8 小时前
Flutter OHOS 三方库与插件适配相关问题
harmonyos
Flutter_OH9 小时前
Flutter OHOS ohos应用编译相关问题
harmonyos
马剑威(威哥爱编程)12 小时前
【共创稿事节】HarmonyOS 7 Agent 开发实战 01:云端大模型 API 接入底座
华为·harmonyos
万物智能信息科技13 小时前
人体感应芯片PIR模块驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
驱动开发·嵌入式硬件·华为·开源·harmonyos
m0_7381858214 小时前
Flutter 鸿蒙化实战:sqflite 适配 OpenHarmony,本地 SQLite 数据库
flutter·华为·harmonyos·鸿蒙
马剑威(威哥爱编程)15 小时前
【共创稿事节】HarmonyOS 7 Agent 开发实战 02:Agent 工程脚手架与对话 UI 骨架
华为·harmonyos
打工仔折腾 AI16 小时前
鸿蒙平板远控电脑实测:ToDesk蓝牙键鼠、双模式鼠标与跨端剪贴板落地记录
人工智能·后端·python·性能优化·计算机外设·电脑·harmonyos
打工仔折腾 AI17 小时前
鸿蒙端ToDesk远控电脑实测:蓝牙键鼠、双模式鼠标与跨端剪贴板实践
后端·python·性能优化·计算机外设·电脑·harmonyos·ai agent 实战
OH_TPC17 小时前
【鸿蒙优选三方库】@ohos/axios:前端转鸿蒙,网络层几乎不用改
前端·华为·harmonyos·鸿蒙
m0_7381858217 小时前
Flutter 鸿蒙化实战:simple_circular_progress_bar 适配 OpenHarmony,环形进度条
flutter·华为·harmonyos·鸿蒙