主题
开屏广告
文档版本 v1.0.0
本页对应 SDK 1.0.0,与离线包 docs/ 目录里的同一份文档内容一致。其他版本见文档中心。
开屏卡在 App 的启动路径上,接法与另两种广告位最大的区别是:必须有一道不依赖任何回调的 兜底放行。线上事故最常见的形态不是没广告,而是卡在开屏页进不去。
完整示例
java
public class SplashActivity extends Activity {
// ---- 兜底放行的三段时限。真实项目按自己的启动体验调,但三段都不能没有 ----
private static final long GUARD_LOAD_MS = 5000; // load 后没有任何回调
private static final long GUARD_SHOW_MS = 2000; // show 后没有 onAdShow(白屏)
private static final long GUARD_SHOWN_MS = 12000; // 展示后没有 onAdClose
private SplashAd mSplashAd;
private boolean mJumped;
private boolean mAdShown;
private final Handler mGuard = new Handler(Looper.getMainLooper());
@Override protected void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.activity_splash);
final ViewGroup container = findViewById(R.id.splash_container); // 需铺满屏幕
mSplashAd = new SplashAd("splash_main");
// 先武装兜底,再 load。反过来写的话,命中缓存时回调是同步的,定时器还没装上
armGuard(GUARD_LOAD_MS, "load 后无回调");
mSplashAd.load(this, new SplashAdListener() {
@Override public void onAdLoaded(String adnType) {
// show() 返回 false = 素材已过期或不可用,必须立刻放行
if (!mSplashAd.show(container)) { goMain(); return; }
armGuard(GUARD_SHOW_MS, "show 后无 onAdShow");
}
@Override public void onAdLoadFailed(AdError error) { goMain(); }
@Override public void onAdShow() {
mAdShown = true;
armGuard(GUARD_SHOWN_MS, "展示后无 onAdClose");
}
@Override public void onAdClick() { }
@Override public void onAdClose() { goMain(); }
});
}
private void armGuard(long delayMs, final String why) {
mGuard.removeCallbacksAndMessages(null);
mGuard.postDelayed(new Runnable() {
@Override public void run() { Log.w("Splash", "兜底放行: " + why); goMain(); }
}, delayMs);
}
// 用户点了广告跳到落地页:暂停计时,回来重新计时。
// 不做这一步,用户在落地页读 20 秒回来会发现开屏自己跳走了
@Override protected void onPause() {
if (mAdShown) mGuard.removeCallbacksAndMessages(null);
super.onPause();
}
@Override protected void onResume() {
super.onResume();
if (mAdShown && !mJumped) armGuard(GUARD_SHOWN_MS, "回到页面后仍无 onAdClose");
}
/** 五条路径(四个回调 + 兜底定时器)都收敛到这里,且只执行一次 */
private synchronized void goMain() {
if (mJumped) return;
mJumped = true;
mGuard.removeCallbacksAndMessages(null);
startActivity(new Intent(this, MainActivity.class));
finish();
}
@Override protected void onDestroy() {
mGuard.removeCallbacksAndMessages(null);
if (mSplashAd != null) mSplashAd.destroy();
super.onDestroy();
}
}要点:
- 容器需铺满屏幕
- 广告 View 由 SDK 挂进容器,业务侧不需要自己
addView - 开屏在启动路径上,超时应配置得短(建议 3 秒内),拿不到就放行进主页。 取值口径见《策略配置格式》
兜底放行
上面的 armGuard 不是可选的最佳实践,是开屏接入的必备项。把全部回调都收敛到 goMain() 只能覆盖「回调来了」的情况,真正出事故的是回调不来:
| 场景 | 现象 | 哪一段兜底救它 |
|---|---|---|
| 广告平台回调丢失 / 被自身异常吞掉 | 开屏页白着不动 | GUARD_LOAD_MS |
show() 返回 true,但广告平台不渲染 | 画面全白,页面永远不关 | GUARD_SHOW_MS |
广告展示了,onAdClose 没来 | 广告播完后卡在开屏页 | GUARD_SHOWN_MS |
中间那一条是真机实测踩到的:优量汇开屏在容器尚未完成布局时展示,回调了「展示成功」却不出画面。 SDK 侧已经修了这个具体的成因,但下一个广告平台、下一个版本还会有别的成因 —— 开屏页能不能进主页,不该取决于第三方 SDK 的回调是否守约。
时限取值:GUARD_LOAD_MS 取该广告位后台配的 total_timeout_ms 再加 1~2 秒余量 (首次广告平台初始化会占掉一部分,见《初始化》「广告平台是按需初始化的」); 另两段按示例里的值即可。
上线前手动验五条路径
正常展示后关闭、点广告再返回、飞行模式无填充、展示中断网、以及展示过程中把广告平台的回调 全部卡住(可用飞行模式模拟)—— 五种情况都必须能进主页。
预加载
开屏是缓存收益最大的广告位。真正的冷启动(新进程)第一条开屏一定是实时请求, 缓存命中发生在热启动开屏、返回桌面后再从图标进入等场景。
java
AdMix.preload(this, "splash_main"); // 主页 onResume,为下一次开屏备货未命中缓存时,开屏的 load() 会以入池方式请求并等待 total_timeout_ms, 超时放行后请求继续跑完,拿到的广告进缓存池留给下一次开屏,不会像纯实时请求那样超时即丢弃。 完整行为见《预加载与缓存》。
Kotlin 写法
kotlin
class SplashActivity : Activity() {
private companion object {
const val GUARD_LOAD_MS = 5000L
const val GUARD_SHOW_MS = 2000L
const val GUARD_SHOWN_MS = 12000L
}
private lateinit var splashAd: SplashAd
private var jumped = false
private var adShown = false
private val guard = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
val container: ViewGroup = findViewById(R.id.splash_container)
splashAd = SplashAd("splash_main")
armGuard(GUARD_LOAD_MS, "load 后无回调") // 先武装兜底,再 load
splashAd.load(this, object : SplashAdListener {
override fun onAdLoaded(adnType: String) {
if (!splashAd.show(container)) { goMain(); return }
armGuard(GUARD_SHOW_MS, "show 后无 onAdShow")
}
override fun onAdLoadFailed(error: AdError) = goMain()
override fun onAdShow() {
adShown = true
armGuard(GUARD_SHOWN_MS, "展示后无 onAdClose")
}
override fun onAdClick() {}
override fun onAdClose() = goMain()
})
}
private fun armGuard(delayMs: Long, why: String) {
guard.removeCallbacksAndMessages(null)
guard.postDelayed({ Log.w("Splash", "兜底放行: $why"); goMain() }, delayMs)
}
override fun onPause() {
if (adShown) guard.removeCallbacksAndMessages(null)
super.onPause()
}
override fun onResume() {
super.onResume()
if (adShown && !jumped) armGuard(GUARD_SHOWN_MS, "回到页面后仍无 onAdClose")
}
@Synchronized private fun goMain() {
if (jumped) return
jumped = true
guard.removeCallbacksAndMessages(null)
startActivity(Intent(this, MainActivity::class.java))
finish()
}
override fun onDestroy() {
guard.removeCallbacksAndMessages(null)
splashAd.destroy()
super.onDestroy()
}
}交互样式
开屏广告的「摇一摇」「扭一扭」「上滑」等交互样式由广告平台后台配置, 热区设置过大即构成诱导点击,是监管通报的高发情形。
SDK 在开屏请求时已显式关闭聚合侧的摇一摇按钮 (MediationAdSlot.setSplashShakeButton(false)),但平台后台配置的交互样式优先级更高, 仍须请平台运营在各广告平台后台逐项确认。详见《合规与隐私》「广告交互样式」。