做 Android 几乎每天都在和 ViewModel 打交道。作为 Jetpack 架构的核心基石组件,ViewModel 早已脱离"可选工具"的范畴,成为 MVVM 架构开发的标配。
但很多开发者只会简单套用模板代码,并不清楚它的底层原理、生命周期逻辑,甚至经常踩内存泄漏、数据丢失、实例创建错误的坑。
今天这篇博客,从零到一拆解 ViewModel,带你搞懂「为什么要用、怎么用、进阶怎么玩、哪些坑绝对不能踩」,看完直接彻底吃透!
一、ViewModel 是什么?核心定位
官方定义:ViewModel 是用于存储和管理界面相关数据的架构组件,具备生命周期感知能力,可在页面配置变更时自动保留数据。
简单来说,ViewModel 的核心职责只有两个:
-
接管 UI 数据 :把 Activity/Fragment 中的数据、业务逻辑剥离出来,实现视图与数据解耦
-
持久化临时数据:屏幕旋转、系统语言切换、暗黑模式切换等配置变更导致页面重建时,自动保留数据,无需手动保存恢复
它的出现,完美解决了传统开发的两大痛点:
-
Activity 职责过重,既管视图又管数据,代码臃肿难维护
-
页面重建数据丢失,需要重写 onSaveInstanceState 冗余代码
二、为什么必须用 ViewModel?核心优势
1. 配置变更自动保活数据
Android 系统触发屏幕旋转、分辨率变更、主题切换等操作时,Activity/Fragment 会销毁并重建。传统写法中,页面内的变量数据会全部重置,需要手动缓存恢复。
而 ViewModel 的生命周期独立于视图,页面重建时不会被销毁,数据全程保留,极大简化了状态保存逻辑。
2. 彻底解耦,符合单一职责
View(Activity/Fragment)只负责:视图初始化、页面跳转、用户交互监听、UI 刷新。
ViewModel 只负责:数据存储、业务逻辑处理、网络请求、数据状态更新。
职责拆分清晰,代码可读性、可维护性、可测试性大幅提升,适配团队协作开发。
3. 天然支持协程,适配异步任务
ViewModel 内置 viewModelScope协程作用域,专门用于处理耗时异步操作(网络请求、数据库读写)。页面销毁时,作用域自动取消,杜绝异步任务空指针、内存泄漏问题。
4. Fragment 数据共享神器
多个 Fragment 依附同一个 Activity 时,通过 Activity 作用域的 ViewModel,可轻松实现跨 Fragment 数据共享,无需繁琐的接口回调、Bundle 传值。
三、ViewModel 生命周期(核心重点)
想要用好 ViewModel,必须吃透它的生命周期,这是区别于普通类的关键。
生命周期流程
-
创建时机:页面首次加载,通过 ViewModelProvider 获取实例时创建
-
存活阶段:页面正常运行、多次配置重建,ViewModel 始终存活,实例唯一
-
销毁时机:页面真正销毁(finish、退出页面、Fragment 彻底移除)时触发
-
回收回调 :销毁前自动调用
onCleared(),可手动释放资源、取消任务
核心结论:ViewModel 的生命周期 > Activity/Fragment 视图生命周期,仅跟随页面真正销毁而终止。
四、零基础实战:标准使用流程(Kotlin)
以最常用的计数器案例,手把手演示 ViewModel 标准用法,包含数据监听、状态更新、生命周期管理。
步骤1:创建 ViewModel 类
继承 ViewModel 父类,存储 UI 状态、编写业务逻辑,配合 LiveData/StateFlow 实现可观察数据。
Kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
class CounterViewModel : ViewModel() {
// 私有可变数据,禁止外部修改
private val _count = MutableLiveData<Int>(0)
// 对外暴露不可变数据,仅供观察
val count: LiveData<Int> = _count
// 业务逻辑:数据自增
fun addCount() {
val current = _count.value ?: 0
_count.value = current + 1
}
// 页面销毁时资源释放(可选)
override fun onCleared() {
super.onCleared()
// 取消网络请求、关闭流、释放资源等
}
}
步骤2:页面中获取 ViewModel 并观察数据
重点禁忌 :绝对不能直接 new CounterViewModel() 创建实例!必须通过 ViewModelProvider 获取,保证生命周期托管。
Kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 标准获取ViewModel实例
val viewModel = ViewModelProvider(this)[CounterViewModel::class.java]
val tvCount = findViewById<TextView>(R.id.tv_count)
val btnAdd = findViewById<Button>(R.id.btn_add)
// 观察数据变化,自动刷新UI
viewModel.count.observe(this) { count ->
tvCount.text = "当前计数:$count"
}
// 触发业务逻辑
btnAdd.setOnClickListener {
viewModel.addCount()
}
}
}
效果验证
点击按钮计数增加,旋转屏幕重建页面,计数数据不会丢失,这就是 ViewModel 的核心能力。
五、进阶用法:带参数 ViewModel(Factory 工厂)
日常开发中,ViewModel 经常需要传入参数(用户ID、仓库对象、数据库实例等),默认无参构造无法满足需求,需要借助 ViewModelProvider.Factory 实现带参初始化。
步骤1:编写带参 ViewModel
Kotlin
class UserViewModel(private val userId: String) : ViewModel() {
// 可使用传入的userId执行业务逻辑
fun getUserInfo() {
// 网络请求获取对应用户数据
}
}
步骤2:创建 Factory 工厂类
Kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class UserViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
return UserViewModel(userId) as T
}
throw IllegalArgumentException("未知的ViewModel类型")
}
}
步骤3:页面中通过 Factory 获取实例
Kotlin
val userId = "10086"
val viewModel = ViewModelProvider(this, UserViewModelFactory(userId))[UserViewModel::class.java]
六、ViewModel 核心禁忌与避坑指南(必看)
很多内存泄漏、数据错乱的问题,都是因为乱用 ViewModel 导致的,以下 4 条规则务必遵守。
1. 禁止直接 new ViewModel()
手动 new 出来的实例,无法被系统生命周期托管,页面重建会重复创建实例,数据丢失,且无法自动释放资源。必须统一通过 ViewModelProvider 获取实例。
2. ViewModel 绝对不能持有 UI 引用
严禁在 ViewModel 中持有 Activity、Fragment、View、Context 引用!
ViewModel 生命周期比 View 长,页面销毁后 ViewModel 仍存活,持有 UI 引用会造成严重内存泄漏。如需上下文,统一使用 ApplicationContext。
3. 异步任务必须用 viewModelScope
不要在 ViewModel 中使用 GlobalScope、自定义协程,页面销毁时无法自动取消,会导致后台任务持续执行,引发崩溃和内存泄漏。
viewModelScope 是专属作用域,页面销毁自动取消所有任务,安全可靠。
4. 区分 ViewModel 作用域
-
传入 this(Activity):全局页面共享,适配多 Fragment 数据互通
-
传入 this(Fragment):仅当前 Fragment 私有,Fragment 销毁即回收
作用域用错,会导致数据复用错乱、页面残留旧数据问题。
七、ViewModel 与 MVVM 架构的适配
ViewModel 是 MVVM 架构的核心中枢,完整分层逻辑:
-
View 层:Activity/Fragment,只负责 UI 展示、用户交互
-
ViewModel 层:中转数据、处理业务逻辑、调用仓库层接口,不接触任何 UI
-
Repository 层:统一处理数据源(网络/本地数据库/缓存),供 ViewModel 调用
三层架构完全解耦,各司其职,让项目结构更规范,适配大型项目迭代维护。
八、全文总结
-
ViewModel 核心价值:生命周期独立、保活页面数据、解耦视图与业务,是 Android 现代化开发的必备组件;
-
标准使用方式:通过 ViewModelProvider 获取实例,配合 LiveData/StateFlow 实现数据驱动 UI;
-
带参初始化必须用 Factory 工厂类,解决自定义参数注入问题;
-
核心避坑:不手动创建实例、不持有 UI 引用、只用 viewModelScope、精准控制作用域;
-
完美适配 MVVM 架构,是项目规范化、轻量化、易维护的核心基础。
掌握 ViewModel 的原理和规范用法,不仅能解决日常开发的 90% 页面状态问题,更能帮你搭建规范的项目架构,告别臃肿、混乱的传统代码!