冷启动 = 进程不存在 → 启动进程 → 加载 Application → 创建首个 Activity → 首帧渲染完成。
三个关键节点:
am_proc_start 系统开始拉进程Application.onCreate 结束onWindowFocusChanged(true) 触发我们要压的是 T2 - T0。中间的每一步都可能是黑锅。
只信一种数据:adb shell am start -W。
adb shell am start -W -n com.demo.app/.MainActivity输出里关注:
ThisTime:Activity 启动耗时TotalTime:包含 Application 的总耗时WaitTime:AMS 层的等待时间再叠加代码内打点:
class App : Application() {
override fun onCreate() {
val t0 = SystemClock.elapsedRealtime()
super.onCreate()
// ... 初始化
val cost = SystemClock.elapsedRealtime() - t0
Log.i("Startup", "App.onCreate cost=${cost}ms")
}
}Activity 侧用 Choreographer.getInstance().postFrameCallback 或者 reportFullyDrawn() 抓首帧。
这轮优化里 Application 从 1.4s 降到 380ms,靠的是三件事。
把 SDK 按"必须同步"/"可延后"/"可后台"三类拆开:
override fun onCreate() {
super.onCreate()
// 必须同步
initCrashReport()
initLogger()
// 主线程 idle 后跑
Looper.myQueue().addIdleHandler {
initAnalytics()
initImageLoader()
false
}
// 后台线程
Thread {
initPushSDK()
initABTest()
}.start()
}关键点:IdleHandler 只跑主线程能容忍的活,别把耗时 SDK 塞进去,否则只是把卡顿从启动挪到首屏交互。
用 App Startup 库或者手写 ContentProvider 触发器都行,核心是把非首屏依赖的初始化推到 Activity onResume 后:
class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
if (!inited) {
window.decorView.post {
initSecondaryModules()
inited = true
}
}
}
}第三方 SDK 常用 悄悄在启动阶段跑代码。检查方式:
adb shell dumpsys package com.demo.app | grep -A2 "Provider"看到不必要的 Provider,用 tools:node="remove" 关掉,然后手动懒加载:
<provider
android:name="com.some.sdk.InitProvider"
android:authorities="${applicationId}.some.init"
tools:node="remove" />这一步单独省了 220ms,很多 SDK 的 Provider 其实完全不需要跟启动绑定。
Application 优化到位后,Activity 那 900ms 就成了瓶颈。
用 Layout Inspector 或 hierarchyviewer 看首屏层级。这次踩到两个坑:
ConstraintLayout 里嵌了 5 层 LinearLayout,重构成扁平 Constraint 后省 90msinclude 了一个巨大的通用头部布局,里面 90% 视图 visibility=gone,改成 ViewStub 懒加载省 60ms首屏依赖的接口不要在 onCreate 里同步等。用 Flow + Loading 骨架屏:
class HomeViewModel : ViewModel() {
val state = MutableStateFlow<UiState>(UiState.Loading)
init {
viewModelScope.launch {
state.value = try {
UiState.Success(repository.load())
} catch (e: Exception) {
UiState.Error(e)
}
}
}
}Activity 侧先渲染骨架,数据回来再刷。用户感知的"启动完成"不是数据完成,是首帧可见。
老代码里用 SplashActivity → MainActivity 跳转,光 Activity 创建就多花 150ms。改成 Theme 层的 windowBackground:
<style name="AppTheme.Launcher" parent="AppTheme">
<item name="android:windowBackground">@drawable/launch_bg</item>
</style>MainActivity 用 Launcher 主题,super.onCreate 后立刻切回 AppTheme。省一个 Activity,稳赚。
优化完不做防回归,三个月后必然被打回原形。加两道闸:
用 am start -W 跑 20 次,取 P90,超过基线 15% 直接失败。这套脚本可以放到 GitHub Actions 或 Jenkins。
上报 Application.attachBaseContext 到 onWindowFocusChanged 的耗时,按机型分位统计。低端机 P90 才是真实体验,别只看平均值。
object StartupTracker {
private var t0 = 0L
fun mark() { t0 = SystemClock.elapsedRealtime() }
fun report(tag: String) {
val cost = SystemClock.elapsedRealtime() - t0
Analytics.log("startup", mapOf("tag" to tag, "cost" to cost))
}
}有几种做法要谨慎:
真正立竿见影的还是那三件:减少 Application 里的同步工作、首屏布局扁平化、用主题闪屏代替 SplashActivity。
冷启动优化没什么魔法:测量 → 拆解 → 分级 → 防回归。别一上来就找玄学方案,先把 am start -W 跑起来,把 Application 里的初始化列个表,砍掉一半你就赢了一半。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。