Jetpack?ViewModel!!!

做 Android 几乎每天都在和 ViewModel 打交道。作为 Jetpack 架构的核心基石组件,ViewModel 早已脱离"可选工具"的范畴,成为 MVVM 架构开发的标配。

但很多开发者只会简单套用模板代码,并不清楚它的底层原理、生命周期逻辑,甚至经常踩内存泄漏、数据丢失、实例创建错误的坑。

今天这篇博客,从零到一拆解 ViewModel,带你搞懂「为什么要用、怎么用、进阶怎么玩、哪些坑绝对不能踩」,看完直接彻底吃透!

一、ViewModel 是什么?核心定位

官方定义:ViewModel 是用于存储和管理界面相关数据的架构组件,具备生命周期感知能力,可在页面配置变更时自动保留数据

简单来说,ViewModel 的核心职责只有两个:

  1. 接管 UI 数据 :把 Activity/Fragment 中的数据、业务逻辑剥离出来,实现视图与数据解耦

  2. 持久化临时数据:屏幕旋转、系统语言切换、暗黑模式切换等配置变更导致页面重建时,自动保留数据,无需手动保存恢复

它的出现,完美解决了传统开发的两大痛点:

  • 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,必须吃透它的生命周期,这是区别于普通类的关键。

生命周期流程

  1. 创建时机:页面首次加载,通过 ViewModelProvider 获取实例时创建

  2. 存活阶段:页面正常运行、多次配置重建,ViewModel 始终存活,实例唯一

  3. 销毁时机:页面真正销毁(finish、退出页面、Fragment 彻底移除)时触发

  4. 回收回调 :销毁前自动调用 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 调用

三层架构完全解耦,各司其职,让项目结构更规范,适配大型项目迭代维护。

八、全文总结

  1. ViewModel 核心价值:生命周期独立、保活页面数据、解耦视图与业务,是 Android 现代化开发的必备组件;

  2. 标准使用方式:通过 ViewModelProvider 获取实例,配合 LiveData/StateFlow 实现数据驱动 UI;

  3. 带参初始化必须用 Factory 工厂类,解决自定义参数注入问题;

  4. 核心避坑:不手动创建实例、不持有 UI 引用、只用 viewModelScope、精准控制作用域;

  5. 完美适配 MVVM 架构,是项目规范化、轻量化、易维护的核心基础。

掌握 ViewModel 的原理和规范用法,不仅能解决日常开发的 90% 页面状态问题,更能帮你搭建规范的项目架构,告别臃肿、混乱的传统代码!

相关推荐
白远山1 小时前
智慧场馆解决方案软件开发实战:从架构设计到落地部署指南
java·开发语言·架构·需求分析
IT笔记1 小时前
Rust 迭代器多级链式调用执行顺序笔记
开发语言·笔记·rust
Godikov1 小时前
Android 工控终端实战:从秒级卡顿到毫秒响应,SQLite/LitePal 性能优化全记录
android
雪落漂泊2 小时前
C++11(上)
开发语言·c++
春涧草茶2 小时前
慢就是快12-2三种访问权限|getter与setter|魔法方法
开发语言·python
無限進步D2 小时前
Java Web 前端 简介
java·开发语言·前端·css·html·css3·web
qq_401700413 小时前
Qt QUrl 详解与代码示例
开发语言·数据库·qt
小灰灰搞电子3 小时前
Rust Attribute(属性标记)完整整理
开发语言·后端·rust
tedcloud1233 小时前
God‘s Eye View 怎么搭建?在云服务器上部署一个实时 3D 地球可视化平台
linux·服务器·开发语言·后端·rust