3、启动优化(下):启动耗时测量与黑白屏优化

上一章我们聊了启动优化的理论基础,说白了就是「怎么让 App 更快地出现在用户面前」。但光有理论不行,你得先知道问题在哪,才能对症下药。这一章,我重点讲讲启动耗时的测量手段,以及那个让无数开发者头疼的黑白屏问题。

启动耗时测量:别靠感觉,靠数据

我见过不少团队,优化启动全凭「感觉」——「嗯,这次好像快了点」。这其实很不靠谱。你想想看,没有量化指标,你怎么知道优化有没有效果?

1. ADB 命令:最粗暴也最直接

ADB 命令是测量启动耗时最基础的手段。我个人习惯用它来做快速验证,尤其是在开发阶段。

# 冷启动
adb shell am start -W -n com.example.app/.MainActivity

# 输出示例
ThisTime: 350
TotalTime: 350
WaitTime: 360

这三个时间分别代表什么?

指标 含义
ThisTime 最后一个 Activity 启动耗时
TotalTime 所有 Activity 启动耗时总和
WaitTime AMS 调度总耗时(包含系统开销)
我的习惯:每次优化前后,我都会用 ADB 命令跑 5 次取平均值。单次测量有波动,平均值才靠谱。

2. 手动埋点:精确到每一行代码

ADB 命令只能看到整体耗时,但具体慢在哪?这就需要手动埋点了。我在项目中遇到过这样的情况:整体启动看起来很快,但某个第三方库初始化偷偷吃了 200ms。

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        val startTime = System.currentTimeMillis()
        
        // 初始化 SDK A
        initSDKA()
        Log.d("Startup", "SDK A init: ${System.currentTimeMillis() - startTime}ms")
        
        // 初始化 SDK B
        initSDKB()
        Log.d("Startup", "SDK B init: ${System.currentTimeMillis() - startTime}ms")
    }
}

嗯,这里要注意:埋点代码本身也有开销。我建议只在调试阶段开启,线上版本通过 BuildConfig 控制。

3. FrameMetrics:系统级的帧率监控

如果你觉得手动埋点太「侵入式」,那 FrameMetrics 是个好选择。它是 Android 7.0 引入的 API,可以监听每一帧的渲染耗时。

window.addOnFrameMetricsAvailableListener({ _, frameMetrics, _ ->

    val frameDuration = frameMetrics.getMetric(
        FrameMetrics.TOTAL_DURATION
    ) / 1_000_000 // 纳秒转毫秒

    if (frameDuration > 16) {
        Log.w("FrameMetrics", "卡顿帧: ${frameDuration}ms")
    }

}, Handler(Looper.getMainLooper()))
避坑指南:我曾经在线上版本直接开启了 FrameMetrics 监听,结果发现它本身也会带来性能损耗。建议只在 Debug 包或灰度测试中使用。

启动黑白屏优化:别让用户干等

你有没有遇到过这种情况:点击 App 图标后,屏幕先白一下(或黑一下),然后才显示界面?这就是启动黑白屏。说白了,它是系统在加载 Application 和 Activity 时的「过渡状态」。

为什么会有黑白屏?

系统在启动 App 时,会先创建一个空白窗口。如果 App 主题是 Light,就是白屏;如果是 Dark,就是黑屏。这个窗口会一直显示,直到 Activity 的 onCreate 完成布局绘制。

解决方案:用「伪启动页」替代

我建议的做法是:把启动黑白屏变成你的品牌展示页。这样用户不仅不会觉得慢,反而觉得「嗯,这个 App 启动有仪式感」。

具体怎么做?三步走:

  1. 设置透明主题:在 styles.xml 中定义一个透明主题
  2. 设置启动页主题:在 AndroidManifest 中给 MainActivity 设置该主题
  3. 恢复默认主题:在 Activity 的 onCreate 中恢复默认主题
<!-- styles.xml -->
<style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@drawable/splash_background</item>
    <item name="android:windowFullscreen">true</item>
</style>

<!-- AndroidManifest.xml -->
<activity
    android:name=".MainActivity"
    android:theme="@style/SplashTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // 恢复默认主题
        setTheme(R.style.AppTheme)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
核心思路:用一张品牌图片作为 windowBackground,替代默认的白屏或黑屏。用户看到的是你的 Logo,而不是刺眼的空白。

启动器(Startup)库的使用:告别手动初始化

说到启动优化,就不得不提 AndroidX Startup 库。它解决了一个很实际的问题:多个库的初始化顺序和时机。

我刚开始做优化时,所有初始化都堆在 Application 的 onCreate 里。结果就是:启动慢,而且各个库之间还有依赖关系,一不小心就出 bug。

Startup 库的核心概念

Startup 库提供了一个 Initializer 接口,你只需要实现它,然后声明依赖关系,库会自动帮你处理初始化顺序。

class SDKInitializer : Initializer<SDK> {
    override fun create(context: Context): SDK {
        return SDK.init(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // 依赖另一个 Initializer
        return listOf(AnotherSDKInitializer::class.java)
    }
}

然后在 AndroidManifest 中声明:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.example.SDKInitializer"
        android:value="androidx.startup" />
</provider>

懒加载:不是所有库都需要在启动时初始化

你想想看,有些 SDK 比如推送、统计,真的需要在 Application 创建时就初始化吗?其实可以延迟到真正使用时再初始化。

Startup 库支持懒加载:

// 手动触发初始化
AppInitializer.getInstance(context)
    .initializeComponent(SDKInitializer::class.java)
我的经验:把非核心的 SDK 都改成懒加载。比如分享 SDK、支付 SDK,这些用户不一定马上用到。启动时只初始化 Crash 收集、网络库等核心组件。

总结一下

这一章我们聊了三件事:

  • 启动耗时测量:ADB 命令看整体,手动埋点看细节,FrameMetrics 看帧率
  • 黑白屏优化:用品牌图片替代空白窗口,让等待变得有质感
  • Startup 库:自动管理初始化顺序,支持懒加载

下一章,我们会深入布局优化。说白了,启动优化只是第一步,界面渲染才是用户真正「看到」的性能。到时候我会分享一些我在大厂项目中用到的布局优化技巧,保证让你大开眼界。

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