Warm Start:本质、热启动与Android优化

作者: IT Sectr 发布日期: 2026-03-31 阅读时间: 8 分钟

Warm Start 是一种Android应用的启动场景,其中应用的进程已存在于内存中(例如最小化后),但Activity已被系统销毁以节省资源。Application.onCreate已执行,类已加载,但UI需要重新创建。根据 Google, 2024,Warm Start需要200到800毫秒,约占4GB RAM设备上所有启动的40%。

要点

  • Warm Start — 使用现有进程启动应用,但内存中没有Activity
  • 与Cold Start的区别:Application.onCreate不执行,类已加载
  • 时间 Warm Start为200–800毫秒,Cold Start为1–5秒
  • 场景:数小时后返回应用,OOM-killer卸载Activity
  • 优化 侧重于保持Activity状态和缓存数据

什么是Warm Start

Warm Start(热启动)是介于Cold Start和Hot Start之间的状态:应用的进程存在于内存中(有时在Linux后台缓存中),但Activity不活跃并将被重新创建。Android系统在RAM不足时可以从堆栈中卸载Activity,同时保持进程存活。当用户返回应用时,Warm Start开始:创建新的Activity实例,执行生命周期方法onCreate → onStart → onResume,但跳过Application.onCreate和类加载。

Warm Start的原因

Android系统根据进程优先级(importance rank)决定是否卸载Activity。后台的Activity(PROCESS_STATE_IMPORTANT_FOREGROUND或PROCESS_STATE_TOP_SLEEPING级别)可能在应用最小化后5–30分钟内被销毁,具体取决于可用RAM。在3GB RAM设备上,Activity可能在10分钟后被卸载,在8GB设备上——数小时后。重要提示:在Warm Start中,onSaveInstanceState在Activity销毁前被调用,开发者可以保存UI状态。

用户感知

用户看不到Warm和Cold Start之间的区别——他只是点击应用图标并等待。但在Warm Start中可能会出现白屏(blank window),如果应用没有为启动窗口设置自己的主题。Google建议在清单中为启动Activity设置自定义主题(Theme.AppCompat.Light或Theme.Material3.DayNight),以避免Warm Start时白/黑屏闪烁。在Android 12+上,SplashScreen API也会隐藏此效果。

Warm Start vs Cold Start vs Hot Start

了解三种启动类型之间的区别对于选择正确的性能分析和优化策略至关重要。每种类型都有其持续时间、瓶颈和测量工具。

标准Cold StartWarm StartHot Start
进程重新创建存在于内存中存在于内存中
Application.onCreate执行不执行不执行
Activity从头创建从头创建从堆栈恢复
时间1–5秒200–800毫秒< 200毫秒
onCreate Activity完整完整(带恢复)跳过

实际上,Warm Start占所有应用启动的30%到60%,取决于用户习惯和设备RAM容量。保持许多应用打开的用户(多任务处理者)更常遇到Warm Start。对于社交网络和即时通讯应用来说,Warm Start是最常见的场景,因为应用始终在后台运行。对于银行应用,则相反,Cold Start占主导地位(出于安全原因强制清理进程)。

热启动的阶段

Warm Start由三个阶段组成,每个阶段都可以被测量和优化。与Cold Start不同,这里没有fork和类加载阶段,但有状态恢复阶段(restore),这可能很耗时。

阶段1:启动窗口(window background)

系统检查应用是否有启动窗口的主题。如果没有设置主题,则显示白屏(或黑屏,取决于系统)。如果设置了主题,则显示主题中的背景。此阶段持续10–30毫秒,但如果主题与应用的实际UI不匹配,则视觉上可感知。使用带有自定义windowBackground的Theme.Material3.DayNight,其颜色与第一个屏幕的背景匹配——这会产生即时加载的效果。

阶段2:创建Activity(恢复)

系统调用onCreate,传递在Activity销毁前于onSaveInstanceState中保存的Bundle savedInstanceState。如果应用正确保存了状态(字段文本、滚动位置、ViewModel数据),恢复会很快发生。如果没有——Activity从空白页面开始,用户会看到加载器直到数据加载完成。关键点:ViewModel对象只有在进程未被销毁时才能在Warm Start中存活——在Warm Start中ViewModel保留在内存中。

阶段3:第一帧(TTFD)

在onCreate之后,执行onStart → onResume,系统调用第一次渲染。Warm Start的TTFD(首次绘制时间)在中端设备上应小于300毫秒。如果第一个屏幕包含带有复杂RecyclerView的重型View或通过网络加载图像,TTFD可能超过阈值。在第一帧之后使用PlaceholderShimmer实现内容的平滑加载。

如何测量Warm Start

测量Warm Start比Cold Start更复杂,因为您需要模拟«进程存活,Activity已销毁»的状态。带有-S标志的标准ADB命令不合适——它会杀死进程。对于Warm Start使用其他方法。

不带-S的ADB shell am start

