拓展-架构

架构

MVC

MVC模型

在 Android 中,三者的关系如下:

安卓中MVC模型

  1. 模型层(Model):主要负责网络请求,数据库处理,I/O操作,即页面的数据来源。
  2. 视图层(View):对应于xml布局文件和java代码动态view部分。
  3. 控制层(Controller):主要负责业务逻辑,在android中由Activity承担,同时因为XML视图功能太弱,所以Activity既要负责视图的先睡又要加入控制逻辑,承当的功能过多。

由于在 Androidxml 布局的功能性太弱,所以 Activity 承担了绝大部分的工作,所以在 Androidmvc 更像:

安卓中MVC的缺点

总结:

  • 具有一定的分层,model 解耦,controllerview 并没有解耦
  • controllerviewAndroid 中无法做到彻底分离,Controller 变得臃肿不堪
  • 易于理解、开发速度快、可维护性高
  • Activity同时负责ViewController层的工作,违背了单一职责原则
  • Model层与View层存在耦合,存在互相依赖,违背了最小知识原则

MVP

MVP模型

MVP框架

  1. View层:对应于ActivityXML,只负责显示UI,只与Presenter层交互,与Model层没有耦合。

  2. Presenter层:主要负责处理业务逻辑,通过接口回调View层。

  3. Model层:主要负责网络请求,数据库处理等操作,这个没有什么变化。

通过引入接口 BaseView,让相应的视图组件如 ActivityFragment去实现 BaseView,把业务逻辑放在 presenter 层中,弱化 Model 只有跟 view 相关的操作都由 View 层去完成。

总结:

  • 彻底解决了 MVCViewController 傻傻分不清楚的问题
  • 但是随着业务逻辑的增加,一个页面可能会非常复杂,UI的改变是非常多,会有非常多的case,这样就会造成View的接口会很庞大
  • 更容易单元测试

MVP架构同样有自己的问题:

  1. Presenter层通过接口与View通信,实际上持有了View的引用。
  2. 但是随着业务逻辑的增加,一个页面可能会非常复杂,这样就会造成View的接口会很庞大。

MVVM

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

唯一的区别是,它采用双向数据绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。

MVVM框架

MVVMMVP的主要区别在于,不用去主动刷新UI了,只要Model数据变了,会自动反映到UI上。

MVVM模型

在 MVP 中 View 和 Presenter 要相互持有,方便调用对方,而在 MVP 中 View 和 ViewModel 通过 Binding进行关联,他们之前的关联处理通过 DataBinding 完成。

总结:

  • 很好的解决了 MVC 和 MVP 的问题
  • 视图状态较多,ViewModel 的构建和维护的成本都会比较高
  • 但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源

升级版MVVM

安卓/升级版MVVM

MVVM不足:

  • 为保证对外暴露的LiveData是不可变的,需要添加不少模板代码并且容易遗忘。

    为保证数据流的单向流动,LiveData向外暴露时需要转化成immutable,这需要添加不少模板代码,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class TestViewModel: ViewModel(){
    //为保证对外暴露的LiveData不可变,增加一个状态就要添加两个LiveData变量
    private val _pageState: MutableLiveData<PageState> = MutableLiveData()
    val pageState: LiveData<PageState> = _pageState
    private val _state1: MutableLiveData<String> = MutableLiveData()
    val state1: LiveData<String> = _state1
    private val _state2: MutableLiveData<String> = MutableLiveData()
    val state2: LiveData<String> = _state2
    //...
    }

    必须定义一个可变,一个不可变的

  • View层与ViewModel层的交互比较零乱,不成体系。

MVI

MVIMVVM很相似,其借鉴了前端框架的思想,更加强调数据的单向流动和唯一数据源,架构图:

安卓/MVI框架

其主要分为以下几部分:

  1. Model:与MVVM中的Model不同的是,MVIModel主要是指UI状态(State)。例如页面加载状态、控件位置等都是一种UI状态。
  2. View: 与其他MVX中的View一致,可能是一个Activity或者任意UI承载单元。MVI中的View通过订阅Intent的变化实现界面刷新。(注意:这里不是ActivityIntent
  3. Intent: 此Intent不是ActivityIntent,用户的任何操作都被包装成Intent后发送给Model层进行数据请求。

单向数据流

MVI强调数据的单向流动,主要分为以下几步:

  1. 用户操作以Intent的形式通知Model

  2. Model基于Intent更新State

  3. View接收到State变化刷新UI

数据永远在一个环形结构中单向流动,不能反向流动:

安卓/MVI单向数据流

MVI使用

总体架构图

MVI总体架构图

我们使用ViewModel来承载MVIModel层,总体结构也与MVVM类似,主要区别在于ModelView层交互的部分。

  1. Model层承载UI状态,并暴露出ViewStateView订阅,ViewState是个data class,包含所有页面状态。

  2. View层通过Action更新ViewState,替代MVVM通过调用ViewModel方法交互的方式。

MVI实例介绍

添加ViewStateViewEvent

ViewState承载页面的所有状态,ViewEvent则是一次性事件,如Toast等,如下所示:

1
2
3
4
5
6
data class MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)  

