## 3 ViewModel在重建中的存活机制:ViewModelScope、onCleared与onSaveInstanceState的区别 ### 3.1 存活机制核心原理 ViewModel 在 Activity 重建(如配置变更、系统杀死后重建)时存活的核心原因在于 **作用域分离**。Activity 的 `onSaveInstanceState` 用于保存可序列化的轻量状态(Bundle),而 ViewModel 通过将数据持有者提升至 `ViewModelStoreOwner` 的存储范围内,避免因界面重建而丢失数据。 - **ViewModelScope**:ViewModels 内置的协程作用域,跟随 ViewModel 的生命周期,当 ViewModel 被清理时自动取消所有协程。 - **onCleared()**:ViewModel 生命周期的终点回调,用于释放资源(如关闭数据库连接、取消网络请求、反注册监听器)。 - **onSaveInstanceState**:Activity/Fragment 的回调,用于保存 UI 瞬态状态(如滚动位置、输入框内容、选中的 Tab),数据存储在 Bundle 中,受 Binder 事务大小限制(约 1MB)。 **关键区别**:ViewModel 存活但并非永远存在;当宿主 Activity 被真正销毁(finish())或进程被系统杀死且用户没有重新进入该 Activity 时,ViewModel 也会被清理。`onSaveInstanceState` 则仅用于保存 UI 层状态,不涉及业务逻辑层。 ### 3.2 ViewModel 存活生命周期流程图 ViewModel 存活机制对比图 ViewModel 对象池 存在 ViewModelStore 中 Activity 重建时复用 finish() 或进程杀死后清除 ViewModelScope 自动取消协程 无需手动释放 适合耗时操作 onSaveInstanceState Bundle 序列化存储 支持配置变更/进程杀死 容量有限 (~1MB) onCleared() 释放资源回调 清理监听器/连接 不可用于保存 UI 状态 配置变更 📌 核心区别: ViewModel 存活于内存中,适合保存复杂业务数据;onSaveInstanceState 存于序列化 Bundle,适合保存 UI 瞬态; ViewModelScope 自动取消协程;onCleared 用于资源释放。 ### 3.3 实现流程与代码示例 #### 3.3.1 ViewModel 定义及 ViewModelScope 使用
// MyViewModel.kt
class MyViewModel : ViewModel() {

    // 使用 viewModelScope 启动协程
    fun loadData() {
        viewModelScope.launch {
            // 模拟网络请求
            val result = repository.fetchData()
            _data.value = result
        }
    }

    // 自定义协程作用域(不推荐,除非需要更精细控制)
    private val customScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    fun longRunningTask() {
        customScope.launch {
            // 该协程不会随 ViewModel 清理自动取消,需手动处理
            processLargeDataSet()
        }
    }

    override fun onCleared() {
        super.onCleared()
        // 手动取消自定义作用域
        customScope.cancel()
        // 释放其他资源
        repository.close()
    }
}
#### 3.3.2 onSaveInstanceState 保存 UI 状态
// MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels()
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 恢复 UI 状态
        savedInstanceState?.let { bundle ->
            val savedPosition = bundle.getInt("scroll_position", 0)
            binding.recyclerView.scrollToPosition(savedPosition)
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        // 保存 UI 瞬态数据
        outState.putInt("scroll_position", binding.recyclerView.computeVerticalScrollOffset())
        outState.putString("input_text", binding.editText.text.toString())
        outState.putParcelable("selected_item", binding.listView.selectedItem)
    }
}
#### 3.3.3 ViewModel 中的状态恢复(配合 SavedStateHandle)
// ProfileViewModel.kt
class ProfileViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // SavedStateHandle 自动处理配置变更和进程杀死
    val userId: MutableLiveData = savedStateHandle.getLiveData("userId", "")

    fun updateUserId(newId: String) {
        savedStateHandle["userId"] = newId
    }

    override fun onCleared() {
        super.onCleared()
        // 清理工作,不要在此处保存数据
        analyticsTracker.flush()
    }
}
### 3.4 三个机制对比表 | 特性 | ViewModelScope | onCleared() | onSaveInstanceState | |------|---------------|-------------|---------------------| | 触发时机 | ViewModel 初始化时创建 | ViewModel 被清理前 | Activity 进入后台或销毁前 | | 作用域 | 跟随 ViewModel 生命周期 | 仅调用一次 | 每次可能销毁时 | | 数据类型 | 协程任务 | 资源释放(非序列化) | 可序列化数据(Bundle) | | 容量限制 | 无 | 无 | ~1MB (Binder 事务) | | 适用场景 | 网络请求、数据库操作 | 关闭数据库/文件、释放锁 | 滚动位置、输入框内容、选中项 | | 进程杀死后恢复 | 否(新 ViewModel 实例) | 否 | 是(从 Bundle 恢复) | | 配置变更后恢复 | 是(相同 ViewModel 实例) | 否 | 是 | ### 3.5 调试与常见错误 #### 3.5.1 调试技巧
// 在 ViewModel 中添加日志来观察生命周期
class DebugViewModel : ViewModel() {

