启动优化
问题
Android 应用的启动过程是怎样的?如何优化冷启动时间?
答案
三种启动方式
| 类型 | 说明 | 耗时 |
|---|---|---|
| 冷启动 | 进程不存在,从 fork 进程开始 | 最长 |
| 温启动 | 进程存在但 Activity 被销毁 | 中等 |
| 热启动 | Activity 在内存中,直接 resume | 最短 |
冷启动全流程
优化策略
1. Application.onCreate 优化
@HiltAndroidApp
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// ❌ 同步初始化所有 SDK
// initCrashlytics()
// initAnalytics()
// initPush()
// ✅ 按优先级分级初始化
// 必须同步:Crashlytics
initCrashlytics()
// 延迟到空闲时:Analytics、Push
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post {
// IdleHandler 在主线程空闲时执行
Looper.myQueue().addIdleHandler {
initAnalytics()
initPush()
false // 执行一次后移除
}
}
}
}
2. 使用 App Startup 库
// 声明式初始化,替代 ContentProvider 自动初始化
class AnalyticsInitializer : Initializer<Analytics> {
override fun create(context: Context): Analytics {
return Analytics.init(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(CrashInitializer::class.java) // 依赖关系
}
}
3. 布局优化
// 使用 ViewStub 延迟加载非首屏内容
// 首屏只加载核心 UI,其他用 ViewStub
// Compose 场景下:减少首帧的 Composable 数量
@Composable
fun HomeScreen() {
// 首帧只显示骨架屏
var loaded by remember { mutableStateOf(false) }
if (loaded) {
FullContent()
} else {
Skeleton()
}
LaunchedEffect(Unit) {
loaded = true
}
}
4. Baseline Profiles(基线配置文件)
// 预编译热路径代码,减少 JIT 开销
@ExperimentalBaselineProfilesApi
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generateBaselineProfile() = rule.collect("com.example.app") {
pressHome()
startActivityAndWait()
// 模拟启动关键路径
}
}
测量启动时间
# adb 测量
adb shell am start-activity -W com.example/.MainActivity
# TotalTime: 冷启动总耗时
# Macrobenchmark 自动化测量
@LargeTest
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startup() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
}
}
常见面试问题
Q1: 如何将冷启动优化到 500ms 以内?
答案:
- 减少 Application.onCreate 耗时:异步初始化、按需加载
- 减少首帧布局复杂度:扁平化布局、ViewStub
- 使用 Baseline Profiles:预编译热路径
- 避免主线程 IO:使用 StrictMode 检测
- 使用 SplashScreen API:利用系统窗口快速展示品牌页
Q2: App Startup 库的原理?
答案:
传统方式中,许多库通过自己的 ContentProvider 自动初始化,每个 CP 都有开销。App Startup 合并所有初始化到一个 InitializationProvider 中,按依赖关系图有序执行,减少 ContentProvider 数量。