sealed class MainViewEvent {
data class ShowSnackbar(val message: String) : MainViewEvent()
data class ShowToast(val message: String) : MainViewEvent()
}
  1. 我们这里ViewState只定义了两个,一个是请求状态,一个是页面数据。

  2. ViewEvent也很简单,一个简单的密封类,显示ToastSnackbar

ViewState更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MainViewModel : ViewModel() {
private val _viewStates: MutableLiveData<MainViewState> = MutableLiveData()
val viewStates = _viewStates.asLiveData()
private val _viewEvents: SingleLiveEvent<MainViewEvent> = SingleLiveEvent()
val viewEvents = _viewEvents.asLiveData()

init {
emit(MainViewState(fetchStatus = FetchStatus.NotFetched, newsList = emptyList()))
}

private fun fabClicked() {
count++
emit(MainViewEvent.ShowToast(message = "Fab clicked count $count"))
}

private fun emit(state: MainViewState?) {
_viewStates.value = state
}

private fun emit(event: MainViewEvent?) {
_viewEvents.value = event
}
}

如上所示:

  1. 我们只需定义ViewStateViewEvent两个State,后续增加状态时在data class中添加即可,不需要再写模板代码。

  2. ViewEvents是一次性的,通过SingleLiveEvent实现,当然你也可以用Channel当来实现。

  3. 当状态更新时,通过emit来更新状态。

View监听ViewState
1
2
3
4
5
6
7
8
private fun initViewModel() {
viewModel.viewStates.observe(this) {
renderViewState(it)
}
viewModel.viewEvents.observe(this) {
renderViewEvent(it)
}
}

如上所示,MVI 使用 ViewStateState 集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态,相对 MVVM 减少了不少模板代码。

View通过Action更新State
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MainActivity : AppCompatActivity() {
private fun initView() {
fabStar.setOnClickListener {
viewModel.dispatch(MainViewAction.FabClicked)
}
}
}
class MainViewModel : ViewModel() {
fun dispatch(action: MainViewAction) =
reduce(viewStates.value, action)

private fun reduce(state: MainViewState?, viewAction: MainViewAction) {
when (viewAction) {
is MainViewAction.NewsItemClicked -> newsItemClicked(viewAction.newsItem)
MainViewAction.FabClicked -> fabClicked()
MainViewAction.OnSwipeRefresh -> fetchNews(state)
MainViewAction.FetchNews -> fetchNews(state)
}
}
}

如上所示,View通过ActionViewModel交互,通过 Action 通信,有利于 ViewViewModel 之间的进一步解耦,同时所有调用以 Action 的形式汇总到一处,也有利于对行为的集中分析和监控。

总结

本文主要介绍了MVC,MVP,MVVMMVI架构,目前MVVM是官方推荐的架构,但仍然有以下几个痛点:

  1. MVVMMVP的主要区别在于双向数据绑定,但由于很多人(比如我)并不喜欢使用DataBindg,其实并没有使用MVVM双向绑定的特性,而是单一数据源。

  2. 当页面复杂时,需要定义很多State,并且需要定义可变与不可变两种,状态会以双倍的速度膨胀,模板代码较多且容易遗忘。

  3. ViewViewModel通过ViewModel暴露的方法交互,比较凌乱难以维护。

MVI可以比较好的解决以上痛点,它主要有以下优势:

  1. 强调数据单向流动,很容易对状态变化进行跟踪和回溯。

  2. 使用ViewStateState集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态,相对 MVVM 减少了不少模板代码。

  3. ViewModel通过ViewStateAction通信,通过浏览ViewStateAciton 定义就可以理清 ViewModel 的职责,可以直接拿来作为接口文档使用。

当然MVI也有一些缺点,比如:

  1. 所有的操作最终都会转换成State,所以当复杂页面的State容易膨胀。

  2. state是不变的,因此每当state需要更新时都要创建新对象替代老对象,这会带来一定内存开销。

软件开发中没有银弹,所有架构都不是完美的,有自己的适用场景,读者可根据自己的需求选择使用。

但通过以上的分析与介绍,我相信使用MVI架构代替没有使用DataBindingMVVM是一个比较好的选择