随着 Android 应用复杂性的增加,采用良好的架构模式变得越来越重要。MVVM(Model-View-ViewModel) 是一种流行的架构模式,旨在将应用的 UI 逻辑、数据和业务逻辑分离,使代码更易于维护、测试和扩展。本章节将深入讲解 MVVM 架构模式的原理、组件、Jetpack 组件(如 ViewModel 和 LiveData)的使用,以及如何在 Android 项目中应用 MVVM 架构。
MVVM 架构模式简介
-
MVVM 的起源:
- MVVM(Model-View-ViewModel)架构模式最初由 Microsoft 提出,用于构建 WPF(Windows Presentation Foundation)和 Silverlight 应用。
- 随着 Android 开发的复杂化,MVVM 被引入到 Android 开发中,并成为主流架构模式之一。
-
MVVM 的核心思想:
- 分离关注点: 将应用的 UI 逻辑、数据和业务逻辑分离,使代码更清晰、易于维护。
- 数据驱动 UI: ViewModel 持有 UI 数据,并暴露数据给 View,View 通过观察数据的变化来更新 UI。
- 可测试性: ViewModel 不依赖于 View,可以独立进行单元测试。
-
MVVM 的优点:
- 代码清晰: 各层职责分明,代码更易于理解和维护。
- 可测试性高: ViewModel 可以独立于 UI 进行单元测试,提高测试覆盖率。
- 可复用性: ViewModel 可以被多个 View 复用,减少代码重复。
- 数据绑定: 通过数据绑定机制,View 可以自动响应数据变化,无需手动更新 UI。
10.2 MVVM 架构模式的组成部分
MVVM 架构模式主要由以下三个部分组成:
-
Model(模型):
- 负责数据的获取、存储和业务逻辑处理。
- 例如,数据库、网络请求、数据模型等。
-
View(视图):
- 负责 UI 的展示和用户交互。
- 例如,Activity, Fragment, Composable 等。
-
ViewModel(视图模型):
- 充当 View 和 Model 之间的桥梁,负责处理 UI 相关的数据和逻辑。
- ViewModel 不直接引用 View,可以独立于 View 进行单元测试。
10.3 Jetpack 组件在 MVVM 中的应用
Jetpack 提供了多个组件,可以帮助我们更好地实现 MVVM 架构:
-
ViewModel:
- ViewModel 是 MVVM 架构中的核心组件,负责存储和管理 UI 相关的数据。
- ViewModel 生命周期与 View 分离,可以在配置变化(例如屏幕旋转)时保留数据。
class MyViewModel : ViewModel() {private val _data = MutableLiveData<String>()val data: LiveData<String> get() = _datafun loadData() {// 模拟网络请求_data.value = "Hello, MVVM!"} }
-
LiveData:
- LiveData 是一种可观察的数据持有者,可以感知生命周期变化,避免内存泄漏。
- View 可以观察 LiveData 的变化,并自动更新 UI。
class MyActivity : AppCompatActivity() {private lateinit var viewModel: MyViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)viewModel = ViewModelProvider(this).get(MyViewModel::class.java)viewModel.data.observe(this) { data ->// 更新 UIfindViewById<TextView>(R.id.textView).text = data}viewModel.loadData()} }
-
Data Binding:
- Data Binding 允许将 UI 组件直接绑定到 ViewModel 的数据,减少样板代码。
- 例如,将 TextView 的文本属性绑定到 ViewModel 的数据。
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="viewModel"type="com.example.app.MyViewModel" /></data><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{viewModel.data}" /> </layout>
-
Room:
- Room 是 Android 官方提供的持久化库,提供了对 SQLite 数据库的抽象层。
- 可以与 ViewModel 结合使用,实现数据的存储和查询。
@Entity data class User(@PrimaryKey val id: Int,val name: String )@Dao interface UserDao {@Query("SELECT * FROM user")fun getAllUsers(): LiveData<List<User>> }@Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() {abstract fun userDao(): UserDao }class MyViewModel(application: Application) : AndroidViewModel(application) {private val db = Room.databaseBuilder(application,AppDatabase::class.java, "database-name").build()val users: LiveData<List<User>> = db.userDao().getAllUsers() }
10.4 MVVM 架构模式的实现步骤
在前面的章节中,我们介绍了 MVVM 架构模式的基本概念和组成部分。接下来,我们将详细介绍如何在 Android 项目中实现 MVVM 架构,包括 ViewModel 的创建与使用、LiveData 的观察、数据绑定以及与 Repository 模式的结合。
10.4.1 创建 ViewModel
ViewModel 是 MVVM 架构的核心组件,负责存储和管理 UI 相关的数据和业务逻辑。ViewModel 不直接引用 View,因此可以独立于 View 进行单元测试。
-
步骤:
- 创建一个继承自
ViewModel
的类。 - 在 ViewModel 中定义 UI 相关的数据和逻辑。
- 使用
LiveData
或StateFlow
暴露数据给 View。
- 创建一个继承自
-
示例:
// UserRepository.kt class UserRepository {fun getUsers(): List<User> {// 模拟网络请求或数据库查询return listOf(User(1, "Alice"), User(2, "Bob"))} }// UserViewModel.kt class UserViewModel(private val repository: UserRepository) : ViewModel() {private val _users = MutableLiveData<List<User>>()val users: LiveData<List<User>> get() = _usersfun loadUsers() {// 模拟数据加载val userList = repository.getUsers()_users.value = userList} }// User.kt data class User(val id: Int, val name: String)
10.4.2 创建 View
View 负责 UI 的展示和用户交互。在 Android 中,View 通常是 Activity 或 Fragment。在 Jetpack Compose 中,View 可以是 Composable 函数。
-
步骤:
- 在 Activity 或 Fragment 中创建 ViewModel 实例。
- 使用
ViewModelProvider
或ViewModel
的构造函数注入 ViewModel。 - 观察 ViewModel 中的 LiveData 数据,并更新 UI。
-
示例:
// UserActivity.kt class UserActivity : AppCompatActivity() {private lateinit var viewModel: UserViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_user)// 创建 ViewModel 实例viewModel = ViewModelProvider(this, ViewModelFactory(UserRepository())).get(UserViewModel::class.java)// 观察 LiveData 数据viewModel.users.observe(this) { users ->// 更新 UI,例如使用 RecyclerView 显示用户列表val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)recyclerView.adapter = UserAdapter(users)}// 加载数据viewModel.loadUsers()} }// UserAdapter.kt class UserAdapter(private val users: List<User>) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)return UserViewHolder(view)}override fun onBindViewHolder(holder: UserViewHolder, position: Int) {val user = users[position]holder.bind(user)}override fun getItemCount(): Int = users.sizeclass UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind(user: User) {itemView.findViewById<TextView>(R.id.textViewName).text = user.name}} }
10.4.3 使用 Data Binding
Data Binding 允许将 UI 组件直接绑定到 ViewModel 的数据,减少样板代码,提高代码可读性。
-
步骤:
- 在
build.gradle
文件中启用 Data Binding。android {...buildFeatures {dataBinding true} }
- 在布局文件中使用
<layout>
标签包裹 UI 组件,并定义 ViewModel 变量。<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="viewModel"type="com.example.app.UserViewModel" /></data><RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent"android:adapter="@{viewModel.users}" /> </layout>
- 在 Activity 或 Fragment 中设置布局和数据绑定。
class UserActivity : AppCompatActivity() {private lateinit var viewModel: UserViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val binding: ActivityUserBinding = DataBindingUtil.setContentView(this, R.layout.activity_user)viewModel = ViewModelProvider(this, ViewModelFactory(UserRepository())).get(UserViewModel::class.java)binding.viewModel = viewModelbinding.lifecycleOwner = thisviewModel.loadUsers()} }
- 在
-
示例:
<!-- activity_user.xml --> <layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="viewModel"type="com.example.app.UserViewModel" /></data><RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent"android:adapter="@{viewModel.users}" /> </layout>
// UserViewModel.kt class UserViewModel(private val repository: UserRepository) : ViewModel() {val users: LiveData<List<User>> = MutableLiveData()fun loadUsers() {val userList = repository.getUsers()(users as MutableLiveData).value = userList} }
10.4.4 使用 Repository 模式
Repository 模式 是 MVVM 架构的重要组成部分,用于抽象数据源,提供统一的接口给 ViewModel。Repository 模式可以有效地管理数据来源,包括网络请求、数据库、文件存储等,使 ViewModel 更加专注于 UI 逻辑和数据处理,而无需关心数据的具体来源。
10.4.4.1 Repository 模式的优势
- 数据来源抽象: Repository 模式将数据来源抽象出来,使 ViewModel 不需要关心数据的具体来源(如网络、数据库等)。
- 数据缓存: 可以通过 Repository 实现数据的缓存机制,提高应用性能。
- 单一职责: 每个 Repository 类只负责特定的数据源,遵循单一职责原则。
- 可测试性: Repository 可以独立于 ViewModel 进行单元测试,提高代码的可测试性。
10.4.4.2 实现 Repository 模式
以下是一个典型的 Repository 模式实现步骤:
-
定义数据模型:
- 定义数据模型类,例如
User
。
data class User(val id: Int, val name: String)
- 定义数据模型类,例如
-
定义数据源接口:
- 定义数据源接口,例如
UserDataSource
,用于获取用户数据。
interface UserDataSource {suspend fun getUsers(): List<User> }
- 定义数据源接口,例如
-
实现具体的数据源:
- 实现网络数据源,例如
RemoteUserDataSource
,通过 API 获取用户数据。
class RemoteUserDataSource(private val apiService: ApiService) : UserDataSource {override suspend fun getUsers(): List<User> {return apiService.fetchUsers()} }
- 实现本地数据源,例如
LocalUserDataSource
,从数据库获取用户数据。
class LocalUserDataSource(private val userDao: UserDao) : UserDataSource {override suspend fun getUsers(): List<User> {return userDao.getAllUsers()} }
- 实现网络数据源,例如
-
实现 Repository 类:
- 创建
UserRepository
类,负责管理数据源。 - 可以根据需要实现缓存机制,例如先从网络获取数据,再保存到数据库。
class UserRepository(private val remoteDataSource: RemoteUserDataSource, private val localDataSource: LocalUserDataSource) {suspend fun getUsers(forceRefresh: Boolean = false): List<User> {if (forceRefresh) {// 从网络获取数据val users = remoteDataSource.getUsers()// 保存到数据库localDataSource.saveUsers(users)return users} else {// 先从数据库获取数据val users = localDataSource.getUsers()if (users.isNotEmpty()) {return users} else {// 如果数据库为空,则从网络获取数据val usersFromNetwork = remoteDataSource.getUsers()// 保存到数据库localDataSource.saveUsers(usersFromNetwork)return usersFromNetwork}}} }
- 创建
-
在 ViewModel 中使用 Repository:
- 在 ViewModel 中注入
UserRepository
实例,并调用其方法获取数据。
class UserViewModel(private val repository: UserRepository) : ViewModel() {private val _users = MutableLiveData<List<User>>()val users: LiveData<List<User>> get() = _usersfun loadUsers(forceRefresh: Boolean = false) {viewModelScope.launch {try {val users = repository.getUsers(forceRefresh)_users.postValue(users)} catch (e: Exception) {// 处理异常,例如显示错误信息}}} }
- 在 ViewModel 中注入
-
依赖注入:
- 使用依赖注入框架(例如 Hilt)来管理 Repository 和数据源的依赖关系。
@Module @InstallIn(SingletonComponent::class) object RepositoryModule {@Provides@Singletonfun provideUserRepository(remoteDataSource: RemoteUserDataSource,localDataSource: LocalUserDataSource): UserRepository {return UserRepository(remoteDataSource, localDataSource)}@Provides@Singletonfun provideRemoteUserDataSource(apiService: ApiService): RemoteUserDataSource {return RemoteUserDataSource(apiService)}@Provides@Singletonfun provideLocalUserDataSource(userDao: UserDao): LocalUserDataSource {return LocalUserDataSource(userDao)} }
通过使用 Repository 模式,ViewModel 可以专注于 UI 逻辑和数据处理,而不需要关心数据的具体来源和实现细节。这种分离使得代码更加清晰、可维护,并且易于测试。
作者简介
前腾讯电子签的前端负责人,现 whentimes tech CTO,专注于前端技术的大咖一枚!一路走来,从小屏到大屏,从 Web 到移动,什么前端难题都见过。热衷于用技术打磨产品,带领团队把复杂的事情做到极简,体验做到极致。喜欢探索新技术,也爱分享一些实战经验,帮助大家少走弯路!
温馨提示:可搜老码小张公号联系导师