Android Jetpack - 2 ViewModel

1. 认识ViewModel

1.1 为什么要使用ViewModel?

在传统的Android开发中,开发者面临几个核心痛点,ViewModel正是为解决这些问题而设计的架构组件。

四大核心问题

  1. 数据丢失问题 :Activity/Fragment因配置变更(屏幕旋转、语言切换、深色模式切换)而重建时,所有临时UI数据(用户输入、列表滚动位置、未提交的表单)都会丢失。
  2. 内存泄漏风险:异步任务(网络请求、数据库操作)持有UI组件的引用,当UI销毁后任务仍在执行,导致Activity无法被垃圾回收。
  3. 职责边界模糊:Activity和Fragment变成了"上帝类",既要处理UI交互、视图绑定,又要处理数据加载、业务逻辑,违反了单一职责原则。
  4. 测试困难:业务逻辑与Android组件(Context、View)紧耦合,难以编写单元测试,必须依赖昂贵的仪器化测试。

ViewModel的解决方案

kotlin

kotlin 复制代码
// 传统方式 - 数据随Activity销毁
class ProblemActivity : AppCompatActivity() {
    private var userInput: String = "" // 旋转屏幕后丢失!
    private var currentPage: Int = 1   // 旋转屏幕后丢失!
    
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        // 必须手动保存每个字段
        outState.putString("input", userInput)
        outState.putInt("page", currentPage)
    }
}

// ViewModel方式 - 数据自动保留
class SolutionViewModel : ViewModel() {
    private val _userInput = MutableLiveData("")
    val userInput: LiveData<String> = _userInput // 旋转后依然存在
    
    private val _currentPage = MutableLiveData(1)
    val currentPage: LiveData<Int> = _currentPage // 旋转后依然存在
}

1.2 ViewModel的使用方法

基本模式:状态管理与UI响应

kotlin

kotlin 复制代码
// 1. 定义ViewModel
class UserProfileViewModel(
    private val userId: String,
    private val repository: UserRepository
) : ViewModel() {
    
    // UI状态封装
    data class UiState(
        val user: User? = null,
        val isLoading: Boolean = false,
        val error: String? = null
    )
    
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    // 初始化加载
    init {
        loadUser()
    }
    
    private fun loadUser() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val user = repository.getUser(userId)
                _uiState.update { it.copy(isLoading = false, user = user) }
            } catch (e: Exception) {
                _uiState.update { it.copy(isLoading = false, error = e.message) }
            }
        }
    }
    
    // 用户操作处理
    fun onRefresh() {
        loadUser()
    }
}

// 2. 在Activity/Fragment中使用
class UserProfileActivity : AppCompatActivity() {
    private val viewModel: UserProfileViewModel by viewModels {
        UserProfileViewModelFactory("user123", UserRepository())
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 观察UI状态变化
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when {
                        state.isLoading -> showLoading()
                        state.error != null -> showError(state.error)
                        state.user != null -> showUser(state.user)
                    }
                }
            }
        }
        
        // 绑定用户操作
        refreshButton.setOnClickListener {
            viewModel.onRefresh()
        }
    }
}

1.3 ViewModel的创建方式

多种创建方式对比与选择

方式 代码示例 适用场景 优点 缺点
基础方式 ViewModelProvider(this).get(MyViewModel::class.java) 简单场景、教学示例 无需额外依赖 代码冗长,需手动管理
Kotlin委托 private val vm: MyViewModel by viewModels() 绝大多数场景 简洁、延迟初始化、空安全 需要添加ktx依赖
自定义Factory by viewModels { MyFactory(params) } 需要参数的ViewModel 支持依赖注入 需要编写Factory类
Hilt注入 @HiltViewModel class MyViewModel @Inject constructor() 现代项目、多模块架构 全自动依赖管理 需要配置Hilt

1. Kotlin属性委托(推荐)

kotlin

kotlin 复制代码
// Activity中
class MainActivity : AppCompatActivity() {
    // 最简单的方式
    private val viewModel1: MyViewModel by viewModels()
    
    // 带自定义Factory
    private val viewModel2: MyViewModel by viewModels {
        MyViewModelFactory(repository)
    }
}

// Fragment中
class MyFragment : Fragment() {
    // Fragment作用域
    private val viewModel1: MyViewModel by viewModels()
    
    // 与Activity共享(多Fragment通信)
    private val sharedViewModel: SharedViewModel by activityViewModels()
    
    // 带参数
    private val detailViewModel: DetailViewModel by viewModels {
        DetailViewModelFactory(args.itemId)
    }
}

