Android App 生命周期与 Process Death 详解
一、概述
Android 应用的生命周期管理是移动开发的核心课题之一。与桌面应用不同,Android 系统在资源紧张时会主动回收后台进程,导致用户再次进入时应用从头重建。如何在这种极端场景下保持良好的用户体验,是衡量 Android 工程师水平的重要标准。
核心挑战
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
二、Android 进程生命周期
2.1 进程重要性等级
Android 系统将进程分为五个优先级,内存不足时按优先级从低到高回收:
┌─────────────────────────────────────────────────────────────────────┐│ Android 进程优先级(由高到低) ││ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 1. 前台进程(Foreground Process) │ ││ │ • 有 Activity 处于 resumed 状态 │ ││ │ • 有 Service 绑定到前台 Activity │ ││ │ • 有前台 Service(startForeground) │ ││ │ • BroadcastReceiver 正在 onReceive() │ ││ │ ✓ 极少被杀,只在内存极度紧张时 │ ││ └──────────────────────────────────────────────────────────────┘ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 2. 可见进程(Visible Process) │ ││ │ • Activity 处于 onPause 但仍可见(被 Dialog 覆盖) │ ││ │ • Service 绑定到可见 Activity │ ││ │ ✓ 通常不被杀,除非为维持前台进程运行 │ ││ └──────────────────────────────────────────────────────────────┘ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 3. 服务进程(Service Process) │ ││ │ • 有 startService() 启动的 Service 在运行 │ ││ │ • 典型场景:音乐播放、文件下载 │ ││ │ △ 运行超过 30 分钟后优先级降低 │ ││ └──────────────────────────────────────────────────────────────┘ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 4. 缓存进程(Cached Process) │ ││ │ • 所有 Activity 都处于 stopped/destroyed │ ││ │ • 用户已导航离开,应用进入后台 │ ││ │ ✗ 系统随时可能回收,是 Process Death 的主要来源 │ ││ └──────────────────────────────────────────────────────────────┘ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 5. 空进程(Empty Process) │ ││ │ • 没有任何活跃组件 │ ││ │ • 仅作为缓存保留(加速冷启动) │ ││ │ ✗ 最先被回收 │ ││ └──────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────┘
2.2 ProcessLifecycleOwner — App 级生命周期
ProcessLifecycleOwner 是 Jetpack Lifecycle 提供的应用级生命周期观察者,能感知整个 App 的前后台切换,而非单个 Activity 的状态。
┌─────────────────────────────────────────────────────────────────┐│ ProcessLifecycleOwner 状态转换 ││ ││ App 冷启动 ││ ↓ ││ ON_CREATE ──→ ON_START ──→ ON_RESUME ← App 进入前台 ││ │ ││ 用户按 Home 键 ││ ↓ ││ ON_PAUSE ││ ↓ (约 700ms 延迟,确认真正后台) ││ ON_STOP ← App 进入后台 ││ │ ││ 用户返回 App ││ ↓ ││ ON_START ──→ ON_RESUME ││ ││ 注意:ProcessLifecycleOwner 永远不会收到 ON_DESTROY ││ (进程死亡时直接消失,没有回调机会) │└─────────────────────────────────────────────────────────────────┘
使用示例:
// Application 级别监听前后台切换classMyApp : Application(), DefaultLifecycleObserver {overridefunonCreate() {super.onCreate() ProcessLifecycleOwner.get().lifecycle.addObserver(this) }overridefunonStart(owner: LifecycleOwner) {// App 从后台回到前台 Log.d("AppLifecycle", "App 进入前台") AppStateManager.isInForeground = true }overridefunonStop(owner: LifecycleOwner) {// App 进入后台 Log.d("AppLifecycle", "App 进入后台") AppStateManager.isInForeground = false }}
三、Activity 与 Fragment 完整生命周期
3.1 Activity 生命周期详解
┌─────────────────────────────────────────────────────────────────────┐│ Activity 完整生命周期 ││ ││ [首次创建] [从后台返回] [配置变更/系统回收后返回] ││ ↓ ↓ ↓ ││ onCreate() onRestart() onCreate() ││ ↓ 初始化 UI/数据 ↓ ↓ Bundle 中有数据 ││ onStart() onStart() onStart() ││ ↓ 可见不可交互 ↓ ↓ ││ onResume() onResume() onResume() ││ ↓ 前台,可交互 ↓ ││ ┌──────────────────────────────────┐ ││ │ 运行中(Resumed) │ ││ └──────────────────────────────────┘ ││ ↓ 部分遮挡/失去焦点 ││ onPause() ← 应在此保存轻量级持久数据(如草稿) ││ ↓ 完全不可见(用户按 Home/切换 App) ││ onStop() ← 停止耗资源的操作(如相机、动画) ││ ↓ ││ onSaveInstanceState() ← 系统可能在此之后杀死进程 ││ ↓ ↑ ││ [进程可能被杀死] [用户返回,恢复] ││ ↓ ││ onDestroy() ← finish() 或配置变更导致的销毁 │└─────────────────────────────────────────────────────────────────────┘
3.2 关键回调的职责边界
|
|
|
|
|---|---|---|
onCreate() |
|
|
onStart() |
|
|
onResume() |
|
|
onPause() |
|
|
onStop() |
|
|
onDestroy() |
|
|
onSaveInstanceState() |
|
|
关键陷阱:
onDestroy()在进程被系统杀死时不会被调用,切勿依赖它保存数据。
3.3 Fragment 生命周期与 Activity 的关系
┌──────────────────────────────────────────────────────────────────────┐│ Fragment 与 Activity 生命周期对应关系 ││ ││ Activity Fragment Fragment View ││ ────────── ──────────────── ────────────── ││ onCreate() ──→ onAttach() ││ onCreate() ││ onCreateView() ──→ View 创建 ││ onViewCreated() ──→ 初始化 View ││ onViewStateRestored() ││ onStart() ──→ onStart() ││ onResume() ──→ onResume() ││ ││ onPause() ──→ onPause() ││ onStop() ──→ onStop() ││ onSaveInstanceState() ││ onDestroyView() ──→ View 销毁 ││ onDestroy() ──→ onDestroy() ││ onDetach() │└──────────────────────────────────────────────────────────────────────┘
Fragment ViewLifecycleOwner 的重要性:
// ✗ 错误:使用 this(Fragment 生命周期)观察 LiveData// Fragment 重建时 View 已销毁,但 Fragment 实例仍存在,可能导致 null 崩溃viewModel.data.observe(this) { data -> binding.textView.text = data// binding 可能已经是旧的 View!}// ✓ 正确:使用 viewLifecycleOwner(View 生命周期)viewModel.data.observe(viewLifecycleOwner) { data -> binding.textView.text = data}
四、Process Death 深度解析
4.1 什么是 Process Death
Process Death(进程死亡)是指 Android 系统为回收内存,主动终止后台应用进程的行为。
┌─────────────────────────────────────────────────────────────────────┐│ Process Death 触发场景 ││ ││ 用户操作 App A ──→ 按 Home 键 ──→ App A 进入后台 ││ │ ││ [系统内存不足 / LRU 淘汰] ││ │ ││ App A 进程被 Kill -9 ││ (所有内存状态全部丢失) ││ │ ││ 用户重新点击 App A 图标 ││ ↓ ││ ┌───────────────────────────────────┐ ││ │ 系统需要还原用户之前的导航状态! │ ││ │ │ ││ │ Back Stack 结构已由系统持久化: │ ││ │ [Activity C] ← [Activity B] ← [MainActivity] ││ │ │ ││ │ 按原有栈结构重新创建 Activity C │ ││ │ 并将 savedInstanceState Bundle │ ││ │ 传递给 onCreate() │ ││ └───────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────┘
4.2 Process Death 与 正常销毁的本质区别
┌──────────────────────┬──────────────────────────┬──────────────────────┐│ 对比项 │ 正常销毁(finish/旋转屏) │ Process Death │├──────────────────────┼──────────────────────────┼──────────────────────┤│ onStop 调用 │ ✓ 调用 │ ✓ 调用 ││ onSaveInstanceState │ ✓ 调用(旋转/被系统回收) │ ✓ 调用 ││ onDestroy 调用 │ ✓ 调用 │ ✗ 不调用 ││ ViewModel 保留 │ 旋转✓ / finish ✗ │ ✗ 全部丢失 ││ 静态变量 │ 可能保留 │ ✗ 全部丢失 ││ Application 对象 │ 保留 │ ✗ 重新创建 ││ 单例对象 │ 保留 │ ✗ 全部重新创建 ││ Bundle 数据 │ onSaveInstanceState 中保存 │ ✓ 系统持久化保留 │└──────────────────────┴──────────────────────────┴──────────────────────┘
4.3 在开发环境中模拟 Process Death
# 方法一:通过 adb 命令(最准确)# 1. 先将 App 切换到后台(按 Home 键)# 2. 执行命令杀死进程(不会触发 onDestroy)adb shell am kill <package_name># 例如:adb shell am kill com.example.myapp# 方法二:Android Studio 的 "Terminate Application" 按钮# Run 面板 → 左侧红色停止按钮旁的下拉 → "Terminate Application"# 注意:直接点击停止会调用 onDestroy,不是真正的 Process Death# 方法三:Developer Options(推荐日常测试)# 系统设置 → 开发者选项 → "Don't keep activities"# 勾选后每次 App 进入后台都会销毁 Activity(但进程不一定死)
五、onSaveInstanceState 机制
5.1 工作原理
┌─────────────────────────────────────────────────────────────────────┐│ onSaveInstanceState 调用时机 ││ ││ Android 6.0 之前:onPause() 之前调用 ││ Android 6.0 之后:onStop() 之后调用(时间窗口更大) ││ Android 9.0 之后:onStop() 之后调用(确保 Fragment 事务可提交) ││ ││ 触发条件: ││ ✓ 系统可能回收 Activity(按 Home 键进入后台) ││ ✓ 配置变更(旋转屏幕、字体大小改变、语言切换) ││ ✓ 多窗口模式切换 ││ ││ 不触发条件: ││ ✗ 用户主动 finish()(用户明确选择离开,不需要恢复) ││ ✗ 用户按返回键(同上) │└─────────────────────────────────────────────────────────────────────┘
5.2 系统自动保存的 View 状态
Android View 系统内置了状态保存机制,前提是 View 必须有 ID:
// EditText、RecyclerView、ScrollView、CheckBox 等控件会自动保存状态// 自动保存示例:// <EditText android:id="@+id/et_input" .../> → 内容自动保存// 没有 ID 的 View 不会自动保存!// <EditText .../> → 内容丢失// 自定义 View 实现状态保存classCustomView@JvmOverloadsconstructor( context: Context, attrs: AttributeSet? = null) : View(context, attrs) {privatevar selectedIndex = 0overridefunonSaveInstanceState(): Parcelable {val superState = super.onSaveInstanceState()return Bundle().apply { putParcelable("super_state", superState) putInt("selected_index", selectedIndex) } }overridefunonRestoreInstanceState(state: Parcelable?) {if (state is Bundle) { selectedIndex = state.getInt("selected_index")super.onRestoreInstanceState(state.getParcelable("super_state")) } else {super.onRestoreInstanceState(state) } }}
5.3 手动保存 UI 状态
classCheckoutActivity : AppCompatActivity() {privatevar cartItems: List<CartItem> = emptyList()privatevar currentStep = 0overridefunonSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)// ✓ 适合保存:轻量级 UI 状态(< 50KB) outState.putInt("current_step", currentStep) outState.putParcelableArrayList("cart_items", ArrayList(cartItems))// ✗ 不适合保存:Bitmap、大型列表、网络数据 }overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) setContentView(R.layout.activity_checkout)// 区分首次创建和恢复场景if (savedInstanceState != null) {// 从系统回收或配置变更恢复 currentStep = savedInstanceState.getInt("current_step") cartItems = savedInstanceState.getParcelableArrayList("cart_items") ?: emptyList() Log.d("Checkout", "恢复状态: step=$currentStep") } else {// 首次创建 Log.d("Checkout", "首次创建") } }}
5.4 onSaveInstanceState 的局限性
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
六、ViewModel 在生命周期管理中的角色
6.1 ViewModel 的生命周期
┌─────────────────────────────────────────────────────────────────────┐│ ViewModel vs Activity 生命周期对比 ││ ││ Activity 生命周期: ││ onCreate → onStart → onResume → onPause → onStop → onDestroy ││ ↑ ↑ ││ │ [旋转屏幕] │ ││ │ │ ││ onCreate → ... → onDestroy → onCreate → ... → onDestroy ││ ││ ViewModel 生命周期: ││ ┌─────────────────────────────────────────────────────────────┐ ││ │ ViewModel 存活 │ ││ │ 旋转/配置变更不会销毁 ViewModel,Activity 重建后自动关联 │ ││ └─────────────────────────────────────────────────────────────┘ ││ ↓ finish() 或 Process Death ││ onCleared() 被调用 ││ ViewModel 销毁 ││ ││ ViewModel 无法在 Process Death 中存活! │└─────────────────────────────────────────────────────────────────────┘
6.2 ViewModel 的正确使用
// ViewModel 保存内存中的 UI 状态(抵抗配置变更)classProductViewModel(privateval repository: ProductRepository,privateval savedStateHandle: SavedStateHandle // 处理 Process Death 的关键) : ViewModel() {// 从 savedStateHandle 恢复 productId(Process Death 后可恢复)privateval productId: String = savedStateHandle["product_id"] ?: throw IllegalArgumentException("product_id is required")// UI 状态(仅存活于内存,Process Death 后丢失,需要重新加载)privateval _uiState = MutableStateFlow(ProductUiState.Loading)val uiState: StateFlow<ProductUiState> = _uiState.asStateFlow()// 用户选择状态(存储在 savedStateHandle,Process Death 后可恢复)var selectedColor: String?get() = savedStateHandle["selected_color"]set(value) { savedStateHandle["selected_color"] = value }init { loadProduct() }privatefunloadProduct() { viewModelScope.launch { _uiState.value = ProductUiState.Loadingtry {val product = repository.getProduct(productId) _uiState.value = ProductUiState.Success(product) } catch (e: Exception) { _uiState.value = ProductUiState.Error(e.message ?: "加载失败") } } }overridefunonCleared() {super.onCleared()// 释放资源(viewModelScope 会自动取消协程) }}
七、SavedStateHandle — Process Death 的核心解决方案
7.1 SavedStateHandle 原理
SavedStateHandle 是 Jetpack 提供的状态保存方案,它将 ViewModel 的数据桥接到 onSaveInstanceState 机制中,从而在 Process Death 后也能恢复状态。
┌─────────────────────────────────────────────────────────────────────┐│ SavedStateHandle 工作原理 ││ ││ [进程活跃时] ││ SavedStateHandle ←→ ViewModel(内存中) ││ ││ [onSaveInstanceState 触发时] ││ SavedStateHandle 的数据 ──序列化──→ Bundle ──→ 系统持久化 ││ ││ [Process Death 后用户返回] ││ 系统持久化 Bundle ──→ Activity.onCreate(savedState) ││ │ ││ └──→ ViewModelProvider 将 Bundle 传给 SavedStateHandle ││ │ ││ └──→ ViewModel.init 时可读取上次保存的值 │└─────────────────────────────────────────────────────────────────────┘
7.2 SavedStateHandle 完整使用示例
// build.gradle// implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0"classSearchViewModel(privateval savedStateHandle: SavedStateHandle,privateval searchRepository: SearchRepository) : ViewModel() {// 方式一:直接读写(适合简单值)var searchQuery: Stringget() = savedStateHandle["search_query"] ?: ""set(value) { savedStateHandle["search_query"] = value performSearch(value) }// 方式二:StateFlow(推荐,支持响应式)val searchQueryFlow: StateFlow<String> = savedStateHandle.getStateFlow("search_query", "")// 方式三:LiveData(兼容旧代码)val searchQueryLive: LiveData<String> = savedStateHandle.getLiveData("search_query", "")privateval _searchResults = MutableStateFlow<List<SearchResult>>(emptyList())val searchResults: StateFlow<List<SearchResult>> = _searchResults.asStateFlow()init {// Process Death 恢复后,自动恢复上次的搜索val lastQuery = savedStateHandle.get<String>("search_query")if (!lastQuery.isNullOrEmpty()) { performSearch(lastQuery) } }funonQueryChanged(query: String) { savedStateHandle["search_query"] = query performSearch(query) }privatefunperformSearch(query: String) { viewModelScope.launch {val results = searchRepository.search(query) _searchResults.value = results } }}// Activity 中使用(Hilt 注入)@HiltViewModelclassSearchViewModel@Injectconstructor(privateval savedStateHandle: SavedStateHandle,privateval searchRepository: SearchRepository) : ViewModel()// Fragment 中使用classSearchFragment : Fragment() {privateval viewModel: SearchViewModel by viewModels()}
7.3 SavedStateHandle 的容量限制
SavedStateHandle 底层依赖 onSaveInstanceState,同样受 Bundle 大小限制(实践中 < 50KB)。
对于大数据,应结合持久化存储:
classCartViewModel(privateval savedStateHandle: SavedStateHandle,privateval cartRepository: CartRepository) : ViewModel() {// ✓ 在 savedStateHandle 中仅存储 ID(轻量)privatevar cartId: String?get() = savedStateHandle["cart_id"]set(value) { savedStateHandle["cart_id"] = value }privateval _cart = MutableStateFlow<Cart?>(null)val cart: StateFlow<Cart?> = _cart.asStateFlow()init {val restoredCartId = cartIdif (restoredCartId != null) {// Process Death 后:用 ID 从数据库重新加载完整数据 loadCart(restoredCartId) } }funcreateCart(userId: String) { viewModelScope.launch {val newCart = cartRepository.createCart(userId) cartId = newCart.id // 保存 ID 到 savedStateHandle _cart.value = newCart } }privatefunloadCart(id: String) { viewModelScope.launch { _cart.value = cartRepository.getCart(id) } }}
八、数据持久化策略分层
8.1 数据存储选择指南
┌─────────────────────────────────────────────────────────────────────┐│ 数据持久化策略选择 ││ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 层级一:内存(ViewModel 中的 StateFlow/LiveData) │ ││ │ 生存时间:配置变更后存活,Process Death 后丢失 │ ││ │ 适合:从网络/数据库加载的数据缓存,用于驱动 UI │ ││ └──────────────────────────────────────────────────────────────┘ ││ ↓ 如需在 Process Death 后恢复 ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 层级二:SavedStateHandle / onSaveInstanceState │ ││ │ 生存时间:Process Death 后存活,应用被用户强杀后丢失 │ ││ │ 适合:UI 瞬态(滚动位置、输入内容、选中状态、当前步骤) │ ││ │ 限制:< 50KB,仅存简单值和 ID │ ││ └──────────────────────────────────────────────────────────────┘ ││ ↓ 如需永久保存 ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 层级三:本地持久化存储 │ ││ │ Room Database:结构化数据,支持复杂查询 │ ││ │ DataStore:用户配置、简单键值对(替代 SharedPreferences) │ ││ │ File:媒体文件、缓存数据 │ ││ │ 生存时间:卸载前永久存在 │ ││ └──────────────────────────────────────────────────────────────┘ ││ ↓ 如需跨设备同步 ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ 层级四:远端存储(服务器 API / Firebase) │ ││ └──────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────┘
8.2 DataStore 替代 SharedPreferences
SharedPreferences 在主线程 apply() 虽不阻塞 UI,但 commit() 会阻塞,且 apply() 在 onStop() 时会等待写入完成,可能导致 ANR。DataStore 完全异步且支持 Flow:
// 定义 DataStoreval Context.userPreferencesStore by preferencesDataStore("user_preferences")classUserPreferencesRepository(privateval context: Context) {privateval USER_DARK_MODE = booleanPreferencesKey("dark_mode")privateval USER_LANGUAGE = stringPreferencesKey("language")// 读取(Flow,响应式)val darkModeEnabled: Flow<Boolean> = context.userPreferencesStore.data .map { preferences -> preferences[USER_DARK_MODE] ?: false }// 写入(挂起函数,异步)suspendfunsetDarkMode(enabled: Boolean) { context.userPreferencesStore.edit { preferences -> preferences[USER_DARK_MODE] = enabled } }// Proto DataStore(强类型,需要 protobuf 定义)// 推荐用于结构化配置}
九、极端场景处理实战
9.1 场景一:多步骤表单(Process Death 中途恢复)
// 模型定义dataclassCheckoutState(val step: Int = 0, // 当前步骤(0=地址, 1=支付, 2=确认)val addressId: String? = null, // 选择的地址 IDval paymentMethodId: String? = null, // 选择的支付方式 IDval couponCode: String = ""// 输入的优惠码) : Parcelable // 实现 Parcelable 才能存入 savedStateHandle@HiltViewModelclassCheckoutViewModel@Injectconstructor(privateval savedStateHandle: SavedStateHandle,privateval checkoutRepository: CheckoutRepository) : ViewModel() {// CheckoutState 实现了 Parcelable,可直接存入 savedStateHandlevar checkoutState: CheckoutStateget() = savedStateHandle["checkout_state"] ?: CheckoutState()set(value) { savedStateHandle["checkout_state"] = value }val checkoutStateFlow: StateFlow<CheckoutState> = savedStateHandle.getStateFlow("checkout_state", CheckoutState())funselectAddress(addressId: String) { checkoutState = checkoutState.copy( step = 1, addressId = addressId ) }funselectPayment(paymentMethodId: String) { checkoutState = checkoutState.copy( step = 2, paymentMethodId = paymentMethodId ) }funupdateCoupon(code: String) { checkoutState = checkoutState.copy(couponCode = code) }}// Fragment 中恢复 UIclassCheckoutFragment : Fragment() {privateval viewModel: CheckoutViewModel by activityViewModels()overridefunonViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewModel.checkoutStateFlow.collect { state ->// 根据 state 恢复到正确步骤 showStep(state.step) state.addressId?.let { restoreAddressSelection(it) } binding.etCoupon.setText(state.couponCode) } } }}
9.2 场景二:后台任务在 Process Death 后继续执行
普通协程在 Process Death 后会被取消。对于必须完成的任务(如上传文件、同步数据),应使用 WorkManager:
// 定义可靠后台任务classUploadWorker( context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {overridesuspendfundoWork(): Result {val fileUri = inputData.getString("file_uri") ?: return Result.failure()val uploadId = inputData.getString("upload_id") ?: return Result.failure()returntry {// 即使进程重启,WorkManager 也会重新执行此任务val uploadedUrl = uploadRepository.uploadFile(fileUri)val outputData = workDataOf("uploaded_url" to uploadedUrl) Result.success(outputData) } catch (e: IOException) {// 网络错误:重试if (runAttemptCount < 3) Result.retry() else Result.failure() } catch (e: Exception) { Result.failure() } }}// 提交任务funscheduleUpload(fileUri: String) {val uploadId = UUID.randomUUID().toString()val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build()val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>() .setInputData(workDataOf("file_uri" to fileUri,"upload_id" to uploadId )) .setConstraints(constraints) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS) .addTag("file_upload") .build() WorkManager.getInstance(context) .enqueueUniqueWork( uploadId, // 唯一名称,防止重复提交 ExistingWorkPolicy.KEEP, uploadRequest )}
9.3 场景三:网络请求去重与 Process Death 后重试
@HiltViewModelclassArticleViewModel@Injectconstructor(privateval savedStateHandle: SavedStateHandle,privateval articleRepository: ArticleRepository) : ViewModel() {privateval articleId: String = savedStateHandle["article_id"] ?: throw IllegalArgumentException("article_id required")// 使用 stateIn 将 Flow 转换为 StateFlow,支持订阅者离开后缓存val articleState: StateFlow<UiState<Article>> = articleRepository .getArticle(articleId) .map<Article, UiState<Article>> { UiState.Success(it) } .catch { emit(UiState.Error(it.message ?: "加载失败")) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), // 5s 内无订阅者则停止上游 initialValue = UiState.Loading )// SharingStarted.WhileSubscribed(5000) 的作用:// - 屏幕旋转时,新 Activity 5秒内重新订阅,数据不会重新加载// - Process Death 后,新进程重新订阅,重新从数据库/网络加载}
9.4 场景四:检测 App 是从 Process Death 恢复还是正常启动
classMainActivity : AppCompatActivity() {overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val launchSource = when {// savedInstanceState 不为空 → 系统恢复(包括 Process Death 恢复和旋转) savedInstanceState != null -> LaunchSource.SYSTEM_RESTORE// intent 中有特定 flag → 通知点击等外部唤起 intent.hasExtra("from_notification") -> LaunchSource.NOTIFICATION// 否则 → 正常冷启动else -> LaunchSource.COLD_START }when (launchSource) { LaunchSource.SYSTEM_RESTORE -> {// Process Death 恢复:不需要显示引导页,直接恢复到上次状态 Log.d("Launch", "从系统恢复,跳过引导") } LaunchSource.COLD_START -> {// 冷启动:可以显示 Splash、检查登录状态等 handleColdStart() } LaunchSource.NOTIFICATION -> {// 通知唤起:导航到对应页面 handleNotificationLaunch() } } }}enumclassLaunchSource{ COLD_START, SYSTEM_RESTORE, NOTIFICATION}
十、生命周期感知组件最佳实践
10.1 使用 repeatOnLifecycle 安全收集 Flow
classHomeFragment : Fragment() {privateval viewModel: HomeViewModel by viewModels()overridefunonViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// ✗ 错误:lifecycleScope.launch 在 Fragment stop 后仍会收到更新// 可能在 View 不可见时更新 UI,浪费资源甚至崩溃 lifecycleScope.launch { viewModel.uiState.collect { state -> updateUi(state) // Fragment 后台时仍执行 } }// ✓ 正确:repeatOnLifecycle 仅在生命周期 STARTED 及以上才收集 viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> updateUi(state) // 只在 Fragment 可见时执行 } } }// ✓ 多个 Flow 并发收集 viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { viewModel.products.collect { updateProductList(it) } } launch { viewModel.cartCount.collect { updateCartBadge(it) } } } } }}
10.2 LifecycleObserver 解耦生命周期逻辑
// 将生命周期相关逻辑封装成独立组件,解耦 Activity/FragmentclassLocationTracker(privateval context: Context,privateval lifecycle: Lifecycle) : DefaultLifecycleObserver {privatevar locationClient: FusedLocationProviderClient? = nullprivatevar locationCallback: LocationCallback? = nullinit { lifecycle.addObserver(this) }overridefunonStart(owner: LifecycleOwner) { startLocationUpdates() }overridefunonStop(owner: LifecycleOwner) { stopLocationUpdates() }overridefunonDestroy(owner: LifecycleOwner) { lifecycle.removeObserver(this) }privatefunstartLocationUpdates() { /* ... */ }privatefunstopLocationUpdates() { /* ... */ }}// Activity 中只需一行代码classMapActivity : AppCompatActivity() {overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// LocationTracker 自己管理生命周期 LocationTracker(this, lifecycle) }}
10.3 避免内存泄漏的生命周期管理
classImageUploadActivity : AppCompatActivity() {// ✗ 错误:持有 Activity 引用的回调,可能导致内存泄漏privateval uploadCallback = object : UploadCallback {overridefunonSuccess(url: String) {// 如果 Activity 已销毁,这里会崩溃 updateUI(url) } }// ✓ 正确方案一:使用 lifecycleScope,Activity 销毁时自动取消privatefunuploadImage(uri: Uri) { lifecycleScope.launch {try {val url = uploadRepository.upload(uri) updateUI(url) } catch (e: CancellationException) {// 正常取消,不处理 } } }// ✓ 正确方案二:在 ViewModel 中处理上传,Activity 只观察结果privateval viewModel: UploadViewModel by viewModels()overridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uploadResult.collect { result ->when (result) {is UploadResult.Success -> showSuccessUI(result.url)is UploadResult.Error -> showError(result.message) UploadResult.Loading -> showProgress() } } } } }}
十一、完整架构设计:应对 Process Death 的最佳实践
11.1 推荐架构分层
┌─────────────────────────────────────────────────────────────────────┐│ 应对 Process Death 的完整架构设计 ││ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ UI 层(Activity / Fragment) │ ││ │ • 观察 StateFlow / LiveData │ ││ │ • 使用 repeatOnLifecycle 安全收集 │ ││ │ • 通过 savedInstanceState 保存 View 的纯 UI 瞬态 │ ││ └────────────────────────┬─────────────────────────────────────┘ ││ │ 委托 ││ ┌────────────────────────▼─────────────────────────────────────┐ ││ │ ViewModel 层 │ ││ │ • 持有 UI 状态(内存级,抵抗配置变更) │ ││ │ • 使用 SavedStateHandle 保存关键 ID 和轻量状态(抵抗 Process Death)││ │ • 通过 viewModelScope 管理协程 │ ││ └────────────────────────┬─────────────────────────────────────┘ ││ │ 请求 ││ ┌────────────────────────▼─────────────────────────────────────┐ ││ │ Repository 层(Single Source of Truth) │ ││ │ • 协调网络数据与本地数据库 │ ││ │ • 暴露 Flow,支持数据库驱动 UI(Room + Flow) │ ││ │ • 数据库作为唯一真实数据源,网络数据先写库再读取 │ ││ └────────────┬─────────────────────────┬────────────────────────┘ ││ │ │ ││ ┌────────────▼───────────┐ ┌──────────▼───────────────────────┐ ││ │ 网络层(Retrofit/OkHttp)│ │ 本地存储层(Room / DataStore) │ ││ │ • 无状态,只做请求 │ │ • Process Death 后数据不丢失 │ ││ └────────────────────────┘ └──────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────┘
11.2 数据流设计(Room + Flow + ViewModel)
// Repository:数据库作为单一数据源classProductRepository@Injectconstructor(privateval productDao: ProductDao,privateval apiService: ApiService) {// Room + Flow:数据库变化时自动通知 ViewModelfungetProducts(): Flow<List<Product>> = productDao.getAllProducts()// 刷新:从网络获取后写入数据库,Flow 自动推送更新suspendfunrefreshProducts() {val remoteProducts = apiService.fetchProducts() productDao.insertAll(remoteProducts)// 不需要手动通知 UI,Room 的 Flow 会自动触发 }}// ViewModel@HiltViewModelclassProductListViewModel@Injectconstructor(privateval repository: ProductRepository) : ViewModel() {val products: StateFlow<List<Product>> = repository.getProducts() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())init {// Process Death 后重新创建 ViewModel,自动重新从数据库加载// 数据库有缓存则立即显示,同时异步刷新 refreshProducts() }privatefunrefreshProducts() { viewModelScope.launch {try { repository.refreshProducts() } catch (e: Exception) {// 网络失败不影响显示(数据库中有旧数据) } } }}
十二、测试生命周期与 Process Death
12.1 使用 Robolectric 测试生命周期
@RunWith(RobolectricTestRunner::class)classMainActivityTest{@Testfun `旋转屏幕后 ViewModel 数据不丢失`() {val scenario = ActivityScenario.launch(MainActivity::class.java) scenario.onActivity { activity ->val viewModel = activity.viewModels<MainViewModel>().value viewModel.setData("test_data") }// 模拟配置变更 scenario.recreate() scenario.onActivity { activity ->val viewModel = activity.viewModels<MainViewModel>().value assertEquals("test_data", viewModel.getData()) } }@Testfun `Process Death 后 SavedStateHandle 数据恢复`() {val scenario = ActivityScenario.launch(MainActivity::class.java) scenario.onActivity { activity ->val viewModel = activity.viewModels<MainViewModel>().value viewModel.setSearchQuery("kotlin") }// 模拟 Process Death(保存状态后重建)val bundle = Bundle() scenario.onActivity { activity -> activity.onSaveInstanceState(bundle) }// 用保存的状态重建val newScenario = ActivityScenario.launch( Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java) .also { it.putExtras(bundle) } ) newScenario.onActivity { activity ->val viewModel = activity.viewModels<MainViewModel>().value assertEquals("kotlin", viewModel.searchQuery) } }}
12.2 StateRestorationTester(Compose)
@Testfun `Compose 组件 Process Death 后状态恢复`() {val restorationTester = StateRestorationTester(composeTestRule) restorationTester.setContent {var count by rememberSaveable { mutableStateOf(0) } Button(onClick = { count++ }) { Text("Count: $count") } }// 点击按钮增加计数 composeTestRule.onNodeWithText("Count: 0").performClick() composeTestRule.onNodeWithText("Count: 1").assertIsDisplayed()// 模拟 Process Death 并恢复 restorationTester.emulateSavedInstanceStateRestore()// 验证状态恢复 composeTestRule.onNodeWithText("Count: 1").assertIsDisplayed()}
十三、常见问题与避坑指南
13.1 高频错误汇总
┌─────────────────────────────────────────────────────────────────────┐│ Process Death 相关高频错误 ││ ││ 错误 1:依赖 Application 单例中的内存状态 ││ ────────────────────────────────────────────────────────────── ││ class MyApp : Application() { ││ var currentUser: User? = null // ✗ Process Death 后丢失 ││ } ││ 解决:将 currentUser 存储在 DataStore/Room 中 ││ ││ 错误 2:在 onDestroy 中保存关键数据 ││ ────────────────────────────────────────────────────────────── ││ override fun onDestroy() { ││ saveData() // ✗ Process Death 时不调用! ││ } ││ 解决:在 onPause/onStop 中保存,或使用 Room 实时同步 ││ ││ 错误 3:将大对象存入 savedInstanceState ││ ────────────────────────────────────────────────────────────── ││ outState.putParcelable("bitmap", largeBitmap) // ✗ 超过 1MB 崩溃 ││ 解决:保存 URI 或文件路径,重建时重新加载 ││ ││ 错误 4:Fragment 提交事务在错误的时机 ││ ────────────────────────────────────────────────────────────── ││ override fun onStop() { ││ supportFragmentManager.commit { ... } // ✗ IllegalStateException ││ } ││ 解决:使用 commitAllowingStateLoss() 或在 onStart/onResume 中提交 ││ ││ 错误 5:未处理 Fragment 回退栈在 Process Death 后的恢复 ││ ────────────────────────────────────────────────────────────── ││ // 系统会自动恢复 Fragment 回退栈,但重复 add 会导致 Fragment 叠加 ││ if (savedInstanceState == null) { // ✓ 仅在首次创建时添加 Fragment ││ supportFragmentManager.commit { ││ add(R.id.container, HomeFragment()) ││ } ││ } │└─────────────────────────────────────────────────────────────────────┘
13.2 Process Death 全场景应对清单
|
|
|
|
|---|---|---|
|
|
SavedStateHandle |
|
|
|
SavedStateHandle
|
|
|
|
onSaveInstanceState
|
onRestoreInstanceState |
|
|
|
|
|
|
DataStore
EncryptedSharedPreferences |
|
|
|
WorkManager |
|
|
|
MediaSession
|
|
|
|
|
|
十四、问题解答
Q1:什么情况下 onSaveInstanceState 会被调用?什么情况下不会?
会调用:按 Home 键进入后台(系统可能回收)、屏幕旋转、多窗口切换、用户切换 App。不会调用:用户按返回键(明确关闭)、调用
finish()(明确结束)。核心判断标准:用户是否有意离开当前页面。意外离开调用,主动离开不调用。
Q2:ViewModel 能在 Process Death 后存活吗?
不能。ViewModel 存储在内存中,Process Death 后进程被杀死,内存全部释放,ViewModel 实例消失。ViewModel 能抵抗的是配置变更(屏幕旋转等),而非进程被杀。处理 Process Death 需要使用
SavedStateHandle(轻量数据)或持久化存储(Room/DataStore)。
Q3:onSaveInstanceState 和 SharedPreferences 有什么区别?
onSaveInstanceState:临时保存 UI 瞬态,用户”返回”该页面时恢复;用户主动关闭 App 后即失效;适合存储 UI 选择状态(如滚动位置)。SharedPreferences/DataStore:持久化存储,卸载前永远存在;适合用户偏好、登录 token 等需要跨会话保留的数据。
Q4:如何模拟 Process Death 进行测试?
最准确的方式:将 App 切换到后台,然后执行
adb shell am kill <package_name>,再切回 App。注意:直接在 Android Studio 点击停止按钮会触发onDestroy(),不是真正的 Process Death。
Q5:repeatOnLifecycle 和 launchWhenStarted 有什么区别?
launchWhenStarted:生命周期低于 STARTED 时挂起协程,但协程仍然存活,当 Flow 有新值时会缓存,恢复后一次性处理,可能导致短时间内大量 UI 更新。repeatOnLifecycle(STARTED):生命周期低于 STARTED 时取消协程,恢复后重新启动,不会积压数据,是推荐做法。
Q6:Fragment 在 Process Death 后恢复时,如何避免 Fragment 重叠?
系统恢复时会自动从 Back Stack 重建 Fragment,开发者不应再次手动添加。正确做法:
if (savedInstanceState == null) { /* 首次创建时才添加 Fragment */ }检查savedInstanceState != null表示正在从系统恢复,Fragment 会自动重建,不需要手动添加。
Q7:SharingStarted.WhileSubscribed(5000) 中 5000ms 的意义?
当所有订阅者离开时,等待 5000ms 后再停止上游 Flow 的收集。这样在屏幕旋转时(Activity 销毁→重建约需 300ms),新 Activity 能在 5s 内重新订阅,不会触发重新加载,直接使用缓存数据。而 Process Death 后重建进程需要更长时间,超过 5s 后上游已停止,重建后会重新订阅并触发加载。
十五、总结
┌─────────────────────────────────────────────────────────────────────┐│ Process Death 完整应对策略一览 ││ ││ 数据分类: ││ ┌──────────────┬────────────────────┬──────────────────────────┐ ││ │ 数据特性 │ 推荐存储方式 │ 示例 │ ││ ├──────────────┼────────────────────┼──────────────────────────┤ ││ │ UI 瞬态(轻量)│ SavedStateHandle │ 当前步骤、选中ID、查询词 │ ││ │ 业务数据 │ Room Database │ 商品列表、用户信息、订单 │ ││ │ 用户偏好 │ DataStore │ 主题、语言、登录状态 │ ││ │ 后台任务 │ WorkManager │ 文件上传、数据同步 │ ││ │ 网络缓存 │ Room + Flow │ 文章内容、图片元数据 │ ││ └──────────────┴────────────────────┴──────────────────────────┘ ││ ││ 架构原则: ││ 1. 以 Room 数据库作为 Single Source of Truth ││ 2. ViewModel + SavedStateHandle 处理 UI 状态 ││ 3. repeatOnLifecycle 安全收集 Flow ││ 4. WorkManager 保证后台任务可靠执行 ││ 5. 不依赖 onDestroy 做任何重要操作 ││ 6. 首次创建和恢复场景明确区分处理逻辑 │└─────────────────────────────────────────────────────────────────────┘
夜雨聆风