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 调度总耗时(包含系统开销) |
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()))
启动黑白屏优化:别让用户干等
你有没有遇到过这种情况:点击 App 图标后,屏幕先白一下(或黑一下),然后才显示界面?这就是启动黑白屏。说白了,它是系统在加载 Application 和 Activity 时的「过渡状态」。
为什么会有黑白屏?
系统在启动 App 时,会先创建一个空白窗口。如果 App 主题是 Light,就是白屏;如果是 Dark,就是黑屏。这个窗口会一直显示,直到 Activity 的 onCreate 完成布局绘制。
解决方案:用「伪启动页」替代
我建议的做法是:把启动黑白屏变成你的品牌展示页。这样用户不仅不会觉得慢,反而觉得「嗯,这个 App 启动有仪式感」。
具体怎么做?三步走:
- 设置透明主题:在 styles.xml 中定义一个透明主题
- 设置启动页主题:在 AndroidManifest 中给 MainActivity 设置该主题
- 恢复默认主题:在 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)
}
}
启动器(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)
总结一下
这一章我们聊了三件事:
- 启动耗时测量:ADB 命令看整体,手动埋点看细节,FrameMetrics 看帧率
- 黑白屏优化:用品牌图片替代空白窗口,让等待变得有质感
- Startup 库:自动管理初始化顺序,支持懒加载
下一章,我们会深入布局优化。说白了,启动优化只是第一步,界面渲染才是用户真正「看到」的性能。到时候我会分享一些我在大厂项目中用到的布局优化技巧,保证让你大开眼界。
公众号:蓝海资料掘金营,微信deep3321