2. 自定义ViewModelFactory

kotlin

kotlin 复制代码
// 带参数的ViewModel
class UserViewModel(
    private val userId: String,
    private val userRepository: UserRepository
) : ViewModel() { /* ... */ }

// 对应的Factory
class UserViewModelFactory(
    private val userId: String,
    private val userRepository: UserRepository
) : ViewModelProvider.Factory {
    
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        // 1. 类型安全检查
        require(modelClass.isAssignableFrom(UserViewModel::class.java)) {
            "Unknown ViewModel class: $modelClass"
        }
        // 2. 创建实例
        return UserViewModel(userId, userRepository) as T
    }
}

// 3. 使用
val factory = UserViewModelFactory("user123", repository)
val viewModel: UserViewModel by viewModels { factory }

2. ViewModel实现原理分析

2.1 ViewModel的创建过程

核心流程源码分析

java

less 复制代码
// ViewModelProvider.java - 获取ViewModel的核心逻辑
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    // 1. 生成唯一Key:默认前缀 + 类全名
    String key = "androidx.lifecycle.ViewModelProvider.DefaultKey:" + canonicalName;
    return get(key, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    // 2. 先从ViewModelStore中获取已存在的实例
    ViewModel viewModel = mViewModelStore.get(key);
    
    if (modelClass.isInstance(viewModel)) {
        // 3. 如果类型匹配,直接返回(复用实例)
        return (T) viewModel;
    }
    
    // 4. 否则通过Factory创建新实例
    viewModel = mFactory.create(modelClass);
    // 5. 存储到ViewModelStore中
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

ViewModelStore的内部结构

java

typescript 复制代码
public class ViewModelStore {
    // 核心:HashMap存储所有ViewModel
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    
    // 存储ViewModel
    final void put(String key, @NonNull ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            // 如果已有相同Key的ViewModel,清理旧的
            oldViewModel.onCleared();
        }
    }
    
    // 获取ViewModel
    final ViewModel get(String key) {
        return mMap.get(key);
    }
    
    // 清理所有ViewModel(在Activity销毁时调用)
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

2.2 by viewModels()实现原理分析

Kotlin属性委托背后的魔法

kotlin

kotlin 复制代码
// androidx.fragment.app.FragmentViewModelLazy.kt
inline fun <reified VM : ViewModel> Fragment.viewModels(
    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    // 创建Lazy委托
    return createViewModelLazy(
        VM::class,
        { ownerProducer().viewModelStore }, // 获取ViewModelStore
        factoryProducer ?: { defaultViewModelProviderFactory }
    )
}

// 实际的Lazy实现
fun <VM : ViewModel> createViewModelLazy(
    viewModelClass: KClass<VM>,
    storeProducer: () -> ViewModelStore,
    factoryProducer: () -> ViewModelProvider.Factory
): Lazy<VM> {
    return object : Lazy<VM> {
        private var cached: VM? = null
        
        override val value: VM
            get() {
                return cached ?: let {
                    // 延迟初始化:第一次访问时才创建
                    ViewModelProvider(storeProducer(), factoryProducer())
                        .get(viewModelClass.java)
                        .also { cached = it }
                }
            }
        
        override fun isInitialized(): Boolean = cached != null
    }
}

与手动创建的等价关系

kotlin

kotlin 复制代码
// 这两种方式是等价的:
private val viewModel: MyViewModel by viewModels()

// 等价于:
private lateinit var viewModel: MyViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
}

2.3 ViewModel如何实现不同的作用域

Android中的ViewModel作用域体系

作用域 获取方式 生命周期 典型使用场景
Activity作用域 by viewModels() 与Activity绑定,Activity销毁时清除 单个Activity内部使用的数据
Fragment作用域 by viewModels() 与Fragment绑定,Fragment销毁时清除 单个Fragment内部使用的数据
Activity共享作用域 by activityViewModels() 与父Activity绑定 多个Fragment间共享数据
Navigation作用域 by navGraphViewModels(R.id.nav_graph) 与导航图绑定 同一导航流程中共享数据

多Fragment共享数据的实现原理

kotlin

kotlin 复制代码
// 共享ViewModel
class SharedViewModel : ViewModel() {
    val selectedItem = MutableLiveData<Item>()
    val searchQuery = MutableLiveData<String>()
}

// Fragment A - 发送数据
class ListFragment : Fragment() {
    // 关键:使用activityViewModels()获取Activity作用域的实例
    private val sharedViewModel: SharedViewModel by activityViewModels()
    
    fun onItemClick(item: Item) {
        sharedViewModel.selectedItem.value = item
    }
}

