广告
- 内容:调
migo.createAd({adType, adUnitId, …}),得到 ad 对象的load / show / hide / destroy句柄集; - 引擎:把每次调用折成
AdHandler上的方法,不回包时判广告请求 stall; - 宿主:实现
AdHandler,桥接到自己的广告 SDK(Pangle / GDT优量汇 / 快手联盟 / 自营) — runtime 不链广告 SDK、不持有广商凭证、不判断广告看没看完。
类型常量走 AdHandler.TYPE_*:
| Type | 场景 |
|---|---|
rewardedVideo |
奖励视频 |
interstitial |
插屏 |
banner |
横幅 |
custom |
自定义(原生样式) |
grid |
格状广告 |
gameBanner / gameIcon / gamePortal |
互推容器三种面 |
宿主不必实现全部 — 每种 create* 都有「not supported」的默认 settle(下面解释为什么默认就有行为)。
adId 句柄与回调线程
Section titled “adId 句柄与回调线程”createAd(adId, adType, adUnitId, optionsJson)的adId是 runtime 分配的句柄,一生不变。这个 ad 对象的所有事件都必须用同一个 id 报回;- 所有 AdHandler 方法在 runtime 的宿主线程上调用,不要阻塞本线程做 SDK 工作。回调到内容侧走
AdEventSink(任意线程安全),全集只有 7 个方法:emitLoad(int)/emitLoad(int, boolean)(是否回落到分享页式兜底)、emitError、emitClose(adId, isEnded)、emitShowFailed(=emitError+emitClose(false))、emitResize、emitHide。没有emitShow/emitClick/emitReward— 奖励发放由emitClose的isEnded承载。
灭后不许再报:destroyAd 之后任何 emit* 对该 adId 都会被丢弃 — 引擎的内存纪律,内容侧已经把句柄还回去了。AdEventSink 的发送是任意线程安全,但没有重试语义,不要指望终态在 SDK 里排队。
emitResize 是 CSS px — 和软键盘 HEIGHT_CHANGE 一样是内容布局坐标,报物理像素会让内容按错误的几倍尺寸切广告位。
降级合同:不接也是工作态
Section titled “降级合同:不接也是工作态”内容调没有 handler 的广告请求,不挂起: AdHandler 每个方法自带的默认实现就是 settle 唯一的事实通道:
| 调用 | 无 handler(默认)行为 |
|---|---|
createAd |
emitError(adId, -1, "createAd:fail not supported") |
loadAd |
emitError(adId, -1, "loadAd:fail not supported") |
showAd |
emitShowFailed(adId, -1, "showAd:fail not supported") = emitError + emitClose(adId, false) |
hideAd |
emitHide(adId) — 定位类广告的 onHide 让内容知空间释放出来,不是错误 |
updateAdStyle |
无操作(布局变化被吃下,不报 ad error) |
destroyAd |
无操作 |
那个 isEnded=false 是流量方安全网也是内容兼容层:
- 安全网:真看过才发奖励,虚报
true等于给凭空用户发奖励金 — publishing 侧的钱没出结果之前不能 claim; - 兼容层:常见小游戏内容把「发放奖励 + 继续游戏」的逻辑放在
onClose回调里。不接时如果只报错不 close,内容就停在被暂停的录制条上 — 玩家被卡,比「没广告发现没有机会奖励」更差。
两个为什么是一个规则:凡是内容在等的结果,不 settle 就是 stall;凡是能伪装 paid 的结果,settle 必须带事实标。这是 AdHandler.java 头注释里逐字注出的设计,不是本文建议。
实现样本(极简)
Section titled “实现样本(极简)”只有一个 createAd,用 adType 分路;是否“看完成”经 emitClose 的 isEnded 上报:
session.setAdHandler(new AdHandler() { @Override public void createAd(int adId, String adType, String adUnitId, String optionsJson, AdEventSink sink) { if (!TYPE_REWARDED_VIDEO.equals(adType)) { AdHandler.super.createAd(adId, adType, adUnitId, optionsJson, sink); return; } RewardedAd ad = pangle.create(adUnitId); ad.setListener(new RewardedAd.AdInteractionListener() { @Override public void onAdReady() { sink.emitLoad(adId); } @Override public void onError(int code, String msg) { sink.emitError(adId, code, msg); } @Override public void onReward(RewardItem item, boolean verified) { sink.emitClose(adId, verified); // 完成代表发奖;中断代表不发 } }); // 异步加载失败也走 emitShowFailed:错误 + close(false),不遗留暂停内容 }});optionsJson 是平台风格的剩余创建参数(adIntervals / style / adTheme / gridCount / count / multiton)的 JSON 对象 — 可以是 "{}",不会是 null。按需解析你支持的键;不支持的键安全忽略。
- 不要在 runtime 宿主线程里做 SDK init — 创建广告对象可以,几十毫秒的 SDK init 全算广告请求 stall,跟没接一个效果。
- adUnitId 验证在宿主侧做 — runtime 不验证广告位 id;做错号的请求要按
emitError回。 - 同一 adUnitId 多次 create 是内容的正常行为(
multiton选项),宿主若要复用对象实例,务必在复用前把 sink 关到正确的 adId 下 — 记住 sink 是绑 adId 的通道不是绑 SDK 对象。