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实现数据绑定
相关推荐
Autumn_ing8 小时前
Design.md生成UI怎么验收?举例运营后台的颜色、状态和数据核对
前端·ui·ai·设计系统·paico·ai生成ui·design.md
keyipatience8 小时前
JavaScript学习(1)书写位置,变量、数据类型、运算符
开发语言·前端·javascript·css·学习
朱涛的自习室9 小时前
我给 Gemini 4 和 DeepSeek 搞了套「末位淘汰」,AI 干活会更猛吗?(Day 3)
android·前端·后端
java1234_小锋11 小时前
【技术专题】HTML5 网页技术 - HTML5简介 & HTML5 文档结构
前端·html·html5
鶴哥只手遮天11 小时前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 1 章 项目全景:monorepo 架构与工程化体系
前端·vue.js·开源
流星。。方12 小时前
小团队官网SEO落地:前端语义化与静态生成的工程实践
前端·状态模式
用户5304297657912 小时前
盘点危化品搬运诊断的6类工具与自处理边界判断
前端
curtain12 小时前
从古法编程到 Agent:我入行这两年,AI 改变了什么
前端·openai
zyfts12 小时前
10分钟自制Codex钉钉通知Skill:AI干完活自动钉钉提醒你
前端
大孜然12 小时前
孜然QQ频道自动化程序机器人配置教程
前端