// Fragment B - 接收数据
class DetailFragment : Fragment() {
    // 获取的是同一个实例!
    private val sharedViewModel: SharedViewModel by activityViewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        sharedViewModel.selectedItem.observe(viewLifecycleOwner) { item ->
            // 显示选中项详情
            showDetails(item)
        }
    }
}

原理activityViewModels()内部使用requireActivity()作为ViewModelStoreOwner,因此多个Fragment从同一个Activity的ViewModelStore中获取ViewModel,自然得到同一个实例。

2.4 为什么Activity在屏幕旋转重建后可以恢复ViewModel?

这是ViewModel最核心的机制,理解它需要深入Android系统层面。

核心结论

ViewModel能在屏幕旋转后恢复,是因为Android系统将数据区分为配置相关数据非配置相关数据 。ViewModel属于后者,其存储容器ViewModelStore在Activity销毁前被系统临时保存,并在新Activity创建后传递过去。整个过程是内存中对象引用的直接转移,而非序列化与反序列化。

详细恢复流程(三个阶段)

阶段1:销毁前保存(旧Activity → 系统临时存储)

  1. 系统检测到屏幕旋转,准备销毁当前Activity
  2. 调用Activity.retainNonConfigurationInstances()方法
  3. ComponentActivity重写onRetainNonConfigurationInstance(),将ViewModelStore包装进NonConfigurationInstances对象
  4. 系统将NonConfigurationInstances存入ActivityClientRecord

阶段2:重建时传递(系统临时存储 → 新Activity)

  1. 系统创建新的Activity实例
  2. ActivityThread.performLaunchActivity()中调用新Activity的attach()方法
  3. ActivityClientRecord中暂存的lastNonConfigurationInstances作为参数传入
  4. 新Activity将其保存到成员变量mLastNonConfigurationInstances

阶段3:按需恢复(新Activity内部获取)

  1. 新Activity首次调用getViewModelStore()方法
  2. 检查mViewModelStore是否为空
  3. mLastNonConfigurationInstances中获取旧的ViewModelStore
  4. 如果获取成功,直接复用;否则创建新的

关键源码节点

java

ini 复制代码
// ComponentActivity.java - 保存ViewModelStore
@Override
public final Object onRetainNonConfigurationInstance() {
    ViewModelStore viewModelStore = mViewModelStore;
    // 包装并返回
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.viewModelStore = viewModelStore; // 关键:保存ViewModelStore
    return nci;
}

// ComponentActivity.java - 恢复ViewModelStore
@NonNull
public ViewModelStore getViewModelStore() {
    if (mViewModelStore == null) {
        // 关键:优先从旧数据中恢复
        NonConfigurationInstances nc = 
            (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mViewModelStore = nc.viewModelStore; // 直接复用!
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore(); // 没有才创建新的
        }
    }
    return mViewModelStore;
}

形象比喻:保险箱传递

可以把整个过程想象成:

  • ViewModel实例:珍贵的珠宝
  • ViewModelStore:存放珠宝的保险箱
  • Activity:租用房间的租客
  1. 租客A(Activity A)退租前,把保险箱寄存在管理处(系统)
  2. 租客B(Activity B)入住同一房间,管理处把保险箱还给他
  3. 珠宝(ViewModel)从未被移动,只是保险箱换了主人

2.5 ViewModel的数据在什么时候才会清除

ViewModel生命周期的三种终结场景

场景 触发条件 ViewModel状态变化 是否调用onCleared()
正常退出 用户按返回键、调用finish() ViewModel被清除 ✅ 是
配置变更 屏幕旋转、语言切换等 ViewModel保留并恢复 ❌ 否
进程被杀死 系统内存不足、应用在后台被杀死 ViewModel随进程消亡 ❌ 否

源码中的清除逻辑

java

scss 复制代码
// ComponentActivity.java
@Override
protected void onDestroy() {
    super.onDestroy();
    
    if (mViewModelStore != null) {
        // 关键判断:是否为配置变更
        if (!isChangingConfigurations()) {
            // 非配置变更 → 清除ViewModel
            mViewModelStore.clear();
        }
        // 配置变更 → 保留ViewModel
    }
}

// ViewModelStore.clear()
public final void clear() {
    for (ViewModel viewModel : mMap.values()) {
        // 调用每个ViewModel的onCleared()
        viewModel.onCleared();
    }
    mMap.clear();
}

onCleared()的正确使用

kotlin

kotlin 复制代码
class ResourceViewModel : ViewModel() {
    
