在Android开发里,随着业务膨胀,Activity和Fragment往往会变成容纳一切的大容器,网络请求、数据库读写、界面刷新全堆在一起。MVVM架构结合Jetpack组件提供了一套官方推荐的分层方案,让界面层只关心展示,领域层与数据层各司其职。ViewModel借助生命周期感知能力在配置变更后保留数据,LiveData以可观察数据结构向界面推送变化,Room把对象映射为SQLite表,Navigation用可视化图谱管理路由。这种组合不仅减少模板代码,还使单元测试可以脱离Android环境运行。

MVVM与Jetpack的核心角色划分
MVVM全称Model-View-ViewModel,核心目标是把视图状态从视图控件中抽离。View层通常是Activity、Fragment以及XML布局,它只负责把数据画出来,并把用户操作抛给ViewModel。ViewModel层持有界面需要的 LiveData 或 StateFlow,不直接引用任何 View 对象,因此能在屏幕旋转时存活,避免重复请求。Model层在Jetpack体系下多由Repository构成,内部组合Room数据库、Retrofit网络源,向上提供统一数据接口。
Jetpack并不是单一库,而是谷歌维护的一系列支持库集合。其中与MVVM强相关的有 ViewModel、LiveData、Room、DataBinding或ViewBinding、Navigation。ViewModel由androidx.lifecycle.ViewModel提供,系统会在配置变更时保留其实例;LiveData是一种生命周期感知的可观察数据持有者,只有在活跃生命周期状态才会通知观察者,天然防止内存泄漏。Room通过注解把实体类与DAO绑定,编译期生成实现,比手写SQLiteHelper更安全。
实际项目中常犯的错误是把ViewModel写成万能类,既拼装数据又直接调Context弹Toast。正确做法是ViewModel只输出纯数据状态,Context相关动作交由View层响应。另外,Repository不应散落在ViewModel中,独立成层后方便切换数据源,例如先从Room取缓存,再触发网络刷新,这种责任切分是清晰结构的关键。
用ViewModel与LiveData搭建数据通道
构建一个用户资料页面,先定义UI状态类,把姓名、头像、加载中标志封装进去。ViewModel暴露一个LiveData<UserUiState>,View层注册观察者,数据一旦变化就刷新控件。这样即使网络返回慢,旋转屏幕也不会丢失已加载内容,因为ViewModel实例没被销毁。相比早期用Bundle保存字段,这种方式代码量少且不易出错。
下面示例展示ViewModel如何组合Repository并向界面提供状态。注意ViewModel构造通过ViewModelProvider或Hilt注入,不直接new。LiveData的switchMap可根据参数动态切换数据源,适合搜索场景。
// UserViewModel.java
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.Transformations;
public class UserViewModel extends ViewModel {
private UserRepository repository = new UserRepository();
private MutableLiveData<String> userId = new MutableLiveData<>();
// 对外暴露不可变LiveData
public LiveData<UserUiState> userState = Transformations.switchMap(userId, id ->
repository.loadUser(id)
);
public void setUserId(String id) {
userId.setValue(id);
}
}
上面代码中Transformations.switchMap在userId改变时自动重建数据源,界面无需手动取消旧请求。若使用Kotlin,可改用StateFlow配合viewModelScope,协程取消更精确。无论哪种,原则都是View不主动拉数据,而是监听推送,这消除了大量回调嵌套,也让ViewModel可独立跑测试,只要伪造Repository即可验证状态机。
Room与Navigation完善数据与路由层
单纯内存数据在进程被杀后就会丢失,Room让本地持久化变得声明式。定义@Entity表与@Dao接口,编译期生成访问代码,支持协程挂起函数与RxJava返回类型。Repository内部优先查Room,再决定要不要走网络,这种离线优先策略提升用户体验,也减轻服务器压力。与ViewModel结合时,DAO返回的LiveData<List<User>>可直接作为数据源,插入后自动通知界面。
Navigation组件把页面跳转抽象成导航图,XML里声明<fragment>与<action>,代码只需findNavController().navigate(R.id.action_a_to_b)。它自动处理返回栈,配合ViewModel共享数据区域,避免用Intent硬传对象。下例给出Dao与导航调用的简单形态:
// UserDao.kt
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.lifecycle.LiveData
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE uid = :id")
fun getUserById(id: String): LiveData<UserEntity>
@Insert
suspend fun insert(user: UserEntity)
}
// 在Fragment中跳转
import androidx.navigation.findNavController
binding.btnDetail.setOnClickListener {
it.findNavController().navigate(R.id.go_detail)
}
把Room与Navigation纳入MVVM后,项目目录通常分为ui、domain、data三层,ui含Activity与ViewModel,data含Room与网络,domain放用例与模型。新成员按包名就能定位逻辑,测试时分别对Repository和ViewModel写用例,界面层只做交互验证。这种结构在中等以上规模应用里明显优于把所有代码塞进Activity,长期维护成本更低,也更容易做模块化拆分。
MVVMJetpackAndroid_Architecture修改时间:2026-08-15 21:28:32