首先通过adb shell monkey或点击图标启动应用,然后将其最小化(adb shell input keyevent 3 keyevent HOME)。等待5–10秒让系统卸载Activity,然后运行adb shell am start -W(不带-S)。该命令将返回比Cold Start更短的启动时间。为了可重现性,使用脚本:启动 → 等待 → home → 等待 → 启动。

bash
# 通过ADB模拟Warm Start
$ adb shell am start -W \
    com.example.app/.MainActivity

# 输出(Warm Start):
# ThisTime: 412 ms
# TotalTime: 412 ms
# WaitTime: 423 ms

用于Warm Start的Macrobenchmark

androidx.benchmark.macro库支持测量Warm Start。在测试中设置startupMode = StartupMode.WARM——库将启动应用、最小化、等待(可配置延迟),然后测量重新启动。Macrobenchmark执行10–20次运行并计算百分位数。在CI/CD中您可以设置阈值:如果P50 Warm Start超过600毫秒——测试失败。这允许在每次提交时跟踪回归。

Firebase Performance Monitoring

Firebase根据自上次关闭应用以来的时间自动区分Cold和Warm Start。如果应用在过去30分钟内被打开,Firebase将启动分类为Warm。在Firebase控制台中,您将看到每个启动类型的单独图表,从而可以评估优化的有效性。例如,在ViewModel中实现状态保存后,可以看到Warm Start减少多达30%。

优化Warm Start

Warm Start的优化侧重于两个方向:加速Activity.onCreate和正确恢复状态。由于Application.onCreate和类加载已经完成,主要瓶颈是第一个屏幕的UI代码。

异步恢复状态

如果保存的状态(savedInstanceState)包含需要反序列化的数据(Bitmap、String、JSON),请在后台线程中执行此操作。不要在onCreate中直接从Bundle读取,而是启动一个协程并显示shimmer屏幕。实际上,在中端设备上反序列化Bundle需要20–100毫秒——看似不多,但对于Warm Start来说,这占总时间的10–50%。使用Jetpack库的Saved State Module,它自动在Bundle或数据库中保存和恢复ViewModel状态。

优化setContentView

XML布局展开(layout inflation)是Warm Start中最昂贵的阶段之一。如果第一个屏幕使用带有AppBar、CollapsingToolbar、NestedScrollView和三个RecyclerView的复杂CoordinatorLayout,inflation时间可能达到300毫秒。解决方案:使用ConstraintLayout实现扁平层次结构,对启动时不可见的部分(bottom sheet、dialog)应用ViewStub,通过AsyncLayoutInflater启用重型片段的异步展开。在Jetpack Compose中不需要inflation,但在Warm Start中编译Compose树可能需要类似的时间。

数据缓存

在Warm Start中,应用在上一会话中加载的数据可能已在缓存中:Room数据库、SharedPreferences、ViewModel中的内存缓存。如果您的第一个屏幕显示来自服务器的列表,请在启动时检查缓存并在后台更新数据。使用cache-then-network策略:首先显示缓存数据(即时),然后从服务器更新(异步)。这将Warm Start的感知时间缩短到100–200毫秒。

kotlin
// 带缓存的ViewModel用于Warm Start
class FeedViewModel : ViewModel() {
    private val cache = MutableStateFlow<List<Item>>(emptyList())

    init {
        // 先缓存,后网络
        viewModelScope.launch {
            cache.emit(db.getItems()) // Warm Start:数据已在数据库中
            cache.emit(api.fetchItems()) // 后台更新
        }
    }
}

在Warm Start中保持状态

正确保存状态是区分好的Warm Start和差的Warm Start的关键因素。用户期望返回应用时看到与离开时相同的内容——包括滚动位置、字段中的文本、选中的标签。

onSaveInstanceState

系统在Activity销毁时调用onSaveInstanceState,但在进程可能被杀死之前。Bundle中只保存简单数据(String、Int、Parcelable、Serializable)。对于复杂数据,使用ViewModel中的SavedStateHandle——它会在Warm Start中自动保存和恢复字段。与onSaveInstanceState不同,SavedStateHandle即使进程在Warm Start中存活也能工作(ViewModel不会被销毁)。示例:对于EditText中的文本,使用SavedStateHandle.getLiveData(“text”)——文本将自动保存和恢复。

ViewModel和Warm Start

如果Warm Start中进程未被杀死,ViewModel保留在内存中且onCleared不会被调用。这意味着上一会话中加载的所有数据都立即可用。但如果进程被杀死(设备深度睡眠超过30分钟),ViewModel被销毁并通过SavedStateHandle重新创建。为了ViewModel在Warm Start中正确工作,使用带有在任何场景下都需要恢复的字段的SavedStateHandle。区别:带有@HiltViewModel的ViewModel自动支持SavedStateHandle。

机制进程存活进程被杀死
ViewModel数据在内存中被销毁,重新创建
SavedStateHandle数据在内存中从Bundle恢复
onSaveInstanceState在Activity卸载时调用不调用
Room DB缓存可用缓存可用(磁盘)

