1. 认识ViewModel
1.1 为什么要使用ViewModel?
在传统的Android开发中,开发者面临几个核心痛点,ViewModel正是为解决这些问题而设计的架构组件。
四大核心问题:
- 数据丢失问题 :Activity/Fragment因配置变更(屏幕旋转、语言切换、深色模式切换)而重建时,所有临时UI数据(用户输入、列表滚动位置、未提交的表单)都会丢失。
- 内存泄漏风险:异步任务(网络请求、数据库操作)持有UI组件的引用,当UI销毁后任务仍在执行,导致Activity无法被垃圾回收。
- 职责边界模糊:Activity和Fragment变成了"上帝类",既要处理UI交互、视图绑定,又要处理数据加载、业务逻辑,违反了单一职责原则。
- 测试困难:业务逻辑与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 → 系统临时存储)
- 系统检测到屏幕旋转,准备销毁当前Activity
- 调用
Activity.retainNonConfigurationInstances()方法 ComponentActivity重写onRetainNonConfigurationInstance(),将ViewModelStore包装进NonConfigurationInstances对象- 系统将
NonConfigurationInstances存入ActivityClientRecord
阶段2:重建时传递(系统临时存储 → 新Activity)
- 系统创建新的Activity实例
- 在
ActivityThread.performLaunchActivity()中调用新Activity的attach()方法 - 将
ActivityClientRecord中暂存的lastNonConfigurationInstances作为参数传入 - 新Activity将其保存到成员变量
mLastNonConfigurationInstances
阶段3:按需恢复(新Activity内部获取)
- 新Activity首次调用
getViewModelStore()方法 - 检查
mViewModelStore是否为空 - 从
mLastNonConfigurationInstances中获取旧的ViewModelStore - 如果获取成功,直接复用;否则创建新的
关键源码节点
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:租用房间的租客
- 租客A(Activity A)退租前,把保险箱寄存在管理处(系统)
- 租客B(Activity B)入住同一房间,管理处把保险箱还给他
- 珠宝(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)
}
}
}
内存泄漏检测与预防策略
防御性编程清单:
- ✅ 绝不持有
Activity/Fragment/View的引用 - ✅ 使用
AndroidViewModel获取Application Context - ✅ 所有协程使用
viewModelScope.launch - ✅ RxJava使用
CompositeDisposable并在onCleared()中清理 - ✅ 使用
LifecycleOwner自动管理LiveData观察者 - ✅ 在
onCleared()中清理所有资源、反注册监听器 - ❌ 避免将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的核心价值回顾
- 生命周期感知:自动处理配置变更,开发者无需关心数据保存与恢复
- 数据持久化:在适当的生命周期内保持数据一致性
- 架构清晰:促进MVVM/MVI架构,实现关注点分离
- 可测试性:纯业务逻辑,易于单元测试
- 数据共享:简化组件间通信,特别是Fragment间通信
最佳实践总结
DO(应该做的) :
- ✅ 使用
by viewModels()简化创建 - ✅ 使用
viewModelScope管理协程 - ✅ 结合Repository模式处理数据
- ✅ 使用SavedStateHandle处理进程死亡恢复
- ✅ 为ViewModel编写单元测试
- ✅ 使用状态容器(State Flow)管理UI状态
DON'T(不应该做的) :
- ❌ 在ViewModel中持有View/Activity引用
- ❌ 使用GlobalScope或未管理的Disposable
- ❌ 将大量数据直接存储在ViewModel中(考虑分页)
- ❌ 忽略
onCleared()中的资源清理 - ❌ 将ViewModel作为全局单例使用
面试要点速查
-
ViewModel如何在屏幕旋转后存活?
- 通过
onRetainNonConfigurationInstance()保存ViewModelStore - 新Activity从
getLastNonConfigurationInstance()恢复
- 通过
-
ViewModel和LiveData的区别?
- ViewModel:存储和管理UI相关数据
- LiveData:可观察的数据持有者,具有生命周期感知
-
如何避免ViewModel内存泄漏?
- 使用Application Context
- 使用viewModelScope管理协程
- 在onCleared()中清理资源
-
ViewModel与onSaveInstanceState的适用场景?
- ViewModel:配置变更,较大的UI数据
- onSaveInstanceState:进程死亡恢复,少量关键状态
未来发展趋势
- 与Compose深度集成 :
viewModel()函数成为Compose标准 - 作用域精细化:Navigation Graph作用域、自定义作用域
- 响应式增强:全面转向StateFlow/SharedFlow
- 多模块支持:Hilt作用域在功能模块中的应用
ViewModel已成为现代Android开发的基石,理解其原理并掌握最佳实践,对于构建健壮、可维护的Android应用至关重要。随着Android架构的不断演进,ViewModel将继续在状态管理和架构设计方面发挥核心作用。