前言
MVC, MVP, MVVM 三种架构思想, 我想每个开发者应该都或多或少接触过, 本篇主要阐述下个人对MVVM的理解, 以及其在Android工程中的应用和实践
MVP
首先, 我们理解下在过去的几年内, 常用到的MVP(Model - View - Presenter)架构思想.
MVP是由MVC演进而来, 整体架构可以分为三层
- Model(数据层) 在良好的分层项目中, 它主要负责API数据, 缓存数据等的管理
- View(视图层) 负责视图的显示与处理, 在Android中, 常常是通过Activity/Fragment来处理相关实现, 并持有Presenter的引用
- Presenter 负责连接
Model层和View层, 负责业务逻辑的处理. 在Android中, 它需要持有Modle与View的引用,View通过调用Presenter的方法, 去执行相应的业务逻辑处理, 而Presenter的内部通过获取Model的数据并将其转换提供给View, 另外View的交互的触发, 通常是由Presenter来决定的.
由此可见, 我们在以MVP架构来开发的时候, 常常是需要通过UI和事件为驱动去执行相应的逻辑变更, 同时, Presenter以及View在触发到相应的交互变更时, 需要考虑到Activity或者Fragment的生命周期
MVVM
我们来看下MVVM模式的主要成员
- Model Model与MVP模式中的Model没有什么区别, 仍然负责相关数据处理
- View 仍然负责视图交互的处理, 订阅
ViewModel的相关数据流并触发相应的交互事件, - ViewModel 负责业务逻辑的同时, 公开与
View的相关观测事件数据流
可以发现MVP和MVVM在分层上, 还是很相似的, View均需要抽象视图体现, Presenter与ViewModel均需要处理业务逻辑, 比较明显的不同是, Presenter仍需要持有View以此直接指定相应的交互体现, 而ViewModel是通过发布View绑定的事件流, 由View自行观测并触发交互, 它并不关心是谁观测这个事件流或做出怎样的交互, 而因此, 在Android中, ViewModel不应该再关注View的生命周期
相关的示意图可看下图

然后我们看下MVVM在Android工程中的推荐分层实践
MVVM核心思想
在Google推荐的官方文档中, MVVM区别于其他架构, 它的核心思想是 数据驱动UI(即模型驱动视图), 这里的数据, 官方比较推荐的做法是, 将持久化数据设置为我们的单一可信来源, 如果配合JetPack来开发, 那么梳理下来的流程,就应该是
- Room提供数据单一可信任来源, View通过VM拿到相关数据, 并对它进行观测
- 而当View需要通过VM调用请求接口的时候, VM通知给到M, M进行请求并将数据更新进Room, 由此触发到View的更新
MVVM 工程内实践
但是在实际的开发工程中, 我们往往是没有一个明确的M界限, 常见的开发场景是将数据相关逻辑均写在VM层(或者P层), 在MVVMArchDemo工程中, 我们摒弃了通过DataBind来实现V对VM的绑定, DI的使用, 以及基于我们当前对数据的操作, 摒弃了对Repostory分层的处理, 这样我们更加聚焦在如何掌握数据驱动UI的思想
下例我们看一个常见的, 具备分页功能的列表页面的实现方式
这里我们有几个注意的点
- 对应列表页面, 他需要观测的数据是什么?
- 对于页面来说, 他需要观测的数据应是当前所有展示的数据, 而不是每页请求后的单页数据, 如果通过VM获取到每页数据去进行订阅, 那是毫无意义的. 由此在Adapter中, 我们应该摒弃掉过往针对单页数据进行通知变更的行为逻辑, 改为使用DiffUtil去观测数据的变更, 以此通知到相应item行为触发
- VM怎么通知到View触发下拉刷新/加载等loading的动效?
- 由于VM的生命周期独立于Activity/Fragment的, 所以VM本身是不宜持有Activity/Fragment, 通过暴露响应的观测数据, 给到Activity/Fragment进行订阅监测, 以此达到触发loading等的view变更
- 暴露给V的LiveData
- MutableLiveData是可变的, 就是交由使用者去发送数据, 而V不不应该保留对数据变更的权限, 由此VM暴露的LiveData是需要处理一下
1 | class HomeFragment : BaseFragment() { |
VM层的处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87class HomeViewModel(application: Application) : BaseViewModel(application) {
val _result = MutableLiveData<ArrayList<ArticleEntity>>()
val result : LiveData<ArrayList<ArticleEntity>> = _result
private val loadDataHandler = LoadDataHandler(getApplication<BaseApplication>().mRetrofit.create(ApiService::class.java),
_result)
val loadingStatus = loadDataHandler.loadingStatus
/**
* 刷新
*/
fun refresh(){
loadDataHandler.loadData(true)
}
/**
* 加载更多
*/
fun loadMore(){
loadDataHandler.loadData(false)
}
private class LoadDataHandler(
val apiService: ApiService,
val _result: MutableLiveData<ArrayList<ArticleEntity>>
) :
Observer<ApiResponse<BaseEntity<PageEntity<ArticleEntity>>>> {
private var mPageData : LiveData<ApiResponse<BaseEntity<PageEntity<ArticleEntity>>>>? = null
private var pageNo = 0
val loadingStatus = MutableLiveData<LoadingState>()
init {
reset()
}
fun reset(){
pageNo = 0
}
fun loadData(shouldReset : Boolean){
if(shouldReset){
reset()
}
mPageData = apiService.fetchArticleList(pageNo)
mPageData?.observeForever(this)
}
fun unregister(){
mPageData?.removeObserver(this)
mPageData = null
}
override fun onChanged(t: ApiResponse<BaseEntity<PageEntity<ArticleEntity>>>?) {
when(t){
is ApiSuccessResponse -> {
pageNo = t.body.data.curPage
if(1 == pageNo){
_result.value = t.body.data.datas
}else{
_result += t.body.data.datas
}
loadingStatus.value = LoadingState(true, t.body.data.curPage < t.body.data.pageCount)
unregister()
}
is ApiErrorResponse -> {
loadingStatus.value = LoadingState(true, false, t.errorMsg)
unregister()
}
is ApiEmptyResponse -> {
unregister()
}
}
}
}
data class LoadingState(
var loadingSusscess : Boolean,
var hasMore : Boolean,
var errorMsg : String? = null
)
}
更多思考与讨论
- 协程的使用
- Repostory的必要性
- DI与DataBinding的必要性