    // 需要清理的资源
    private val compositeDisposable = CompositeDisposable()
    private var timer: Timer? = null
    private val listeners = mutableListOf<Listener>()
    
    init {
        // 初始化资源
        timer = Timer().apply {
            scheduleAtFixedRate(timerTask { /* ... */ }, 0, 1000)
        }
    }
    
    fun registerListener(listener: Listener) {
        listeners.add(listener)
    }
    
    override fun onCleared() {
        super.onCleared()
        // 1. 取消所有异步任务
        compositeDisposable.clear()
        
        // 2. 停止定时器
        timer?.cancel()
        timer = null
        
        // 3. 清理监听器
        listeners.clear()
        
        // 4. 释放其他资源
        releaseOtherResources()
    }
}

3. ViewModel的内存泄漏问题

常见内存泄漏场景与解决方案

场景1:在ViewModel中持有Context引用

kotlin

kotlin 复制代码
// ❌ 错误:直接持有Activity Context
class LeakyViewModel(context: Context) : ViewModel() {
    private val context: Context = context // 内存泄漏!
}

// ✅ 正确1:使用AndroidViewModel获取Application Context
class SafeViewModel1(application: Application) : AndroidViewModel(application) {
    private val context: Context = getApplication<Application>().applicationContext
}

// ✅ 正确2:通过依赖注入传入Application Context
class SafeViewModel2(
    private val appContext: Context // 确保是Application Context
) : ViewModel() {
    init {
        require(appContext.applicationContext == appContext) {
            "必须使用Application Context"
        }
    }
}

场景2:未正确管理的异步任务

kotlin

kotlin 复制代码
// ❌ 错误:使用GlobalScope
class CoroutineLeakViewModel : ViewModel() {
    fun fetchData() {
        GlobalScope.launch { // 生命周期与App相同!
            delay(5000)
            updateUI() // ViewModel可能已销毁
        }
    }
}

// ✅ 正确:使用viewModelScope
class SafeCoroutineViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch { // 自动绑定ViewModel生命周期
            try {
                val data = repository.loadData()
                updateState(data)
            } catch (e: CancellationException) {
                // 正常取消,无需处理
            }
        }
    }
}

// ✅ RxJava的正确管理
class RxViewModel : ViewModel() {
    private val compositeDisposable = CompositeDisposable()
    
    fun fetchData() {
        repository.getData()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { result ->
                // 处理结果
            }
            .addTo(compositeDisposable) // 添加到CompositeDisposable
    }
    
    override fun onCleared() {
        super.onCleared()
        compositeDisposable.clear() // 清理所有Disposable
    }
}

场景3:观察者未及时移除

kotlin

kotlin 复制代码
// ❌ 潜在问题:观察者持有外部引用
class ObserverLeakActivity : AppCompatActivity() {
    private val observer = Observer<String> { data ->
        // 如果observer持有Activity引用...
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.data.observe(this, observer)
    }
}

// ✅ 正确:使用lambda或方法引用
class SafeObserverActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // LiveData会自动在Activity销毁时移除观察者
        viewModel.data.observe(this) { data ->
            updateUI(data)
        }
    }
}

内存泄漏检测与预防策略

防御性编程清单

  1. ✅ 绝不持有Activity/Fragment/View的引用
  2. ✅ 使用AndroidViewModel获取Application Context
  3. ✅ 所有协程使用viewModelScope.launch
  4. ✅ RxJava使用CompositeDisposable并在onCleared()中清理
  5. ✅ 使用LifecycleOwner自动管理LiveData观察者
  6. ✅ 在onCleared()中清理所有资源、反注册监听器
  7. ❌ 避免将ViewModel存储在静态变量或单例中

4. ViewModel和onSaveInstanceState对比

本质区别与适用场景

维度 ViewModel onSaveInstanceState
设计目的 处理配置变更 处理进程死亡
数据载体 内存对象引用 Bundle序列化
存储位置 内存中 Bundle(可序列化到磁盘)
数据大小 无限制(受内存约束) 有限制(通常<1MB)
数据类型 任何Java/Kotlin对象 基本类型、Parcelable、Serializable
恢复时机 配置变更后立即可用 onCreate()中手动恢复
性能开销 几乎无(内存引用) 序列化/反序列化开销

现代方案:ViewModel + SavedStateHandle

kotlin

