一、SplashScreen 启动屏:从适配到自定义的完整实践

启动屏这个话题,说实话,在 Android 12 之前一直是个「野路子」。各家有各家的玩法,有的用 Activity 做闪屏,有的用 Window 背景糊弄,还有的直接上第三方库。我在 2021 年接手一个日活百万的 App 时,光是启动屏的兼容问题就修了三个版本——嗯,那会儿 Android 12 刚出,SplashScreen API 还是强制适配的。

今天我们就来彻底搞懂 SplashScreen。我会从适配原理讲起,再到自定义主题、动画衔接,最后聊聊广告页怎么和启动屏无缝配合。你想想看,如果连第一帧画面都卡顿或者闪白,用户大概率就直接划走了。

1.1 SplashScreen API 的适配原理

说白了,SplashScreen API 是 Android 12 引入的一套系统级启动机制。它的核心思路是:在应用进程启动到 Activity 显示之间,系统直接接管了窗口的绘制。这样就不会出现「白屏」或者「黑屏」的尴尬期。

我个人的理解是,它把原来开发者手动控制的「启动窗口」变成了系统可控的「启动画面」。系统会在冷启动时,从你的主题中读取 windowSplashScreen* 系列属性,然后渲染出一个统一的启动界面。

核心适配原理:
  • 系统在 Zygote 进程 fork 出应用进程后,立即读取主题中的 SplashScreen 属性
  • 使用 SplashScreenView 绘制启动画面,这是一个独立的 View 层级
  • 当 Activity 的 onResume() 执行完毕后,系统自动移除启动画面
  • 整个过程不需要开发者手动创建 Activity 或 Fragment

这里有个关键点:SplashScreen API 只影响冷启动和温启动。热启动时 Activity 已经在内存中,系统不会显示启动画面。我在项目中遇到过测试同事反馈「为什么我切后台再回来没有启动屏了?」——其实就是热启动,这是正常行为。

1.2 自定义启动主题与动画

系统默认的启动画面就是一个白色背景加应用图标,说实话有点丑。我们可以通过主题属性来自定义。来看一个我常用的配置:

<!-- themes.xml -->
<style name="Theme.SplashDemo" parent="Theme.SplashScreen">
    <!-- 启动画面背景色 -->
    <item name="windowSplashScreenBackground">@color/splash_background</item>
    
    <!-- 启动图标 -->
    <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
    
    <!-- 图标动画时长 -->
    <item name="windowSplashScreenAnimationDuration">500</item>
    
    <!-- 启动后显示的 Activity 主题 -->
    <item name="postSplashScreenTheme">@style/Theme.AppMain</item>
</style>

注意这里的 windowSplashScreenAnimatedIcon,它支持 AnimatedVectorDrawable。也就是说,你可以做一个简单的图标缩放或旋转动画。我个人习惯用 Lottie 动画转成 AVD,效果很流畅。

小技巧: 如果你想让启动图标有「从中心放大」的效果,可以在 AVD 中设置 scale 动画。时长建议控制在 300-600ms 之间,太长了用户会觉得卡。

还有一个容易被忽略的属性:windowSplashScreenBrandingImage。这个可以在启动画面底部显示品牌 Logo,比如「Powered by xxx」。不过我在实际项目中很少用,因为会挤占图标空间,尤其是在全面屏手机上。

1.3 启动屏与广告页的衔接

这是很多 App 的刚需——先显示启动屏,再展示广告页,最后进入主页。但这里有个坑:SplashScreen API 默认会在 Activity 可见后立即消失,如果你在 onCreate() 里直接 setContentView 加载广告页,系统会同时移除启动画面,导致画面闪烁。

我的解决方案是:延迟 SplashScreen 的移除时机。具体做法是调用 SplashScreen.setKeepOnScreenCondition()

class SplashActivity : AppCompatActivity() {
    
    private lateinit var splashScreen: SplashScreen
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 获取 SplashScreen 实例
        splashScreen = installSplashScreen()
        
        // 保持启动画面,直到广告加载完成
        splashScreen.setKeepOnScreenCondition {
            // 返回 true 表示保持启动画面,false 表示移除
            !isAdReady
        }
        
        setContentView(R.layout.activity_splash)
        
        // 模拟广告加载
        loadAd()
    }
    
    private var isAdReady = false
    
    private fun loadAd() {
        // 广告加载完成后
        // isAdReady = true
        // 系统会自动移除启动画面,显示广告页
    }
}

这里有个细节:setKeepOnScreenCondition 是在主线程回调的,所以不要在里边做耗时操作。我曾经在这里直接调了网络请求,结果导致启动画面卡了 5 秒才消失——用户反馈说「App 点开没反应」,其实就是这个原因。

注意: 广告页的布局不要放在 onCreate() 中直接 setContentView。建议先加载一个透明或纯色背景的布局,等广告 SDK 回调后再切换内容。否则会出现「启动画面 → 广告页闪一下 → 广告内容」的闪烁问题。

1.4 Android 12+ 的启动屏规范

从 Android 12 开始,Google 对启动屏有了明确的规范要求。如果你的 App targetSdkVersion 是 31 及以上,系统会强制使用 SplashScreen API。换句话说,你不能再用「黑屏 + 延时跳转」这种土办法了。

规范要点如下:

规范项 要求 我的建议
图标尺寸 108dp × 108dp(自适应图标) 使用 AdaptiveIconDrawable,兼容不同形状
图标背景 建议使用透明背景 避免在图标周围加白边,系统会自动处理
动画时长 不超过 1000ms 控制在 400-600ms,太长影响体验
品牌图片 高度不超过 48dp 非必需,建议不加
退出时机 Activity 绘制第一帧后自动退出 使用 setKeepOnScreenCondition 控制

还有一个容易被忽视的点:启动画面不能包含任何交互元素。也就是说,你不能在启动画面上放「跳过」按钮或者点击事件。这是 Google 的硬性规定,审核时可能会被拒。如果你需要跳过功能,建议在广告页实现。

我记得有一次,产品经理要求「启动画面加一个点击跳转主页的功能」,我解释了半天说这是系统级画面,没法加点击事件。最后我们折中了一下:把启动画面时间缩短到 300ms,然后快速过渡到广告页,广告页上放跳过按钮。这样既满足了需求,又符合规范。

知识体系总览

下面这张图是我梳理的 SplashScreen 核心知识结构,你可以对照着回顾一下:

SplashScreen API 适配原理 • 系统级窗口接管 • 冷/温启动生效 • onResume后自动移除 自定义主题与动画 • windowSplashScreenBackground • AnimatedVectorDrawable • 动画时长 300-600ms 广告页衔接 • setKeepOnScreenCondition • 延迟移除启动画面 • 避免布局闪烁 Android 12+ 启动屏规范 • 图标 108dp × 108dp • 动画时长 ≤ 1000ms • 无交互元素 • 使用 AdaptiveIconDrawable • 品牌图片 ≤ 48dp • targetSdkVersion ≥ 31 强制适配

说实话,SplashScreen API 的出现让启动屏的开发变得规范了很多。以前我们还要自己处理启动窗口的显示和隐藏,现在系统帮我们做了大部分工作。你只需要关注主题配置和广告页的衔接逻辑就好。

最后提醒一句:启动屏不是功能,是体验。不要在上面堆太多东西,保持简洁、快速、流畅,这才是用户真正想要的。


公众号:蓝海资料掘金营,微信deep3321