安卓MVC/MVP/MVVM架构

643 阅读5分钟

一.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层来进行一些业务逻辑的处理,如转换数据。

image.png

下面是示例代码,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可以更专注于原本的的职责了。

参考资料

  1. Android App的设计架构:MVC,MVP,MVVM与架构经验谈
  2. 《Android进阶之光》刘望舒
  3. What are MVP and MVC and what is the difference?
  4. What are MVP-Passive View and MVP-Supervising controller
  5. MVC Architectural Pattern in Android – Part 1
  6. Android MVVM Design Pattern