    init {
        Log.d("ViewModelLifecycle", "init: ${this.hashCode()}")
    }

    override fun onCleared() {
        super.onCleared()
        Log.d("ViewModelLifecycle", "onCleared: ${this.hashCode()}")
    }
}

// 在 Activity 中检查 ViewModel 是否复用
val prevHash = viewModel.hashCode()
// 旋转屏幕后,hashCode() 应该相同
Log.d("ViewModelTest", "ViewModel hash: ${viewModel.hashCode()}")
#### 3.5.2 常见错误与避坑 | 错误类型 | 问题描述 | 解决方案 | |---------|---------|---------| | 在 onCleared() 中保存数据 | ViewModel 清理后数据丢失 | 使用 SavedStateHandle 或持久化存储 | | viewModelScope 未正确处理异常 | 协程未捕获的异常导致 ViewModel 泄漏 | 添加 try-catch 或使用 `SupervisorJob` | | onSaveInstanceState 存储大对象 | Bundle 过大导致 TransactionTooLargeException | 仅保存轻量状态,大数据存入 ViewModel | | 自定义 CoroutineScope 未取消 | 导致协程泄漏和内存泄漏 | 在 onCleared() 中 `scope.cancel()` | | 在 ViewModel 中持有 Activity 上下文 | 内存泄漏 | 使用 Application 上下文或 ViewModel 专用类 |
⚠️ Warning:ViewModel 在配置变更时存活,但 不会保存 UI 状态到磁盘。如果用户切换到其他应用且进程被系统杀死,重新启动 Activity 后 ViewModel 将被重新创建,此时必须依赖 onSaveInstanceState 或 SavedStateHandle 恢复 UI 状态。
### 3.6 性能与稳定性优化 - **优先使用 SavedStateHandle**:替代手动 onSaveInstanceState 实现,支持自动序列化,减少样板代码。 - **合理划分数据职责**:业务数据(用户信息、列表数据)放入 ViewModel;UI 瞬态(输入框文本、滚动位置)通过 onSaveInstanceState 或 SavedStateHandle 处理。 - **避免在 ViewModel 中持有 Activity/Fragment 引用**:使用 LiveData、StateFlow 等响应式组件进行通信。 - **监控 ViewModel 泄漏**:使用 LeakCanary 检测异常,确保 onCleared() 不会因为持有外部引用而未被调用。
💡 最佳实践:在 Activity `onCreate()` 中通过 `viewModels()` 获取 ViewModel,配合 `SavedStateHandle` 可实现无痛的状态保持;仅在必要时手动调用 `onSaveInstanceState` 存储 UI 辅助状态。
// 推荐组合使用 SavedStateHandle + ViewModel
class RecommendationViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // 自动保存/恢复用户输入的搜索词
    val searchQuery: LiveData = savedStateHandle.getLiveData("search_query", "")

    // ViewModelScope 处理异步请求
    fun performSearch(query: String) {
        savedStateHandle["search_query"] = query
        viewModelScope.launch {
            val results = repository.search(query)
            _searchResults.value = results
        }
    }

    override fun onCleared() {
        super.onCleared()
        // 清理资源,但不应保存数据
        searchRepository.close()
    }
}