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实现数据绑定
相关推荐
林烈涛12 分钟前
js判断变量是数组还是对象
开发语言·前端·javascript
Komorebi_999942 分钟前
Unocss
开发语言·前端
goto_w2 小时前
前端实现复杂的Excel导出
前端·excel
Baklib梅梅2 小时前
2025文档管理软件推荐:效率、安全与协作全解析
前端·ruby on rails·前端框架·ruby
卷Java2 小时前
小程序前端功能更新说明
java·前端·spring boot·微信小程序·小程序·uni-app
FogLetter2 小时前
前端性能救星:虚拟列表原理与实现,让你的10万条数据流畅如丝!
前端·性能优化
我是天龙_绍2 小时前
前端驼峰,后端下划线,问:如何统一?
前端
知识分享小能手2 小时前
微信小程序入门学习教程,从入门到精通,微信小程序常用API(下)——知识点详解 + 案例实战(5)
前端·javascript·学习·微信小程序·小程序·vue·前端开发
code_YuJun2 小时前
nginx 配置相关
前端·nginx