Android ViewModel 深度解析

一、ViewModel 的本质与价值

在Android开发中,屏幕旋转是一个常见的配置更改场景。当用户旋转设备时,Activity会被销毁并重新创建,导致临时数据丢失。传统解决方案(如onSaveInstanceState)只适合保存少量简单数据。ViewModel应运而生,它解决了配置更改导致的数据丢失问题,同时将数据逻辑与UI分离,成为现代Android架构的核心组件。 主要解决两大问题:

  1. 数据持久化问题

    • 屏幕旋转时保留数据
    • Activity重建时避免重复加载
  2. 生命周期安全问题

    • 自动取消异步任务
    • 防止内存泄漏

一、ViewModel 使用指南:三步构建数据保险箱

1. 创建ViewModel子类

kotlin 复制代码
class UserViewModel : ViewModel() {
    private val _userData = MutableLiveData<User>()
    val userData: LiveData<User> = _userData
    
    fun loadUser(userId: String) {
        viewModelScope.launch {
            _userData.value = repository.getUser(userId)
        }
    }
    
    override fun onCleared() {
        // 清理资源
        super.onCleared()
    }
}

2. 在Activity/Fragment中获取实例

kotlin 复制代码
class UserActivity : AppCompatActivity() {
    // 使用委托简化获取
    private val viewModel: UserViewModel by viewModels {
        UserViewModelFactory(provideUserRepository())
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        
        // 观察数据变化
        viewModel.userData.observe(this) { user ->
            updateUI(user)
        }
        
        loadButton.setOnClickListener {
            viewModel.loadUser("123")
        }
    }
}

3. 自定义Factory实现依赖注入

kotlin 复制代码
class UserViewModelFactory(
    private val repository: UserRepository
) : ViewModelProvider.Factory {
    
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return UserViewModel(repository) as T
    }
}

二、ViewModel 工作原理:数据存活的魔法

核心三剑客

  1. ViewModelStoreOwner:Activity/Fragment作为"房东"
  2. ViewModelStore:房东持有的"保险箱仓库"(内部为Map结构)
  3. ViewModel:存放在仓库中的"数据保险箱"

配置更改时的数据保留流程

三、源码亮点解析:精妙设计揭秘

1. ViewModelStore的保存与恢复机制

核心代码

ini 复制代码
// ComponentActivity.java
public ViewModelStore getViewModelStore() {
    if (mViewModelStore == null) {
        // 尝试从上次配置更改恢复
        NonConfigurationInstances nc = getLastNonConfigurationInstance();
        if (nc != null) mViewModelStore = nc.viewModelStore;
        
        if (mViewModelStore == null) mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}

public Object onRetainNonConfigurationInstance() {
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.viewModelStore = mViewModelStore; // 保存仓库
    return nci;
}

设计精妙之处

  • 利用Activity的onRetainNonConfigurationInstance生命周期钩子
  • 通过NonConfigurationInstances实现轻量级状态保存
  • 避免序列化开销,直接传递对象引用

2. Factory模式:解耦的艺术

类关系图

优势

  • 完全解耦ViewModel的构造逻辑
  • 支持依赖注入(构造函数参数传递)
  • 为Hilt等DI框架提供标准接入点

3. 资源清理:mBagOfTags的妙用

自动资源管理流程

scss 复制代码
// ViewModel.java
void clear() {
    for (Object value : mBagOfTags.values()) {
        if (value instanceof Closeable) {
            ((Closeable) value).close(); // 自动关闭资源
        }
    }
    onCleared(); // 开发者清理点
}

应用场景

  • 自动管理协程作用域(viewModelScope)
  • 统一关闭数据库连接、网络请求等资源
  • 避免手动资源释放的遗漏

4. 线程安全:双重校验锁实现

单例保障逻辑

ini 复制代码
ViewModel viewModel = store.get(key);
if (viewModel == null) {
    viewModel = factory.create(modelClass);
    ViewModel existing = store.putIfAbsent(key, viewModel);
    if (existing != null) {
        viewModel = existing; // 使用已存在实例
        viewModel.onCleared(); // 清理新创建的冗余实例
    }
}
return viewModel;

关键点

  • putIfAbsent保证原子操作
  • 防止多线程环境下的重复创建
  • 确保ViewModel实例全局唯一

5. 协程集成:viewModelScope的魔法

实现原理

kotlin 复制代码
val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) return scope
        
        return setTagIfAbsent(JOB_KEY, 
            CloseableCoroutineScope(SupervisorJob()))
    }

internal class CloseableCoroutineScope(...) : Closeable {
    override fun close() { coroutineContext.cancel() } // 自动取消
}

价值

  • 协程生命周期与ViewModel绑定
  • ViewModel销毁时自动取消所有子协程
  • 避免异步操作导致的内存泄漏

四、ViewModel 最佳实践

  1. 职责分离原则

    • ViewModel:数据处理和业务逻辑
    • Activity/Fragment:UI展示和用户交互
    • 永远不要在ViewModel中持有View引用
  2. 资源管理

    kotlin 复制代码
    override fun onCleared() {
        super.onCleared()
        networkRequest?.cancel()
        database.close()
    }
  3. 架构配合

    • 结合LiveData实现数据观察
    • 使用Repository模式管理数据源
    • 通过DataBinding实现数据绑定
相关推荐
烛阴几秒前
从0到1掌握盒子模型:精准控制网页布局的秘诀
前端·javascript·css
前端工作日常3 小时前
我理解的`npm pack` 和 `npm install <local-path>`
前端
李剑一3 小时前
说个多年老前端都不知道的标签正确玩法——q标签
前端
嘉小华4 小时前
大白话讲解 Android屏幕适配相关概念(dp、px 和 dpi)
前端
姑苏洛言4 小时前
在开发跑腿小程序集成地图时,遇到的坑,MapContext.includePoints(Object object)接口无效在组件中使用无效?
前端
奇舞精选4 小时前
Prompt 工程实用技巧:掌握高效 AI 交互核心
前端·openai
Danny_FD4 小时前
React中可有可无的优化-对象类型的使用
前端·javascript
用户757582318554 小时前
混合应用开发:企业降本增效之道——面向2025年移动应用开发趋势的实践路径
前端
P1erce4 小时前
记一次微信小程序分包经历
前端
LeeAt4 小时前
从Promise到async/await的逻辑演进
前端·javascript