随着 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() = _data fun loadData() { // 模拟网络请求 _data.value = "Hello, MVVM!" } }
-
LiveData:
- LiveData 是一种可观察的数据持有者,可以感知生命周期变化,避免内存泄漏。
- View 可以观察 LiveData 的变化,并自动更新 UI。
class MyActivity : AppCompatActivity() { private lateinit var viewModel: MyViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel = ViewModelProvider(this).get(MyViewModel::class.java) viewModel.data.observe(this) { data -> // 更新 UI findViewById<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> <variable name="viewModel" type="com.example.app.MyViewModel" /> </data> <TextView android: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() = _users fun 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: UserViewModel override 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.size class 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> <variable name="viewModel" type="com.example.app.UserViewModel" /> </data> <RecyclerView android: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: UserViewModel override 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 = viewModel binding.lifecycleOwner = this viewModel.loadUsers() } }
- 在
-
示例:
<!-- activity_user.xml --> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.example.app.UserViewModel" /> </data> <RecyclerView android: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() = _users fun 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 @Singleton fun provideUserRepository( remoteDataSource: RemoteUserDataSource, localDataSource: LocalUserDataSource ): UserRepository { return UserRepository(remoteDataSource, localDataSource) } @Provides @Singleton fun provideRemoteUserDataSource(apiService: ApiService): RemoteUserDataSource { return RemoteUserDataSource(apiService) } @Provides @Singleton fun provideLocalUserDataSource(userDao: UserDao): LocalUserDataSource { return LocalUserDataSource(userDao) } }
通过使用 Repository 模式,ViewModel 可以专注于 UI 逻辑和数据处理,而不需要关心数据的具体来源和实现细节。这种分离使得代码更加清晰、可维护,并且易于测试。
作者简介
前腾讯电子签的前端负责人,现 whentimes tech CTO,专注于前端技术的大咖一枚!一路走来,从小屏到大屏,从 Web 到移动,什么前端难题都见过。热衷于用技术打磨产品,带领团队把复杂的事情做到极简,体验做到极致。喜欢探索新技术,也爱分享一些实战经验,帮助大家少走弯路!
温馨提示:可搜老码小张公号联系导师