一.MVC
1.1 简介
在MVC(Model-View-Controller)架构中,核心业务逻辑和数据结构被封装在Model层,其负责数据的存储、检索、更新等操作。
View层是用户界面,主要用于数据的展现和用户交互,在Android应用中,这通常通过XML布局文件来实现。
Controller层充当Model和View之间的桥梁,负责协调二者之间的交互。在Android中,这一角色通常由Activity或Fragment来扮演。它接收View层的用户输入指令,调用Model层的数据处理功能,然后将结果返回给View层来更新用户界面。
1.2 存在的问题
MVC架构有个问题是
- 在安卓系统中,Activity/Fragment类主要职责是加载布局,初始化界面,与用户交互
- 在MVC结构中,除了与数据相关的代码在Model层之外,剩余的代码几乎全在Controller,也就是Activity/Fragment
可以看到,Activity/Fragment不仅要负责UI,还要负责部分业务逻辑,这导致:
- 在大型项目中Activity/Fragment变得臃肿不堪,不利于维护。
- View层和Controller层过于耦合。
二.MVP
2.1简介
与MVC架构相比,MVP架构将Activity类中的逻辑代码(将Model层获取到的数据转化为适合View展示的形式)移动至Presenter角色中,Activity则由原来Controller角色变成View角色。
2.2解决了什么问题
- 实现单一职责原则。MVP架构中,Activity/Fragment只负责UI。
2.3存在什么问题
-
内存泄漏。在MVP架构中Presenter会持有View的引用,这可能导致View无法被回收从而造成内存泄漏。
-
数据丢失。当Activity配置发生改变时(如屏幕旋转),Activity会触发重新构建的流程,此时Presenter会重新实例化,在配置改变前存储在Presenter的数据则会丢失。
第一个问题比较好解决,引入livedata/flow就可以避免Presenter持有View引用。
第二个问题也有解决方案,如在Activity::onSaveInstanceState()保存数据,Activity::onCreate()恢复数据,但面对复杂的数据时,这解决方案不是很优雅。
鉴于上述两个问题,MVVM应运而生。
三.MVVM
MVVM的全称是Model-View-ViewModel,ViewModel负责Ui状态的存储和管理。
ViewModel如何实现
-
ViewModel应该继承于Jetpack的ViewModel。
import androidx.lifecycle.ViewModel class MainViewModel : ViewModel(){ ... } -
使用以下方式来实例化ViewModel
val viewModel=ViewModelProvider(this).get(MainViewModel::class.java)
ViewModel相比于Presenter解决了什么问题
-
提供了生命周期管理。
Jetpack的ViewModel生命周期如下图所示。
ViewModel释放资源的操作可以通过复写ViewModel::onCleared()方法来实现。
-
数据持久性。
如上图所示,当配置发生改变(如屏幕发生旋转)时,Activity会进行重新实例化,这时ViewModel并不会重新实例化,配置发生改变前存储在ViewModel的数据就不会丢失。
原理不难,第一次使用ViewModel时将其缓存起来,第二次则不重新实例化,使用缓存的,更具体的可以看ViewModelProvider::get()方法。
-
减少了内存泄漏的可能性。
按照MVVM规范,ViewModel不会持有View的引用,而是View通过观察ViewModel的数据(如livedata)实时更新界面。
四.Model层
Model层主要负责数据相关操作。
如下图所示,Model层可以划分为Repository层,DataSource。
Repository
Repository的职责是
- 决策数据的获取策略。如优先从哪个数据源获取数据。
interface UserRepository {
suspend fun getUser(userId: Int): User
}
// 实现Repository
class UserRepositoryImpl(
private val localDataSource: LocalDataSource, private val remoteDataSource: RemoteDataSource
) : UserRepository {
override suspend fun getUser(userId: Int): User {
// 优先从本地数据源获取数据
localDataSource.getUser(userId)?.let {
return it
}
// 本地没有数据,从远程数据源获取
val user = remoteDataSource.fetchUser(userId)
// 将远程获取的数据保存到本地
localDataSource.saveUser(user)
return user
}
}
为什么这么设计
-
单一职责。Repository只负责获取数据。
-
给外部提供获取数据的抽象接口。外部不需要关心数据是从本地获取还是从云端获取的。
DataSource
DataSource为数据来源,有两种
-
本地数据源,即数据库。
class LocalDataSource(private val userDao: UserDao) { fun getUser(userId: Int): User? = userDao.getUserById(userId) fun saveUser(user: User) = userDao.insertUser(user) }通常使用Jetpack Room来实现从数据库获取数据的操作。
-
云端数据源,即网络服务。
// Remote DataSource实现 class RemoteDataSource(private val userService: UserApiService) { suspend fun fetchUser(userId: Int): User = userService.getUserById(userId).body()!! }通常使用Retrofit来实现从网络获取数据的操作。
进阶操作 Service层
Model层的设计中,还可以在Repository层上面再增加一层Service层来进行一些业务逻辑的处理,如转换数据。
下面是示例代码,UserService将从Repository层获取到的用户数据转换为用户详情。
class UserService(private val userRepository: UserRepository) {
// 获取用户详细信息,并进行一些业务逻辑处理
suspend fun getUserDetails(userId: Int): UserDetails {
val user = userRepository.getUser(userId) ?: throw Exception("User not found")
// 执行一些业务逻辑,例如计算年龄段
val ageCategory = when {
user.age < 18 -> "Youth"
user.age in 18..64 -> "Adult"
else -> "Senior"
}
// 格式化用户名
val formattedName = "${user.firstName} ${user.lastName}".toUpperCase()
// 创建并返回处理过的用户详情
return UserDetails(
userId = user.userId,
fullName = formattedName,
ageCategory = ageCategory,
email = user.email
)
}
}
// 用户详细信息数据类
data class UserDetails(
val userId: Int,
val fullName: String,
val ageCategory: String,
val email: String
)
解决了什么问题
Service层的出现将从Model获取到的数据转换为UI适合展示的形式这个一职责从ViewModel/Presenter中剥离 出来了,ViewModel/Presenter可以更专注于原本的的职责了。