主题
Banner 广告
文档版本 v1.2.0
本页对应 SDK 1.2.0,与离线包 docs/ 目录里的同一份文档内容一致。其他版本见文档中心。
与另两种广告位最大的区别是它常驻页面并定时刷新,因此生命周期管理是必做项, 漏掉 destroy() 必然泄漏。
完整示例
java
ViewGroup container = findViewById(R.id.banner_container);
BannerAd bannerAd = new BannerAd("banner_main");
bannerAd.load(activity, container, new BannerAdListener() {
@Override public void onAdLoaded(String adnType) { } // 每次刷新都会回调,adnType 可能变
@Override public void onAdLoadFailed(AdError error) { }
@Override public void onAdShow() { }
@Override public void onAdClick() { }
@Override public void onAdClose() { } // 用户关闭后刷新自动停止
});
// Activity onDestroy 中 —— 不调必然泄漏
bannerAd.destroy();要点:
- 广告 View 由 SDK 挂进
container,业务侧不需要自己addView onAdLoaded每次刷新成功都会回调,adnType可能与上次不同onAdClose表示用户点了广告自带的关闭按钮,此后刷新自动停止
onDestroy 必须调 destroy()
Banner 长期持有 Activity 引用,且定时器一直在转。页面 finish() 后不 destroy() 的后果是 内存持续上涨 + 曝光数虚高(广告还在刷新并上报曝光,而页面已经没了)。
刷新由聚合层驱动
刷新间隔由后台的 refresh_interval_sec 决定,每次刷新重走一整轮完整调度 (竞价、比价、竞胜 / 竞败回传一条不少),业务侧不需要任何干预。
两个适配器都显式关掉了广告平台自带的刷新(穿山甲 setSlideIntervalTime(0)、 优量汇 setRefresh(0))。不这样做的后果是广告位会永远锁死在最初中标的那一家,聚合失去意义。
广告平台后台的 Banner 自动刷新也要关掉
两边都开会重复计费曝光,也会打乱聚合比价。这一项由平台运营在广告平台后台确认。
新旧广告交替时,SDK 先挂新广告再销毁旧的 —— 反过来会有空白闪烁。
尺寸规格
Banner 的尺寸分两层,出问题时要先分清是哪一层:
| 决定于 | 影响 | 谁维护 | |
|---|---|---|---|
| 请求尺寸 | 服务端下发的 width_dp / height_dp | 向广告平台请求什么规格的素材 | 平台运营在后台配置 |
| 容器尺寸 | 宿主布局里那个 ViewGroup 的宽高 | 广告在页面上占多大、怎么显示 | 接入方 |
请求尺寸必须与广告平台后台建该广告位时选定的规格一致,否则穿山甲报 40006 广告位ID不合法,或者返回一条比例怪异的素材。
这个错误码会把排查引向错误的方向
40006 的字面意思是「广告位 ID 不合法」,而真实原因是尺寸规格对不上, 与 ID 本身无关。调整宿主布局里的容器宽高解决不了这个问题 —— 请求尺寸来自服务端配置,不读容器。 处置方式是联系平台运营,核对后台配置的尺寸与广告平台侧登记的规格。
两家广告平台对容器的使用方式并不相同,SDK 把结果拉齐到「都填满容器宽度」:
- 穿山甲:按服务端下发的尺寸请求素材,请求时不读容器。渲染出来之后由 SDK 等比缩放到容器宽度并居中
- 优量汇:广告视图直接加入容器并按容器宽度自适应,容器过窄或宽高比与规格差异过大时会拉伸或留白
因此容器宽高应当按该广告位的规格比例设置,而不是任意值 —— 比例给对了,两家中标时的观感一致。
穿山甲侧的缩放发生在哪一层
缩放用的是绘制期变换,不改广告视图自身的宽高(模板广告的内部排版是按请求尺寸算好的, 改宽高会让素材错位)。点击热区随变换一起缩放,宿主不需要做任何换算。 容器高度写死时缩放倍数会被高度约束住,不会放大到被裁;容器高度是 wrap_content 时, 容器会长到缩放后的高度。
同一广告位下的各家规格应当一致
一个 Banner 广告位会向多家广告平台请求,中标方要到竞价结束才知道。 若各家在自己后台登记的尺寸规格不同(例如一家 320×50、另一家 320×150), 宿主无法事先按正确比例准备槽位:槽位固定高度时,高规格的素材会被裁掉。
因此同一广告位下各广告平台应登记同一尺寸规格。 规格确实无法统一时,宿主容器不要写死高度,改用能随内容撑开的方式。
向平台运营索要 Banner 广告位时,必须同时确认每个广告位的尺寸规格 —— 只拿到广告位 ID、没拿到尺寸,要到真机上才会发现对不上。
读取当前广告的规格
getAdWidthDp() / getAdHeightDp() 返回当前中标的那个平台广告位所登记的规格(dp), 没有广告时返回 0。两者在 onAdLoaded 回调当下即可读取,不必等待布局完成。
java
@Override public void onAdLoaded(String adnType) {
int specW = bannerAd.getAdWidthDp();
int specH = bannerAd.getAdHeightDp();
}返回的是登记规格,不是广告的实际渲染尺寸
两家都会按容器宽度放大或缩小(穿山甲由 SDK 缩放、优量汇自适应), 实测规格 320×50 的广告位放进 427dp 宽的槽位,实际渲染高度是 67~70dp 而不是 50dp。
所以不能把 getAdHeightDp() 直接当成槽位高度。那样做会把槽位设成 50dp、 而广告渲染 67~70dp,素材照样被裁 —— 正是这个接口想避免的情况。
槽位高度写死的页面(例如 uni-app 的 nvue 不支持按内容撑开),正确用法是按规格的宽高比换算:
java
int slotHeightDp = Math.round(specH * (containerWidthDp / (float) specW));换算结果与两家实际渲染出来的高度一致:穿山甲由 SDK 等比缩放到容器宽度,优量汇自适应到容器宽度。
需要「像素级准确」而不是「按比例估算」时,只能在布局完成后测量广告视图的实际大小 —— SDK 不提供这个值,因为它要等一次布局才存在,而这个接口的价值恰恰是 onAdLoaded 当下就能用。
SDK 不会代为调整容器
SDK 只缩放广告视图本身,不会去改宿主容器的 LayoutParams。容器留多大由宿主决定, getAdWidthDp() / getAdHeightDp() 给的就是用来算这个的比例。
在广告上叠加宿主自己的元素
不要遮挡关闭按钮与「广告」标识
两者都由广告平台的模板自己渲染在广告视图内部,位置由模板决定、同一个广告位的不同素材也会变: 实测关闭按钮通常在右上角,「广告」字样在左下与右下都出现过。
遮挡的后果不只是观感:
- 盖住关闭按钮,用户关不掉广告,属于应用商店与主管部门通报的高发项
- 盖住「广告」标识,广告失去可识别性,不符合《广告法》对广告可识别性的要求
最稳妥的做法是不在广告视图上叠加任何东西 —— 宿主自己的标识、举报入口、圆角遮罩 都放在槽位之外。确实要叠加时:
- 避开四个角,只用槽位正中偏上或偏下的窄条,且不要覆盖广告视图本身
- 不要依赖「某个角一定是空的」—— 换一条素材就可能不成立
- 叠加层不要吃掉触摸事件(
android:clickable="false"、不要设OnClickListener), 否则广告点击会被拦下,表现是有曝光没点击
穿山甲一侧的广告视图会被等比缩放
为了与优量汇的自适应表现一致,穿山甲的广告视图会被等比缩放到槽位宽度 (见上文「尺寸规格」)。按固定 dp 定位的叠加层,与广告内元素的相对位置会随缩放倍数变化 —— 槽位越宽,广告内的关闭按钮离右上角越远。叠加层的位置要按槽位比例算,不要写死 dp 偏移。
缓存
Banner 只有首次 load() 会取缓存,定时刷新一律实时请求、不自动补位。 配置未下发 cache_size 时 Banner 默认为 0(不缓存)—— 缓存只对首屏有价值。
Kotlin 写法
kotlin
private var bannerAd: BannerAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_banner)
val container: ViewGroup = findViewById(R.id.banner_container)
bannerAd = BannerAd("banner_main").also { ad ->
ad.load(this, container, object : BannerAdListener {
// 每次定时刷新成功都会回调,adnType 可能与上次不同
override fun onAdLoaded(adnType: String) {}
override fun onAdLoadFailed(error: AdError) {}
override fun onAdShow() {}
override fun onAdClick() {}
override fun onAdClose() {} // 用户关闭后刷新自动停止
})
}
}
override fun onDestroy() {
// Banner 常驻页面且定时刷新,不 destroy 的话页面 finish 后定时器还在转:
// 内存持续上涨 + 曝光数虚高
bannerAd?.destroy()
super.onDestroy()
}验证刷新是否正常
停在 Banner 页看 2~3 个刷新周期,确认无空白、无闪烁、内存不持续上涨。
刷新后「看着没变」通常不是故障
测试广告位的素材池小且风格雷同,肉眼看着像没变,实际已经换了一条。 用刷新计数(onAdLoaded 回调次数)确认,不要靠肉眼。
穿山甲 Banner 在 GroMore 模式下是一段式:适配器拿到非空的 ExpressAdView 才算填充, 拿不到 View 视为无填充并让聚合层继续下探,不调 render()。 填充判定点落在「拿到可展示的 View」这一刻,所以不会出现「竞价赢了、广告位却空着」。