kotlin 复制代码
// 结合两者优势的现代实践
class UserProfileViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    companion object {
        private const val USER_ID_KEY = "user_id"
        private const val SEARCH_QUERY_KEY = "search_query"
    }
    
    // 需要进程死亡恢复的数据
    var userId: String
        get() = savedStateHandle[USER_ID_KEY] ?: ""
        set(value) = savedStateHandle.set(USER_ID_KEY, value)
    
    var searchQuery: String
        get() = savedStateHandle[SEARCH_QUERY_KEY] ?: ""
        set(value) = savedStateHandle.set(SEARCH_QUERY_KEY, value)
    
    // 不需要持久化的临时数据
    private val _userData = MutableStateFlow<User?>(null)
    val userData: StateFlow<User?> = _userData.asStateFlow()
    
    fun loadUser() {
        viewModelScope.launch {
            _userData.value = repository.getUser(userId)
        }
    }
}

选择策略决策树

text

markdown 复制代码
需要持久化数据吗?
├── 否 → 使用ViewModel普通属性
├── 是 → 
    ├── 需要进程死亡后恢复吗?
    │   ├── 否 → ViewModel + LiveData/StateFlow
    │   └── 是 → 
    │       ├── 数据量小且简单 → onSaveInstanceState
    │       └── 数据复杂或需要共享 → ViewModel + SavedStateHandle
    └── 是否是UI状态?
        ├── 是 → ViewModel + SavedStateHandle
        └── 否 → 考虑持久化方案(Room、DataStore)

5. 总结

ViewModel的核心价值回顾

  1. 生命周期感知:自动处理配置变更,开发者无需关心数据保存与恢复
  2. 数据持久化:在适当的生命周期内保持数据一致性
  3. 架构清晰:促进MVVM/MVI架构,实现关注点分离
  4. 可测试性:纯业务逻辑,易于单元测试
  5. 数据共享:简化组件间通信,特别是Fragment间通信

最佳实践总结

DO(应该做的)

  • ✅ 使用by viewModels()简化创建
  • ✅ 使用viewModelScope管理协程
  • ✅ 结合Repository模式处理数据
  • ✅ 使用SavedStateHandle处理进程死亡恢复
  • ✅ 为ViewModel编写单元测试
  • ✅ 使用状态容器(State Flow)管理UI状态

DON'T(不应该做的)

  • ❌ 在ViewModel中持有View/Activity引用
  • ❌ 使用GlobalScope或未管理的Disposable
  • ❌ 将大量数据直接存储在ViewModel中(考虑分页)
  • ❌ 忽略onCleared()中的资源清理
  • ❌ 将ViewModel作为全局单例使用

面试要点速查

  1. ViewModel如何在屏幕旋转后存活?

    • 通过onRetainNonConfigurationInstance()保存ViewModelStore
    • 新Activity从getLastNonConfigurationInstance()恢复
  2. ViewModel和LiveData的区别?

    • ViewModel:存储和管理UI相关数据
    • LiveData:可观察的数据持有者,具有生命周期感知
  3. 如何避免ViewModel内存泄漏?

    • 使用Application Context
    • 使用viewModelScope管理协程
    • 在onCleared()中清理资源
  4. ViewModel与onSaveInstanceState的适用场景?

    • ViewModel:配置变更,较大的UI数据
    • onSaveInstanceState:进程死亡恢复,少量关键状态

未来发展趋势

  1. 与Compose深度集成viewModel()函数成为Compose标准
  2. 作用域精细化:Navigation Graph作用域、自定义作用域
  3. 响应式增强:全面转向StateFlow/SharedFlow
  4. 多模块支持:Hilt作用域在功能模块中的应用

ViewModel已成为现代Android开发的基石,理解其原理并掌握最佳实践,对于构建健壮、可维护的Android应用至关重要。随着Android架构的不断演进,ViewModel将继续在状态管理和架构设计方面发挥核心作用。

相关推荐
姓王者34 分钟前
chen-er 专为Chen式ER图打造的npm包
前端·javascript
崽崽的谷雨38 分钟前
react里ag-grid实现树形数据展示
前端·react.js·前端框架
栀秋66639 分钟前
就地编辑功能开发指南:从代码到体验的优雅蜕变
前端·javascript·代码规范
国服第二切图仔41 分钟前
Electron for 鸿蒙PC项目实战案例 - 连连看小游戏
前端·javascript·electron·鸿蒙pc
3***g20541 分钟前
SQLyog安装配置(注册码)连接MySQL
android·mysql·adb
社恐的下水道蟑螂1 小时前
深度探索 JavaScript 的 OOP 编程之道:从基础到进阶
前端·javascript·架构
1_2_3_1 小时前
前端模块联邦介绍
前端
申阳1 小时前
Day 19:02. 基于 SpringBoot4 开发后台管理系统-项目初始化
前端·后端·程序员