動態

詳情 返回 返回

MVVM 進階版:MVI 架構瞭解一下~ - 動態 詳情

前言

Android開發發展到今天已經相當成熟了,各種架構大家也都耳熟能詳,如MVC,MVP,MVVM等,其中MVVM更是被官方推薦,成為Android開發中的顯學。
不過軟件開發中沒有銀彈,MVVM架構也不是盡善盡美的,在使用過程中也會有一些不太方便之處,而MVI可以很好的解決一部分MVVM的痛點。
本文主要包括以下內容

  1. MVC,MVP,MVVM等經典架構介紹
  2. MVI架構到底是什麼?
  3. MVI架構實戰
需要重點指出的是,標題中説MVI架構是MVVM的進階版是指MVIMVVM非常相似,並在其基礎上做了一定的改良,並不是説MVI架構一定比MVVM適合你的項目
各位同學可以在分析比較各個架構後,選擇合適項目場景的架構

經典架構介紹

MVC架構介紹

MVC是個古老的Android開發架構,隨着MVPMVVM的流行已經逐漸退出歷史舞台,我們在這裏做一個簡單的介紹,其架構圖如下所示:

MVC架構主要分為以下幾部分

  1. 視圖層(View):對應於xml佈局文件和java代碼動態view部分
  2. 控制層(Controller):主要負責業務邏輯,在android中由Activity承擔,同時因為XML視圖功能太弱,所以Activity既要負責視圖的顯示又要加入控制邏輯,承擔的功能過多。
  3. 模型層(Model):主要負責網絡請求,數據庫處理,I/O的操作,即頁面的數據來源

由於androidxml佈局的功能性太弱,Activity實際上負責了View層與Controller層兩者的工作,所以在androidmvc更像是這種形式:

因此MVC架構在android平台上的主要存在以下問題:

  1. Activity同時負責ViewController層的工作,違背了單一職責原則
  2. Model層與View層存在耦合,存在互相依賴,違背了最小知識原則

MVP架構介紹

由於MVC架構在Android平台上的一些缺陷,MVP也就應運而生了,其架構圖如下所示 :

MVP架構主要分為以下幾個部分

  1. View層:對應於ActivityXML,只負責顯示UI,只與Presenter層交互,與Model層沒有耦合
  2. Presenter層: 主要負責處理業務邏輯,通過接口回調View
  3. Model層:主要負責網絡請求,數據庫處理等操作,這個沒有什麼變化

我們可以看到,MVP解決了MVC的兩個問題,即Activity承擔了兩層職責與View層與Model層耦合的問題

MVP架構同樣有自己的問題

  1. Presenter層通過接口與View通信,實際上持有了View的引用
  2. 但是隨着業務邏輯的增加,一個頁面可能會非常複雜,這樣就會造成View的接口會很龐大。

MVVM架構介紹

MVVM 模式將 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一致。
唯一的區別是,它採用雙向數據綁定(data-binding):View的變動,自動反映在 ViewModel,反之亦然
MVVM架構圖如下所示:

可以看出MVVMMVP的主要區別在於,你不用去主動去刷新UI了,只要Model數據變了,會自動反映到UI上。換句話説,MVVM更像是自動化的MVP

MVVM的雙向數據綁定主要通過DataBinding實現,不過相信有很多人跟我一樣,是不喜歡用DataBinding的,這樣架構就變成了下面這樣

  1. View觀察ViewModle的數據變化並自我更新,這其實是單一數據源而不是雙向數據綁定,所以其實MVVM的這一大特性我其實並沒有用到
  2. View通過調用ViewModel提供的方法來與ViewMdoel交互

小結

  1. MVC架構的主要問題在於Activity承擔了ViewController兩層的職責,同時View層與Model層存在耦合
  2. MVP引入Presenter層解決了MVC架構的兩個問題,View只能與Presenter層交互,業務邏輯放在Presenter
  3. MVP的問題在於隨着業務邏輯的增加,View的接口會很龐大,MVVM架構通過雙向數據綁定可以解決這個問題
  4. MVVMMVP的主要區別在於,你不用去主動去刷新UI了,只要Model數據變了,會自動反映到UI上。換句話説,MVVM更像是自動化的MVP
  5. 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架構主要有以下不足

  1. 為保證對外暴露的LiveData是不可變的,需要添加不少模板代碼並且容易遺忘
  2. View層與ViewModel層的交互比較分散零亂,不成體系

MVI架構是什麼?

MVIMVVM 很相似,其借鑑了前端框架的思想,更加強調數據的單向流動和唯一數據源,架構圖如下所示

其主要分為以下幾部分

  1. Model: 與MVVM中的Model不同的是,MVIModel主要指UI狀態(State)。例如頁面加載狀態、控件位置等都是一種UI狀態
  2. View: 與其他MVX中的View一致,可能是一個Activity或者任意UI承載單元。MVI中的View通過訂閲Model的變化實現界面刷新
  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等,如下所示

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更新

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

    private fun initViewModel() {
        viewModel.viewStates.observe(this) {
            renderViewState(it)
        }
        viewModel.viewEvents.observe(this) {
            renderViewEvent(it)
        }
    }

如上所示,MVI 使用 ViewStateState 集中管理,只需要訂閲一個 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通過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是一個比較好的選擇~

相關視頻推薦:

【2021最新版】Android studio安裝教程+Android(安卓)零基礎教程視頻(適合Android 0基礎,Android初學入門)含音視頻_嗶哩嗶哩_bilibili

Android架構設計原理與實戰——Jetpack結合MVP組合應用開發一個優秀的APP!_嗶哩嗶哩_bilibili

Android進階必學:jetpack架構組件—Navigation_嗶哩嗶哩_bilibili

Android進階系統學習——Jetpack先天優秀的基因可以避免數據內存泄漏_嗶哩嗶哩_bilibili

user avatar u_17514447 頭像 u_15878077 頭像 himeka 頭像 wu_cat 頭像 xiaodiandideyangrouchuan 頭像 mamaster777 頭像 vivotech 頭像 binghe001 頭像 wodekouwei 頭像 fuzhengwei 頭像 toplist 頭像 kinfuy 頭像
點贊 46 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.