前言
Android開發發展到今天已經相當成熟了,各種架構大家也都耳熟能詳,如MVC,MVP,MVVM等,其中MVVM更是被官方推薦,成為Android開發中的顯學。
不過軟件開發中沒有銀彈,MVVM架構也不是盡善盡美的,在使用過程中也會有一些不太方便之處,而MVI可以很好的解決一部分MVVM的痛點。
本文主要包括以下內容
MVC,MVP,MVVM等經典架構介紹MVI架構到底是什麼?MVI架構實戰
需要重點指出的是,標題中説MVI架構是MVVM的進階版是指MVI在MVVM非常相似,並在其基礎上做了一定的改良,並不是説MVI架構一定比MVVM適合你的項目
各位同學可以在分析比較各個架構後,選擇合適項目場景的架構
經典架構介紹
MVC架構介紹
MVC是個古老的Android開發架構,隨着MVP與MVVM的流行已經逐漸退出歷史舞台,我們在這裏做一個簡單的介紹,其架構圖如下所示:
MVC架構主要分為以下幾部分
- 視圖層(
View):對應於xml佈局文件和java代碼動態view部分 - 控制層(
Controller):主要負責業務邏輯,在android中由Activity承擔,同時因為XML視圖功能太弱,所以Activity既要負責視圖的顯示又要加入控制邏輯,承擔的功能過多。 - 模型層(
Model):主要負責網絡請求,數據庫處理,I/O的操作,即頁面的數據來源
由於android中xml佈局的功能性太弱,Activity實際上負責了View層與Controller層兩者的工作,所以在android中mvc更像是這種形式:
因此MVC架構在android平台上的主要存在以下問題:
Activity同時負責View與Controller層的工作,違背了單一職責原則Model層與View層存在耦合,存在互相依賴,違背了最小知識原則
MVP架構介紹
由於MVC架構在Android平台上的一些缺陷,MVP也就應運而生了,其架構圖如下所示 :
MVP架構主要分為以下幾個部分
View層:對應於Activity與XML,只負責顯示UI,只與Presenter層交互,與Model層沒有耦合Presenter層: 主要負責處理業務邏輯,通過接口回調View層Model層:主要負責網絡請求,數據庫處理等操作,這個沒有什麼變化
我們可以看到,MVP解決了MVC的兩個問題,即Activity承擔了兩層職責與View層與Model層耦合的問題
但MVP架構同樣有自己的問題
Presenter層通過接口與View通信,實際上持有了View的引用- 但是隨着業務邏輯的增加,一個頁面可能會非常複雜,這樣就會造成
View的接口會很龐大。
MVVM架構介紹
MVVM 模式將 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一致。
唯一的區別是,它採用雙向數據綁定(data-binding):View的變動,自動反映在 ViewModel,反之亦然
MVVM架構圖如下所示:
可以看出MVVM與MVP的主要區別在於,你不用去主動去刷新UI了,只要Model數據變了,會自動反映到UI上。換句話説,MVVM更像是自動化的MVP。
MVVM的雙向數據綁定主要通過DataBinding實現,不過相信有很多人跟我一樣,是不喜歡用DataBinding的,這樣架構就變成了下面這樣
View觀察ViewModle的數據變化並自我更新,這其實是單一數據源而不是雙向數據綁定,所以其實MVVM的這一大特性我其實並沒有用到View通過調用ViewModel提供的方法來與ViewMdoel交互
小結
MVC架構的主要問題在於Activity承擔了View與Controller兩層的職責,同時View層與Model層存在耦合MVP引入Presenter層解決了MVC架構的兩個問題,View只能與Presenter層交互,業務邏輯放在Presenter層MVP的問題在於隨着業務邏輯的增加,View的接口會很龐大,MVVM架構通過雙向數據綁定可以解決這個問題MVVM與MVP的主要區別在於,你不用去主動去刷新UI了,只要Model數據變了,會自動反映到UI上。換句話説,MVVM更像是自動化的MVP。MVVM的雙向數據綁定主要通過DataBinding實現,但有很多人(比如我)不喜歡用DataBinding,而是View通過LiveData等觀察ViewModle的數據變化並自我更新,這其實是單一數據源而不是雙向數據綁定
MVI架構到底是什麼?
MVVM架構有什麼不足?
要了解MVI架構,我們首先來了解下MVVM架構有什麼不足
相信使用MVVM架構的同學都有如下經驗,為了保證數據流的單向流動,LiveData向外暴露時需要轉化成immutable的,這需要添加不少模板代碼並且容易遺忘,如下所示
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
//...
}
如上所示,如果頁面邏輯比較複雜,ViewModel中將會有許多全局變量的LiveData,並且每個LiveData都必須定義兩遍,一個可變的,一個不可變的。這其實就是我通過MVVM架構寫比較複雜頁面時最難受的點。
其次就是View層通過調用ViewModel層的方法來交互的,View層與ViewModel的交互比較分散,不成體系
小結一下,在我的使用中,MVVM架構主要有以下不足
- 為保證對外暴露的
LiveData是不可變的,需要添加不少模板代碼並且容易遺忘 View層與ViewModel層的交互比較分散零亂,不成體系
MVI架構是什麼?
MVI 與 MVVM 很相似,其借鑑了前端框架的思想,更加強調數據的單向流動和唯一數據源,架構圖如下所示
其主要分為以下幾部分
Model: 與MVVM中的Model不同的是,MVI的Model主要指UI狀態(State)。例如頁面加載狀態、控件位置等都是一種UI狀態View: 與其他MVX中的View一致,可能是一個Activity或者任意UI承載單元。MVI中的View通過訂閲Model的變化實現界面刷新Intent: 此Intent不是Activity的Intent,用户的任何操作都被包裝成Intent後發送給Model層進行數據請求
單向數據流
MVI強調數據的單向流動,主要分為以下幾步:
- 用户操作以
Intent的形式通知Model Model基於Intent更新StateView接收到State變化刷新UI。
數據永遠在一個環形結構中單向流動,不能反向流動:
上面簡單的介紹了下MVI架構,下面我們一起來看下具體是怎麼使用MVI架構的
MVI架構實戰
總體架構圖
我們使用ViewModel來承載MVI的Model層,總體結構也與MVVM類似,主要區別在於Model與View層交互的部分
Model層承載UI狀態,並暴露出ViewState供View訂閲,ViewState是個data class,包含所有頁面狀態View層通過Action更新ViewState,替代MVVM通過調用ViewModel方法交互的方式
MVI實例介紹
添加ViewState與ViewEvent
ViewState承載頁面的所有狀態,ViewEvent則是一次性事件,如Toast等,如下所示
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()
}
- 我們這裏
ViewState只定義了兩個,一個是請求狀態,一個是頁面數據 ViewEvent也很簡單,一個簡單的密封類,顯示Toast與Snackbar
ViewState更新
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
}
}
如上所示
- 我們只需定義
ViewState與ViewEvent兩個State,後續增加狀態時在data class中添加即可,不需要再寫模板代碼 ViewEvents是一次性的,通過SingleLiveEvent實現,當然你也可以用Channel當來實現- 當狀態更新時,通過
emit來更新狀態
View監聽ViewState
private fun initViewModel() {
viewModel.viewStates.observe(this) {
renderViewState(it)
}
viewModel.viewEvents.observe(this) {
renderViewEvent(it)
}
}
如上所示,MVI 使用 ViewState 對 State 集中管理,只需要訂閲一個 ViewState 便可獲取頁面的所有狀態,相對 MVVM 減少了不少模板代碼。
View通過Action更新State
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通過Action與ViewModel交互,通過 Action 通信,有利於 View 與 ViewModel 之間的進一步解耦,同時所有調用以 Action 的形式彙總到一處,也有利於對行為的集中分析和監控
總結
本文主要介紹了MVC,MVP,MVVM與MVI架構,目前MVVM是官方推薦的架構,但仍然有以下幾個痛點
MVVM與MVP的主要區別在於雙向數據綁定,但由於很多人(比如我)並不喜歡使用DataBindg,其實並沒有使用MVVM雙向綁定的特性,而是單一數據源- 當頁面複雜時,需要定義很多
State,並且需要定義可變與不可變兩種,狀態會以雙倍的速度膨脹,模板代碼較多且容易遺忘 View與ViewModel通過ViewModel暴露的方法交互,比較零亂難以維護
而MVI可以比較好的解決以上痛點,它主要有以下優勢
- 強調數據單向流動,很容易對狀態變化進行跟蹤和回溯
- 使用
ViewState對State集中管理,只需要訂閲一個ViewState便可獲取頁面的所有狀態,相對MVVM減少了不少模板代碼 ViewModel通過ViewState與Action通信,通過瀏覽ViewState和Aciton定義就可以理清ViewModel的職責,可以直接拿來作為接口文檔使用。
當然MVI也有一些缺點,比如
- 所有的操作最終都會轉換成
State,所以當複雜頁面的State容易膨脹 state是不變的,因此每當state需要更新時都要創建新對象替代老對象,這會帶來一定內存開銷
軟件開發中沒有銀彈,所有架構都不是完美的,有自己的適用場景,讀者可根據自己的需求選擇使用。
但通過以上的分析與介紹,我相信使用MVI架構代替沒有使用DataBinding的MVVM是一個比較好的選擇~
相關視頻推薦:
【2021最新版】Android studio安裝教程+Android(安卓)零基礎教程視頻(適合Android 0基礎,Android初學入門)含音視頻_嗶哩嗶哩_bilibili
Android架構設計原理與實戰——Jetpack結合MVP組合應用開發一個優秀的APP!_嗶哩嗶哩_bilibili
Android進階必學:jetpack架構組件—Navigation_嗶哩嗶哩_bilibili
Android進階系統學習——Jetpack先天優秀的基因可以避免數據內存泄漏_嗶哩嗶哩_bilibili