18. 启动闪屏优化:设置启动主题、使用SplashScreen API、减少白屏/黑屏时间

闪屏优化,说白了就是解决用户点击图标后那一段「干瞪眼」的时间。我见过太多App启动时先闪一下白屏,再跳转闪屏页,最后才进入主页——这种体验,用户没卸载真的是给面子了。

今天咱们就聊聊怎么把这段空白时间压到极致。我个人习惯把闪屏优化分成三个层次:视觉欺骗、系统API利用、冷启动加速。咱们一个一个来。

18.1 为什么会有白屏/黑屏?

先搞清楚根因。Android应用启动时,Zygote进程会fork一个新进程,然后加载Application、创建主线程、初始化Activity。这个过程需要时间。

但系统不会让用户干等着。它会先根据你的AndroidManifest.xml中设置的android:theme,绘制一个「预览窗口」(Preview Window)。如果你没设置主题,默认就是白色或黑色背景——这就是白屏/黑屏的由来。

核心结论:白屏/黑屏不是Bug,是系统在等你初始化完成。我们要做的,是让这个「等待画面」看起来不那么突兀。

18.2 设置启动主题:最简单的视觉欺骗

这是最基础、也最有效的方案。我早期做优化时,就靠这一招把启动感知时间缩短了40%。

思路很简单:给启动Activity设置一个自定义主题,背景用你的闪屏背景色或Logo,这样预览窗口看起来就像闪屏页本身。

步骤一:定义启动主题

<!-- res/values/themes.xml -->
<style name="Theme.Splash" parent="Theme.AppCompat.NoActionBar">
    <item name="android:windowBackground">@drawable/splash_background</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowNoTitle">true</item>
</style>

步骤二:创建闪屏背景drawable

<!-- res/drawable/splash_background.xml -->
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/splash_bg_color" />
    <item
        android:gravity="center"
        android:drawable="@drawable/ic_splash_logo" />
</layer-list>

步骤三:在Manifest中应用

<activity
    android:name=".MainActivity"
    android:theme="@style/Theme.Splash">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

步骤四:在Activity中恢复主题

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // 在super.onCreate之前恢复正式主题
        setTheme(R.style.Theme_App)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

我的经验:背景drawable不要用太复杂的矢量图,否则系统绘制预览窗口时反而会卡。我一般只用纯色+一个居中PNG Logo,文件控制在50KB以内。

18.3 使用SplashScreen API(Android 12+)

从Android 12开始,Google推出了官方的SplashScreen API。说实话,这玩意儿刚出来时我挺抵触的——又得适配新API。但用下来发现,它确实比我们自己手写的方案更优雅。

为什么推荐用官方API?

  • 统一体验:所有App的闪屏动画风格一致,用户不会觉得跳戏
  • 自动适配:系统会根据主题自动选择亮色/暗色模式
  • 冷启动优化:API内部做了预加载优化,比我们自己写更高效

集成步骤

// 1. 在build.gradle中添加依赖
implementation 'androidx.core:core-splashscreen:1.0.1'

// 2. 在Activity中初始化
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // 必须在super.onCreate之前调用
        installSplashScreen()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

// 3. 在主题中配置闪屏样式
<style name="Theme.App" parent="Theme.SplashScreen">
    <item name="windowSplashScreenBackground">@color/splash_bg</item>
    <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
    <item name="postSplashScreenTheme">@style/Theme.App.Main</item>
</style>

注意:如果你要兼容Android 11及以下设备,还是得用18.2节的自定义主题方案。SplashScreen API只在Android 12+生效。我曾经在一个项目里只用了API,结果测试在Android 11上直接闪白屏——嗯,那次被测试小姐姐骂得不轻。

18.4 减少白屏/黑屏时间的进阶技巧

视觉欺骗只是治标,真正要减少等待时间,还得从冷启动链路下手。我总结了一套「三板斧」:

第一板斧:Application初始化懒加载

很多App在Application.onCreate里做了大量初始化——推送SDK、地图SDK、日志框架...这些完全可以延迟到真正需要时再加载。

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        // 只初始化必须的
        initCrashHandler()
        initMemoryMonitor()
        
        // 其他延迟初始化
        InitManager.delayInit(this)
    }
}

object InitManager {
    private val initTasks = mutableListOf<() -> Unit>()
    
    fun delayInit(context: Context) {
        // 在闪屏页展示期间,利用IdleHandler执行
        Looper.myQueue().addIdleHandler {
            initTasks.forEach { it.invoke() }
            false // 只执行一次
        }
    }
}

第二板斧:减少ContentProvider

每个ContentProvider都会增加启动时间。我见过一个App有8个ContentProvider,光初始化就花了200ms。能合并的尽量合并,能用懒加载的别用ContentProvider。

第三板斧:启动页数据预加载

闪屏页展示的这段时间,别让它闲着。我习惯在闪屏页的onCreate里启动一个异步任务,预加载主页需要的数据。

class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        
        // 预加载主页数据
        preloadHomeData()
        
        // 延迟跳转
        window.decorView.postDelayed({
            startActivity(Intent(this, MainActivity::class.java))
            finish()
        }, 1500) // 至少展示1.5秒,避免闪一下就走
    }
    
    private fun preloadHomeData() {
        lifecycleScope.launch(Dispatchers.IO) {
            // 预加载网络数据、数据库查询等
            HomeDataPreloader.preload()
        }
    }
}

18.5 知识体系总览

下面这张图是我自己整理的闪屏优化知识结构,你可以对照着检查自己的项目还有哪些没做到位:

启动闪屏优化知识体系 闪屏优化 视觉欺骗 系统API 冷启动加速 自定义主题 背景Drawable SplashScreen API 兼容适配 懒加载初始化 减少ContentProvider 数据预加载 目标:用户点击图标 → 看到闪屏 → 进入主页 全程无白屏/黑屏,感知启动时间 < 1.5秒

18.6 避坑指南

做闪屏优化这些年,我踩过不少坑。挑几个典型的说说:

坑一:闪屏展示时间过短

我曾经把闪屏时间压到500ms,结果用户反馈「App闪了一下就没了,感觉像崩溃重启」。后来我改成至少1.5秒,用户满意度反而提升了。记住:闪屏不只是为了掩盖加载,更是品牌展示的机会。

坑二:背景Drawable太大

有次我放了一张高清PNG做闪屏背景,结果低端机上预览窗口绘制花了300ms——白屏时间反而更长了。后来换成纯色+小Logo,问题解决。

坑三:SplashScreen API和自定义主题混用

如果你同时设置了windowSplashScreenBackgroundandroid:windowBackground,系统会优先使用API的配置。但低版本设备不认识API属性,就会回退到自定义主题。测试时一定要覆盖Android 11和Android 12两种场景。

18.7 性能指标参考

最后给出一组我常用的性能指标,你可以对照着优化自己的项目:

指标项 优秀 良好 需优化
白屏/黑屏持续时间 0ms(无感知) < 200ms > 500ms
闪屏总展示时间 1.5s - 2.0s 1.0s - 1.5s < 1.0s 或 > 3.0s
Application.onCreate耗时 < 100ms 100ms - 300ms > 500ms
首帧绘制时间 < 500ms 500ms - 1s > 1.5s

嗯,闪屏优化做到这个程度,基本就到位了。记住一个原则:让用户感觉不到「等待」,而不是真的没有等待。视觉欺骗+性能优化双管齐下,才是最优解。


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