引言:为什么你的优化"无感"?
很多同学一提到启动优化,第一反应就是搞个 TaskDispatcher,把 Application.onCreate 里的初始化任务全丢进异步线程。
结果呢? 线下测试冷启动确实快了 200ms,但线上用户依然反馈:"怎么我从后台切回来还是卡一下?""点个推送进 App 还是得等半天?"
这就是典型的"冷启动思维定式 "。在实际场景中,用户使用热启动的频率远高于冷启动。如果你的优化列表里没有 Activity 栈复用、数据预加载回填、View 重绘优化,那你的启动优化只能算做了半套。
一、 核心原理:冷热启动的"底层链路"差异
要搞懂优化,得先看源码。在 Android 系统中,冷热启动的分水岭在于 进程的创建。
1. 冷启动(Cold Start):从 0 到 1
当系统调用 startProcessLocked 时,它会通过 Socket 向 Zygote 发信号,fork 出新进程。
- 关键链路:
ZygoteInit->RuntimeInit->ActivityThread.main()->bindApplication->setWindowManager->performLaunchActivity。 - 耗时大头: 进程创建、ClassLoader 加载类、Application 所有的
ContentProvider初始化及onCreate生命周期。
2. 热启动(Hot Start):老友重逢
进程还在,只是 Activity 被回收了,或者 Task 栈被推到了后台。
- 关键链路:
AMS检查到目标 Activity 在栈中,直接realStartActivityLocked->transaction.setLifecycleState(ON_RESUME)。 - 核心逻辑: 绕过了进程创建和
Application的初始化。它只涉及onRestart()->onStart()->onResume()。
冷启动优化的核心是 "减负" (拆解初始化链);而热启动优化的核心是 "复用"与"状态回填" 。
二、 避坑指南:为什么你的热启动"像"冷启动?
很多 App 即使是热启动也慢得离谱,通常是掉进了这几个"坑"里:
- 重启式跳转: 逻辑写得太糙,不管冷热启动,在
SplashActivity里通通finish()掉然后再startActivity主页,白白浪费了 Activity 栈。 - 过度重绘: 从后台切回时,触发了大量的
onGlobalLayoutListener或者不必要的invalidate,导致主线程掉帧。 - 数据刷新的"暴力美学": 在
onResume里无差别地请求所有接口,导致 CPU 瞬间满载。
三、 实战代码:热启动优化黑科技
1. 善用 ActivityLifecycleCallbacks 监控"真·热启动"
我们不能在每个 Activity 里写逻辑,得在全局层面监控。
Kotlin
/**
* 优雅地监控 App 前后台切换
*/
object AppStatusTracker : Application.ActivityLifecycleCallbacks {
private var activeCount = 0
var isBackground = false
private set
override fun onActivityStarted(activity: Activity) {
if (activeCount == 0) {
// 这就是热启动回来的第一瞬间
isBackground = false
handleHotStartPath(activity)
}
activeCount++
}
override fun onActivityStopped(activity: Activity) {
activeCount--
if (activeCount == 0) {
isBackground = true
}
}
private fun handleHotStartPath(activity: Activity) {
// 针对性优化:比如清除内存缓存中过期的临时标志位
Log.d("Performance", "热启动自: ${activity.javaClass.simpleName}")
}
// 其他回调略...
}
2. IdleHandler:让 CPU 喘口气
无论是冷是热,很多非首屏必要的逻辑(如:检查更新、埋点上报)应该放在系统空闲时执行。
Kotlin
// 利用 MessageQueue 的空闲时间执行低优先级任务
Looper.myQueue().addIdleHandler {
// 这里执行不影响首屏渲染的操作
ThirdPartySDK.initLazy()
false // 返回 false 表示只执行一次
}
3. 热启动下的"数据回填"策略
热启动时,UI 还在内存里,不要重复 inflate。利用 ViewModel 保持数据状态,只在数据真正过期时才通过 Flow 或 LiveData 触发刷新。
Kotlin
class HomeViewModel : ViewModel() {
private val _data = MutableStateFlow<List<Item>>(emptyList())
val data = _data.asStateFlow()
fun fetchDataIfNeeded() {
if (_data.value.isEmpty() || isDataExpired()) {
viewModelScope.launch {
// 请求网络
}
}
}
private fun isDataExpired(): Boolean {
// 自定义逻辑:比如超过 5 分钟没刷新才视为过期
return System.currentTimeMillis() - lastUpdateTime > 300_000
}
}
四、 深度进阶:AMS 视角下的 Intent Flag 骚操作
热启动慢,很多时候是因为 Task 栈管理不当。如果你在点击推送进入 App 时用了 FLAG_ACTIVITY_NEW_TASK 但没处理好单例模式,系统可能会销毁旧栈重新创建。
- 套路: 尽量使用
FLAG_ACTIVITY_CLEAR_TOP和SINGLE_TOP的组合,让目标 Activity 在不销毁的情况下通过onNewIntent响应。 - 性能意识: 每次 Activity 的销毁和创建都会涉及
WMS(WindowManagerService) 的窗口移除和添加,这是极其耗费 CPU 和 GPU 的操作。
五、 复盘总结:启动优化的"三板斧"
优化不是一蹴而就的,需要一套组合拳:
- 冷启动看 Application: 任务编排化、Provider 延迟化、类预加载。
- 热启动看 Activity: 栈复用、数据懒加载、布局层级优化。
- 全场景看主线程: 杜绝
onMeasure/onLayout里的耗时逻辑,监控Choreographer掉帧情况。
写在最后:
性能优化是一场持久战。不要为了优化而优化,过度设计往往是 Bug 的温床。
**掘友们,你们在做启动优化时,遇到过最难搞的"坑"是什么?欢迎在评论区留言,我们一起在评论区"切磋"一下!