Cold Start(冷启动)是 Android 应用从零状态开始的完整启动周期,此时应用进程在内存中不存在,Activity 也尚未创建。系统会创建新进程、加载类、初始化 Application、创建 Activity 并执行首次渲染。根据 Google(2024年)的数据,中端设备上的冷启动可能需要 1 到 5 秒,每延迟 100 毫秒,用户留存概率就会降低 3%。
要点总结
Cold Start(冷启动)是指 Android 应用从最初始状态启动的场景:操作系统创建新进程(从 Zygote fork)、分配内存、将 DEX 代码加载到 ART、初始化类并创建 Application 实例,然后创建第一个 Activity。在应用启动之前,设备内存中没有任何关于它的数据,除非使用 Background Dexopt 缓存的类映像。
冷启动发生在三种情况下:应用安装后首次启动、设备重启后启动、以及系统因内存不足移除进程后启动。在 2–4 GB RAM 的设备上,系统会激进地移除后台进程,因此 Cold Start 可能在每次闲置数小时后返回应用时发生。在 Android 12+ 中,系统可以保留冻结的进程(freeze / cached),但在主动内存节省(OOM-killer)情况下,进程会被销毁。
根据 Google(Find My Device 报告,2023 年)的数据,65% 的用户会在应用 3 秒内未打开时关闭它。对于社交网络和即时通讯工具(用户每天返回数十次的应用),Cold Start 直接影响留存率。在 Google Play Console 中,Cold Start 指标属于 Android Vitals 部分,并作为 ANR 和性能指标之一显示。超过 "差" Cold Start 阈值(25% 设备上超过 5 秒)的应用会在控制台中收到警告,并可能在搜索结果中降级。
Android 区分三种应用启动类型,每种类型有不同的持续时间、对 UX 的影响和优化方法。了解差异对于选择正确的分析策略是必要的。
| 启动类型 | 进程状态 | Application.onCreate | 典型时间 |
|---|---|---|---|
| Cold | 无进程 | 执行 | 1–5 秒 |
| Warm | 进程存在,Activity 不存在 | 不执行 | 200–600 毫秒 |
| Hot | 进程 + Activity 在内存中 | 不执行 | < 200 毫秒 |
Warm Start 发生在应用进程已存在于后台但 Activity 已被销毁时(例如,用户长时间暂停后返回且系统释放了 Activity 内存)。Hot Start — 当用户最小化应用并立即重新打开时:Activity 暂停,恢复需要最少时间。对用户而言,Cold Start 是最明显的启动类型,优化它能最大程度地改善 UX。
在应用至少启动一次后,Cold Start 可以变为 Warm Start — ART 缓存已编译的类映像(Boot Profile 中的 Image),重复加载 DEX 变得更快速。因此,第一次 Cold Start 后的第二次启动通常快 20–40%。如果应用使用 Baseline Profiles,配置文件会在第一次启动时加载,第二次启动可能更快:发布 Baseline Profiles 的 Google Play 在 Android 12+ 设备上将 Cold Start 加速了 30%。
Cold Start 由严格定义的阶段组成,每个阶段都可以独立测量和优化。了解这些阶段有助于确定应用在哪个阶段丢失时间。Google 区分四个主要阶段:进程创建、Application 初始化、Activity 创建和第一帧。
Android 系统(ActivityManagerService)通过从 Zygote 进程 fork 来创建新进程。Zygote 是预加载了通用 Android 类的进程。Fork 在 30–80 毫秒内执行 — 这是应用无法控制的时间。Fork 后,ActivityThread 启动 — 应用主循环的实例。在此阶段,还通过 ClassLoader 进行 类加载,ART 开始解释第一个字节码。如果应用使用大量静态初始化器,此阶段可能会延长。
ActivityThread 启动后立即调用 Application.onCreate。这里开发者最常犯的错误是同时初始化所有内容:Crashlytics、Firebase、网络客户端、数据库、Dagger 组件、DI 容器。每次这样的初始化都是主线程上阻塞的时间。如果 Application.onCreate 需要 500 毫秒,在这半秒钟内用户会看到白色(或黑色)屏幕。此阶段的最佳持续时间是在中等设备上少于 200 毫秒。
Application 初始化后,创建 Activity(MainActivity 或 Launcher Activity)实例。调用 Activity.onCreate,在此进行 setContentView、片段初始化、ViewModel 配置、订阅 LiveData/Flow。如果 onCreate 在主线程上同步加载数据(SharedPreferences、SQLite、API),此阶段会延长。目标是在中等设备上将 onCreate 控制在 200–400 毫秒内。
onCreate 完成后,开始首次渲染:measure、layout、draw。这个时刻称为 TTFD(Time To First Draw)。如果应用使用启动画面(通过 Android 12+ 的 SplashScreen API 或通过主题),渲染可能更快发生,但用户仍会等待 splash 消失。Cold Start 的理想 TTFD 是少于 1.5 秒。
测量 Cold Start 需要特殊工具,因为普通日志记录(Log.d)只在 Application 创建后才开始工作,而 fork 和类加载的时间仍无法访问。Google 推荐三种方法:ADB 命令、Android Vitals 和自定义性能宏。
最简单且可重复的方法是 adb shell am start -S -W 命令。标志 -S 在启动前强制停止应用(保证 Cold Start)。该命令输出三个指标:ThisTime(Activity 启动时间)、TotalTime(包括进程启动的总时间)和 WaitTime(包括 Activity Manager 所有延迟的时间)。为获得干净的测量结果,进行 5–7 次测量并取中位数 — 单次测量会受到噪声影响(CPU 节流、后台负载)。
# 强制 Cold Start 测量
$ adb shell am start -S -W \
com.example.app/.MainActivity
# 命令输出:
# ThisTime: 1842 ms
# TotalTime: 1842 ms
# WaitTime: 1855 ms
Google Play Console 从所有安装了应用的设备收集匿名指标。在 Android Vitals → Launch time 部分,将按设备型号和 Android 版本显示 Cold Start 的中位数分布。这是查看用户设备而非测试设备上真实指标的唯一方法。如果在 Redmi 9A(2 GB RAM)上 Cold Start 超过 5 秒,而在 Pixel 8 上为 1.2 秒,问题在于内存容量和类数量。Google 还基于第 25 百分位显示 用户感知的延迟(user-perceptible delay)。
Google Jetpack Macrobenchmark(androidx.benchmark 库)允许编写应用启动的仪器化测试。测试安装应用、以冷状态启动并测量到第一帧的时间。Macrobenchmark 自动进行 20 次运行、剔除异常值并显示稳定的百分位数。对于 CI/CD,可以比较基线和当前启动 — 如果时间增加,CI 流水线可能会失败。
Cold Start 优化是一项系统性工作,影响应用的多个层面:代码、资源、构建配置和初始化架构。Google 建议从最昂贵的部分 — Application.onCreate 开始,然后逐步处理较小的问题。
将所有启动时不需要的初始化从 Application.onCreate 移动到首次使用的点。Firebase、Crashlytics、analytics SDK、推送通知、DI 组件 — 所有这些都可以在第一个屏幕渲染后初始化。在 Kotlin 中使用 Lazy(by lazy)或使用显式 initialize(context) 调用的 ContentProvider 初始化。根据 Google(Android Performance,2023 年)的数据,延迟初始化可将使用 5+ SDK 的应用的 Cold Start 减少 40–60%。
Baseline Profiles 是在应用启动时使用的关键类和方法的 AOT 编译。没有 Baseline Profiles,ART 会解释 DEX 代码或通过 JIT 编译,这需要时间。有了配置文件,ART 在安装应用时将指定的方法编译为本机代码(AOT)。Google 声称 Baseline Profiles 在 Android 9+ 上可将 Cold Start 加速 15–40%,在 Android 12+ 的 ART 优化下可达 60%。要创建配置文件,请使用 androidx.benchmark:benchmark-baseline-profile-gradle-plugin 插件。
androidx.startup 库允许组织组件初始化并在一个 ContentProvider 中执行。代替来自不同库的多个 ContentProvider(每个向冷启动添加 1–2 毫秒),App Startup 将它们合并到依赖关系图中并仅在需要时初始化。启动时,只执行标记为 @Initializer 且第一屏幕所需的组件。对于其他组件,设置标志 needEarlyInit = false — 它们在首次渲染后启动。
// App Startup Initializer — 启动后初始化
class AnalyticsInitializer : Initializer<Unit> {
override fun create(context: Context) {
Analytics.init(context)
}
override fun dependencies() = listOf<Class<out Initializer<*>>>()
}
// 在 AndroidManifest.xml 中标记为可选
// <meta-data android:name="AnalyticsInitializer"
// android:value="false" />
DEX 文件的大小直接影响 ART 加载它的时间。使用 R8/ProGuard 进行混淆和删除死代码(MinifyEnabled = true)。在清单中启用 android:extractNativeLibs="false",这样 APK 在安装时不会解压 .so 文件。对于具有 10 个以上 Reference Tracking 类的项目,仅对第一屏幕添加 startup-priority。DEX 中每个多余的方法会添加 0.5–2 毫秒的加载时间,对于具有 50k+ 方法的应用(带有 primary dex 的 multidex)— 可达 300 毫秒。
Google Play Console 中的 Android Vitals(Launch time 部分)从安装了应用的所有设备收集数据,前提是用户同意匿名诊断。指标分为三类:"good"(良好)、"moderate"(中等)、"bad"(差),取决于 Cold Start 时间。
Google 将 "bad" Cold Start 定义为在任何设备上超过 5 秒的时间。但在实践中,对于旗舰设备(Snapdragon 8 Gen),良好时间少于 1.5 秒,对于中端设备 — 少于 2.5 秒,对于低端设备 — 少于 4 秒。Android Vitals 按 设备型号 显示中位数,有助于了解应用在哪些设备上启动缓慢。如果 Cold Start 在 Samsung A 系列或 Xiaomi Redmi 设备上表现差,原因通常是闪存弱和 RAM 小(通过 Baseline Profiles 加速正是在此类设备上效果最大)。
除了在控制台中显示外,Cold Start 指标还会影响 Google Play 搜索中的应用质量评估。"差"启动比例高的应用会在安装页面上获得 "Performance warning" 标签,从而降低转化率。根据 Google(Android Performance Playbook,2024 年)的数据,解决 Cold Start 问题的应用平均提高 5% 的安装转化率并改善 3–7% 的留存率(D1)。
要获得更详细的监控,请使用 Firebase Performance Monitoring。它在会话级别跟踪 Cold Start,按应用版本和 Android 版本拆分。与 Android Vitals 不同,Firebase 显示按阶段划分的所花时间的 跟踪图。例如,可以看到在版本 3.2.0 中 Application.onCreate 花费了 800 毫秒(由于新的推送通知库),而在版本 3.2.1 中 — 200 毫秒(修复后)。
下面提供了两个直接加速 Cold Start 的实用示例:将 SDK 初始化移至启动后和使用 SplashScreen API。
典型错误是在 Application.onCreate 中初始化所有 SDK。下面展示了如何将非关键初始化移到第一帧渲染后启动的协程中。重要提示:Firebase、Crashlytics 和 Crash Reporting SDK 必须在启动时初始化 — 它们不能延迟,因为它们会捕获其他组件初始化时的崩溃。对于其他组件,在第一个 Activity 中使用 lifecycleScope。
// ❌ 差 — 所有初始化在 Application.onCreate 中
class App : Application() {
override fun onCreate() {
super.onCreate()
Firebase.init(this) // 关键
Analytics.init(this) // 可以稍后
Database.init(this) // 可以稍后
ImageLoader.init(this) // 可以稍后
}
}
// ✅ 好 — Firebase 在启动时,其余在 after inflate
class App : Application() {
override fun onCreate() {
super.onCreate()
Firebase.init(this)
}
}
// 在 MainActivity 第一帧后:
lifecycleScope.launchWhenResumed {
initializeNonCriticalSdks()
}
在 Android 12+ 上,使用官方的 SplashScreen API,它在进程启动时立即显示系统启动画面(深色/浅色背景上的应用图标)。这向用户隐藏了初始化时间 — 他看到的是启动画面而不是白色屏幕。对于旧设备,使用 theme-based splash(样式中的 Theme.SplashScreen)。重要提示:启动画面不应超过 300 毫秒 — 如果应用在此时间内未准备好,绘制"永久"骨架(shimmer)并显示加载进度。
// SplashScreen API — Android 12+
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition {
isReady.value == false
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
// Theme-based splash (Android 5-11)
// 在 themes.xml 中:
// <style name="Theme.App.Starting" parent="Theme.SplashScreen">
// <item name="windowSplashScreenBackground">@color/white</item>
// <item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
// </style>
常见问题解答
模拟器使用强大的宿主机并通过硬件加速(HAXM / WHPX)模拟处理器。物理设备,尤其是低端设备(eMMC 内存而非 UFS),I/O 慢得多。建议在中端物理设备上测量 Cold Start 以获得真实数据。
根据 Google 的建议,中端设备的中位数 Cold Start 应少于 2 秒。旗舰设备 — 少于 1.5 秒。低端设备(2 GB RAM)最多可接受 4 秒,但建议优化到 3 秒。超过 5 秒的值被视为关键。
间接影响 — 是的。如果在清单中指定了矢量图标(AdaptiveIcon),则需要在启动时编译为 drawable。如果图标包含复杂的路径(带有数十条曲线的 pathData),编译需要 10–30 毫秒。使用具有优化 pathData 的 VectorDrawable(通过 SVGOMG 或 Android Studio Vector Asset)。
是的,如果 Feature Module(Android App Bundle)是按需(on-demand)加载的,其 Cold Start 从点击功能到第一帧计算。按需模块通过 Play Core Library 加载,其安装会为启动时间增加 500–3000 毫秒。像主模块一样优化功能代码。
超过 64k 方法的应用需要 Multidex。这意味着 ART 必须加载多个 DEX 文件,这会根据 classes.dex 数量将 Cold Start 时间增加 200–800 毫秒。使用 minSdk 21+(支持原生 multidex 的 ART)并通过 --main-dex-list 配置 primary dex,使关键类位于第一个 DEX 文件中。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。