前言
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层), 在MVVMArch
Demo工程中, 我们摒弃了通过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的必要性