保持RecyclerView滚动位置

Warm Start最常见的问题之一——丢失滚动位置。用户滚动到第50个元素,最小化应用,返回——看到列表开头。解决方案:保存layoutManager.onSaveInstanceState(保存第一个可见元素的位置和偏移量)并在onRestoreInstanceState中恢复。也可以将最后可见位置以日期/时间键保存到SharedPreferences中,以便在Warm Start中快速恢复位置。

kotlin
// 保存RecyclerView滚动位置
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putParcelable(
        "rv_state", binding.recyclerView
            .layoutManager?.onSaveInstanceState()
    )
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    savedInstanceState?.getParcelable<Parcelable>("rv_state")
        ?.let { binding.recyclerView.layoutManager?.onRestoreInstanceState(it) }
}

Warm Start代码示例

两个Warm Start优化的实际示例:在ViewModel中使用SavedStateHandle以及启动后异步恢复复杂数据。

带有SavedStateHandle的ViewModel

SavedStateHandle自动将字段保存到Bundle并在Warm Start中恢复它们。用户配置文件字段(String、JSON)将在不向服务器发送额外请求的情况下恢复。如果进程被杀死,SavedStateHandle将从Bundle加载最后保存的状态。

kotlin
class ProfileViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val profile: StateFlow<Profile?>
        get() = savedStateHandle
            .getStateFlow("profile", null)

    fun loadProfile(id: String) {
        viewModelScope.launch {
            savedStateHandle["profile"] =
                api.getProfile(id)
        }
    }
}

// Warm Start:profile不为null,UI无需加载器
// 加载后:profile在SavedStateHandle中更新

用于重型屏幕的AsyncLayoutInflater

如果第一个屏幕包含复杂的布局(地图、渐变、多个列表),请使用AsyncLayoutInflater在后台展开重型元素。在布局展开期间,显示带有shimmer效果的占位符。这在使用Warm Start时尤其重要,每一毫秒都很宝贵。AsyncLayoutInflater在后台线程上工作,并通过回调将准备好的View传递到主线程。

kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 用于即时渲染的占位布局
        setContentView(R.layout.placeholder_shimmer)

        // 异步加载重型布局
        AsyncLayoutInflater(this).inflate(
            R.layout.activity_main_complex,
            findViewById(R.id.container)
        ) { view, resId, parent ->
            parent?.removeAllViews()
            parent?.addView(view)
        }
    }
}

常见问题

Warm Start能变成Cold Start吗?

是的,如果在Warm Start时刻系统决定杀死应用进程(例如释放内存给其他应用),启动将从头变成Cold Start。这在2–3GB RAM设备上多个应用同时运行时发生。实际上,在中端设备上最小化后,Warm Start仅在10–20分钟内得到保证。

ViewModel在Warm Start中会保留吗?

是的,如果进程未被杀死,ViewModel保留在内存中且onCleared不会被调用。这是Warm Start的关键优势:所有加载的数据、网络请求、ViewModel中的缓存——立即可用。如果进程被杀死,ViewModel通过ViewModelProvider.Factory或@HiltViewModel重新创建,SavedStateHandle恢复保存的字段。

为什么Warm Start可能比Cold Start慢?

理论上,Warm Start总是比Cold Start快,但实际上存在差异最小的情况:如果Application.onCreate很轻(50毫秒)而Activity.onCreate很重(800毫秒),那么Warm Start(800毫秒)几乎等于Cold Start(850毫秒)。在这种情况下,需要优化的不是Application,而是Activity.onCreate——它成为Warm Start的瓶颈。

SplashScreen API如何影响Warm Start?

Android 12+上的SplashScreen API在启动时立即显示系统启动画面(彩色背景上的图标)——适用于Cold和Warm Start。对于Warm Start,启动画面仅显示100–300毫秒,然后被应用UI取代。SplashScreen本身不会加速启动,但会掩盖Activity创建的时间,改善感知。

如果Cold Start已经很快,是否需要优化Warm Start?

是的,因为Warm Start发生的频率是Cold Start的2–3倍。如果Cold Start需要1.2秒而Warm Start需要600毫秒,那么40%的启动(Warm)仍然需要0.6秒,这是可感知的。将Warm Start优化到200–300毫秒能带给用户即时返回的感觉。在6+ GB RAM的设备上,Warm Start可能占所有启动的80%,其优化成为优先事项。

总结

  • Warm Start — 使用现有进程启动,内存中没有Activity,时间200–800毫秒
  • 与Cold Start的主要区别:Application.onCreate不执行,类已加载
  • Warm Start的三个阶段:启动窗口 → 创建Activity → 第一帧
  • 通过不带-S标志的ADB或带有StartupMode.WARM的Macrobenchmark测量
  • 优化:SavedStateHandle、AsyncLayoutInflater、cache-then-network、ConstraintLayout
  • ViewModel在Warm Start中保留(进程存活)——数据立即可用
  • Warm Start占所有应用启动的40–80%

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读