Airbnb开源框架,真响应式架构——MvRx
今日科技快訊
2月21日凌晨,三星美國舊金山舉行Galaxy Unpacked 2019新品發(fā)布會。正式發(fā)布年度旗艦Galaxy S10系列手機(jī)以及折疊手機(jī)Galaxy Fold,還推出了首款5G手機(jī)。
Galaxy Fold此前曾在2018年11月開發(fā)者大會上亮相,但當(dāng)時并沒宣布具體參數(shù),今天這是它終于定妝出場。它的形態(tài)是兩折的,仿佛一個錢包,外表是一塊4.6英寸的狹長屏幕,打開后,內(nèi)部是一塊7.3英寸的屏幕。三星Galaxy S10系列一共包含三款機(jī)型,分別是S10、S10+、S10e。相比去年的S9系列,S10系列外觀最大的變化在于采用Dynamic AMOLED屏幕設(shè)計,網(wǎng)友俗稱“挖孔屏”,其中S10為6.1英寸2K分辨率雙曲全視屏、S10+為6.4英寸2K分辨率雙曲全視屏,S10e由于定位相比前兩者更低,所以仍然使用1080P非雙曲全視屏。
作者簡介
明天就是周六啦,提前祝大家周末愉快!
本篇來自?珞澤珈群?的投稿文章,和大家分享了一個 Airbnb 的開源庫,響應(yīng)式架構(gòu)的MvRx,希望對大家有所幫助!
?珞澤珈群?的博客地址:
https://www.jianshu.com/u/679d8deee5ae
前言
Airbnb 最近開源了一個庫,他們稱之為 Android 界的 Autopilot——MvRx(ModelView ReactiveX 的縮寫,讀作 mavericks)。這個庫并不“單純”,它其實是一個架構(gòu),已經(jīng)被應(yīng)用在了 Airbnb 幾乎所有的產(chǎn)品上。地址是:
https://github.com/airbnb/MvRx
這個庫綜合運(yùn)用了以下幾種技術(shù)
Kotlin (MvRx is Kotlin first and Kotlin only)
Android Architecture Components
RxJava
React (概念上的)
Epoxy (可選但推薦)
光看這個清單,也知道事情并不簡單。利用這個庫我們可以方便地構(gòu)建出 MVVM 架構(gòu)的APP,讓開發(fā)更加的簡單、高效。
真響應(yīng)式架構(gòu)
響應(yīng)式(React)架構(gòu)并沒有什么定義,只是我覺得這么描述 MvRx 比較準(zhǔn)確。這里所說的響應(yīng)式架構(gòu)是指,數(shù)據(jù)響應(yīng)式以及界面響應(yīng)式。數(shù)據(jù)響應(yīng)式大體指數(shù)據(jù)以流的形式呈現(xiàn)(RxJava 那套東西),界面響應(yīng)式大體指數(shù)據(jù)驅(qū)動界面更新,界面顯示與數(shù)據(jù)狀態(tài)保持一致。
以如上的定義來看,在 RxJava 的幫助下,幾乎所有架構(gòu)都可以實現(xiàn)數(shù)據(jù)響應(yīng)式,因為數(shù)據(jù)響應(yīng)式實際上是 Model 層的設(shè)計。但是界面響應(yīng)式則基本上沒有哪個框架實現(xiàn)了,最接近的應(yīng)該是Android Architecture Components,但是Android Architecture Components 并沒有保證界面與數(shù)據(jù)狀態(tài)的一致,我們通過 LiveData 通知界面更新,只是把數(shù)據(jù)帶給了界面,界面顯示與數(shù)據(jù)狀態(tài)并不一定是一致的(例如,LiveData 攜帶了下一頁的數(shù)據(jù),界面只是把該數(shù)據(jù)加到了 RecyclerView 的后面,數(shù)據(jù)并沒有完全代表了當(dāng)前界面的狀態(tài))。而 MvRx 真正實現(xiàn)了界面的響應(yīng)式,所以我稱之為真響應(yīng)式架構(gòu)。
如果你了解過 Flutter,那么 MvRx 很容易理解,因為兩者都采用了響應(yīng)式構(gòu)建的思想,以下是關(guān)于 Flutter 的描述,把它替換為 MvRx 也基本上適用。
Flutter 組件采用現(xiàn)代響應(yīng)式框架構(gòu)建,這是從 React 中獲得的靈感,中心思想是用組件 (widget) 構(gòu)建你的 UI。 組件描述了在給定其當(dāng)前配置和狀態(tài)時他們顯示的樣子。當(dāng)組件狀態(tài)改變,組件會重構(gòu)它的描述 (description),Flutter 會對比之前的描述,以確定底層渲染樹從當(dāng)前狀態(tài)轉(zhuǎn)換到下一個狀態(tài)所需要的最小更改。
由于 Flutter 的實現(xiàn)不受原生的限制,它完全用另外一套方式實現(xiàn)了界面的渲染,并且響應(yīng)式在設(shè)計之初就是 Flutter 的核心,所以在 Flutter 中任何組件(可以理解為 Android 中的 View)都是響應(yīng)式的,都可以確定它從當(dāng)前狀態(tài)轉(zhuǎn)換到下一個狀態(tài)所需要的最小更改,顯然這一點在原生 Android 上是實現(xiàn)不了的。而 MvRx 在原生 Android 的基礎(chǔ)上幾乎實現(xiàn)了所有界面的響應(yīng)式,這一點還是非常厲害的。
命令式 MVP 與響應(yīng)式 MVVM
MVP 模式在 Android 界一直很流行,因為它比較好理解。其核心思想是,通過接口隔離數(shù)據(jù)與顯示,數(shù)據(jù)的變動通過接口回調(diào)的方式去通知界面更新。這正是典型的命令式 M-V(數(shù)據(jù)-顯示)鏈接。在這種模式下 View 層是完全被動的,完全受控于 Presenter 層的命令。這種模式并沒有什么大問題,只是有一些不太方便之處,主要體現(xiàn)在 M-V 的緊密鏈接,導(dǎo)致復(fù)用比較困難,要么 View 層需要定義不必要的接口(這樣 Presenter 可以復(fù)用),要么就需要為幾乎每個 View 都定義一個對應(yīng)的 Presenter,想想都心累。
不同于 MVP 通過接口的方式來隔離數(shù)據(jù)與顯示,MVVM 是使用觀察者的方式來隔離數(shù)據(jù)與顯示。以 Android Architecture Components 構(gòu)建的 MVVM 模式為例,View 通過觀察 LiveData 來驅(qū)動界面更新。MVVM 帶來的主要好處是打破了 M-V 的緊密鏈接,ViewModel 復(fù)用變得很簡單,View 層需要什么數(shù)據(jù)觀察什么數(shù)據(jù)即可。將 View 抽離為觀察者,可以實現(xiàn)響應(yīng)式 MVVM 架構(gòu),只是 View 本身不是響應(yīng)式的。
以我的實踐來看 Android Architecture Components 構(gòu)建的 MVVM 的主要問題是,RxJava 與 LiveData 的銜接并不方便,還有就是按照 Google 給出的 sample,數(shù)據(jù)加載的狀態(tài)需要和數(shù)據(jù)本身打包在一起,然后通過 LiveData 傳遞出去,我覺得這不是一個好的做法。我在實踐中是在 Observer 的 onSubscribe,onNext,onError 方法中分別對不同的 MutableLiveData 賦值,然后在 View 中去觀察這些 LiveData 來更新界面的。說實話,這很丑陋,但是比 Google 給出的 sample 要方便許多。
MvRx 的真響應(yīng)式 MVVM
MvRx 構(gòu)建的 MVVM 模式,完美地解決了上述的問題。MvRx 放棄了LiveData,使用State 來通知 View 層數(shù)據(jù)的改變(當(dāng)然仍然是可感知生命周期的)。MvRx 可以方便地把RxJava Observable 的請求過程包裝成 Ansyc 類,不僅可以改變 State 來通知 View 層,而且也包含了數(shù)據(jù)加載的狀態(tài)(成功、失敗、加載中等)。如果結(jié)合 Airbnb 的另一個開源庫 Epoxy,那么幾乎可以做到真正的響應(yīng)式,即View層在數(shù)據(jù)改變時僅僅描述當(dāng)前數(shù)據(jù)狀態(tài)下界面的樣子,Epoxy 可以幫我們實現(xiàn)與之前數(shù)據(jù)狀態(tài)的比較,然后找出差別,僅更新那些有差別的 View 部分。這是對 MvRx 的大致描述。下面來看看 MvRx 是如果使用的。
MvRx的使用
MvRx 的重要概念
MvRx 有四個重要的概念,分別是 State、ViewModel、View 和 Async。
State
包含界面顯示的所有數(shù)據(jù),實現(xiàn)類需是繼承自 MvRxState 的 immutable Kotlin data class。像是這樣
data?class?TasksState(????val?tasks:?List<Task>?=?emptyList(),
????val?taskRequest:?Async<List<Task>>?=?Uninitialized,
????val?isLoading:?Boolean?=?false,
????val?lastEditedTask:?String??=?null
)?:?MvRxState?//MvRxState?僅是一個標(biāo)記接口
State 的作用是承載數(shù)據(jù),并且應(yīng)該包含有界面顯示的所有數(shù)據(jù)。當(dāng)然可以對界面進(jìn)行拆分,使用多個State共同決定界面的顯示。
State必須是不可變的(immutable),即State的所有屬性必須是val的。只有ViewModel 可以改變 State,改變 State 時一般使用其 copy 方法,創(chuàng)建一個新的 State對象。
可以把 MvRx 的 State 類比成 Architecture Components 中的 LiveData,它們的相同點是都可以被 View 觀察,不同點是,State 的改變會觸發(fā) View 的 invalidate()方法,從而通知界面重繪。
ViewModel
完全繼承自 Architecture Components中的 ViewModel,ViewModel 包含有除了界面顯示之外的業(yè)務(wù)邏輯。此外,最關(guān)鍵的一點是,ViewModel 還包含有一個State,ViewModel 可以改變 State 的狀態(tài),然后 View 可以觀察 State 的狀態(tài)。實現(xiàn)類需繼承 BaseMvRxViewModel,并且必須向 BaseMvRxViewModel 傳遞 initialState(代表了View 的初始狀態(tài))。像是這樣
class?TasksViewModel(initialState:?TasksState)?:?BaseMvRxViewModel<TasksState>(initialState)View
一般而言是一個繼承自 BaseMvRxFragment 的 Fragment。BaseMvRxFragment 實現(xiàn)了接口 MvRxView,這個接口有一個 invalidate() 方法,每當(dāng) ViewModel 的 state 發(fā)生改變時 invalidate() 方法都會被調(diào)用。View 也可以觀察 State 中的某個或某幾個屬性的變化,View 是沒辦法改變 State 狀態(tài)的,只有 ViewModel 可以改變 State 的狀態(tài)。
Async
代表了數(shù)據(jù)加載的狀態(tài)。Async 是一個Kotlin sealed class,它有四種類型:Uninitialized, Loading, Success, Fail(包含了一個名為 error 的屬性,可以獲取錯誤類型)。Async 重載了操作符 invoke,除了在 Success 返回數(shù)據(jù)外,其它情況下都返回null:
var?foo?=?Loading()println(foo())?//?null
foo?=?Success<Int>(5)
println(foo())?//?5
foo?=?Fail(IllegalStateException("bar"))
println(foo())?//?null
在 ViewModel 中可以通過擴(kuò)展函數(shù)execute把Observable<T>的請求過程包裝成Asnyc<T>,這可以方便地表示數(shù)據(jù)獲取的狀態(tài)(下面會有介紹)。
以上四個核心概念是怎么聯(lián)系到一起的呢?請看下圖:
圖中沒有包含 Asnyc,State 可包含若干個 Asnyc,用來表示數(shù)據(jù)加載的狀態(tài),便于顯示Loading 或者加載錯誤信息等。
按照理想情形,View 不需要主動觀察 State,State 的任意改變都會調(diào)用 View 的invalidate方法,在 invalidate 方法中根據(jù)當(dāng)前的 State(在 View 中通過 ViewModel 的withState 方法獲取 State)直接重繪一下 View 即可。然而這太過于理想,實際上可以通過 selectSubscribe,asyncSubscribe 等方法觀察 State 中某個屬性的改變,根據(jù)特定的屬性更新 View 的特定部分。
以上是 MvRx 的四個核心概念。下面以官方 sample 為例,展示一下 MvRx 應(yīng)該怎樣使用。
如何使用
ToDo Sample,架構(gòu)界的 Hello World。界面長這個樣子。
以下以首界面為例,介紹應(yīng)該如何使用 MvRx。
State的使用
data?class?Task(
????var?title:?String?=?"",
????var?description:?String?=?"",
????var?id:?String?=?UUID.randomUUID().toString(),
????var?complete:?Boolean?=?false
)
data?class?TasksState(
????val?tasks:?List<Task>?=?emptyList(),?//界面上的待辦事
????val?taskRequest:?Async<List<Task>>?=?Uninitialized,?//代表請求的狀態(tài)
????val?isLoading:?Boolean?=?false,?//是否顯示Loading
????val?lastEditedTask:?String??=?null?//上次編輯的待辦事ID
)?:?MvRxState
State 包含了這個界面要顯示的所有數(shù)據(jù)。
ViewModel 的使用
具體的業(yè)務(wù)邏輯并不重要,主要看 ViewModel 是如何定義的。
/**?*?必須有一個initialState
?*?source是數(shù)據(jù)源,可以是數(shù)據(jù)庫,也可以是網(wǎng)絡(luò)請求等(例子中是數(shù)據(jù)庫)
?**/
class?TasksViewModel(initialState:?TasksState,?private?val?source:?TasksDataSource)?:?MvRxViewModel<TasksState>(initialState)?{
????//工廠方法,必須實現(xiàn)MvRxViewModelFactory接口
????companion?object?:?MvRxViewModelFactory<TasksViewModel,?TasksState>?{
????????/**
?????????*?主要用途是通過依賴注入傳入一些參數(shù)來構(gòu)造ViewModel
?????????*?TasksState是MvRx幫我們構(gòu)造的(通過反射)
?????????**/
????????override?fun?create(viewModelContext:?ViewModelContext,?state:?TasksState):?BaseMvRxViewModel<TasksState>?{
????????????//例子中并沒有使用依賴注入,而是直接獲取數(shù)據(jù)庫
????????????val?database?=?ToDoDatabase.getInstance(viewModelContext.activity)
????????????val?dataSource?=?DatabaseDataSource(database.taskDao(),?2000)
????????????return?TasksViewModel(state,?dataSource)
????????}
????}
????init?{
????????//方便調(diào)試,State狀態(tài)改變時打印出來
????????logStateChanges()
????????//初始加載任務(wù)
????????refreshTasks()
????}
????//獲取待辦事
????fun?refreshTasks()?{
????????source.getTasks()
????????????.doOnSubscribe?{?setState?{?copy(isLoading?=?true)?}?}
????????????.doOnComplete?{?setState?{?copy(isLoading?=?false)?}?}
????????????//execute把Observable包裝成Async
????????????.execute?{?copy(taskRequest?=?it,?tasks?=?it()??:?tasks,?lastEditedTask?=?null)?}
????}
????//新增或者更新待辦事
????fun?upsertTask(task:?Task)?{
????????//通過setState改變?State的狀態(tài)
????????setState?{?copy(tasks?=?tasks.upsert(task)?{?it.id?==?task.id?},?lastEditedTask?=??task.id)?}
????????//因為是數(shù)據(jù)庫操作,一般不會失敗,所以沒有理會數(shù)據(jù)操作的狀態(tài)
????????source.upsertTask(task)
????}
????//標(biāo)記任務(wù)完成與否
????fun?setComplete(id:?String,?complete:?Boolean)?{
????????setState?{
????????????//沒有這個任務(wù),拉倒;this指之前的?State,直接返回之前的?State意思就是無需更新
????????????val?task?=?tasks.findTask(id)??:?return@setState?this
????????????//這個任務(wù)已經(jīng)完成了,拉倒
????????????if?(task.complete?==?complete)?return@setState?this
????????????//找到這個任務(wù),并更新
????????????copy(tasks?=?tasks.copy(tasks.indexOf(task),?task.copy(complete?=?complete)),?lastEditedTask?=?id)
????????}
????????//數(shù)據(jù)庫更新
????????source.setComplete(id,?complete)
????}
????//清空已完成的待辦事
????fun?clearCompletedTasks()?=?setState?{
????????source.clearCompletedTasks()
????????copy(tasks?=?tasks.filter?{?!it.complete?},?lastEditedTask?=?null)
????}
????//刪除待辦事
????fun?deleteTask(id:?String)?{
????????setState?{?copy(tasks?=?tasks.delete?{?it.id?==?id?},?lastEditedTask?=?id)?}
????????source.deleteTask(id)
????}
}
ViewModel 實現(xiàn)了業(yè)務(wù)邏輯,其核心作用就是與 Model 層(這里的 source)溝通,并更新 State。這里有幾點需要說明:
按照 MvRx 的要求,ViewModel 可以沒有工廠方法,這樣的話 MvRx 會通過反射構(gòu)造出 ViewModel(當(dāng)然這一般不可能,畢竟 ViewModel 一般都包含 Model 層)。如果 ViewModel 包含有除 initialState 之外的其它構(gòu)造參數(shù),則需要我們實現(xiàn)工廠方法。如上所示,必須通過伴生對象實現(xiàn) MvRxViewModelFactory 接口。
只能在ViewModel中更新State。更新State有兩種方法,setState或者 execute。setState 很好理解,直接更新 State 即可。其定義如下
????//參數(shù)是State上的擴(kuò)展函數(shù),會接收到上次?State的值
????protected?fun?setState(reducer:?S.()?->?S)?{
????????//...
????}
}
因為 State 是 immutable Kotlin data class,所以一般而言都是通過 data class 的 copy方法返回新的 State。execute 是一個擴(kuò)展方法,其定義如下
abstract?class?BaseMvRxViewModel<S?:?MvRxState>?{????/**
?????*?Helper?to?map?an?observable?to?an?Async?property?on?the?state?object.
?????*/
????//參數(shù)依然是State上的擴(kuò)展函數(shù)
????fun?<T>?Observable<T>.execute(
????????stateReducer:?S.(Async<T>)?->?S
????)?=?execute({?it?},?null,?stateReducer)
????/**
?????*?Execute?an?observable?and?wrap?its?progression?with?AsyncData?reduced?to?the?global?state.
?????*
?????*?@param?mapper?A?map?converting?the?observable?type?to?the?desired?AsyncData?type.
?????*?@param?successMetaData?A?map?that?provides?metadata?to?set?on?the?Success?result.
?????*????????????????????????It?allows?data?about?the?original?Observable?to?be?kept?and?accessed?later.?For?example,
?????*????????????????????????your?mapper?could?map?a?network?request?to?just?the?data?your?UI?needs,?but?your?base?layers?could
?????*????????????????????????keep?metadata?about?the?request,?like?timing,?for?logging.
?????*?@param?stateReducer?A?reducer?that?is?applied?to?the?current?state?and?should?return?the
?????*?????????????????????new?state.?Because?the?state?is?the?receiver?and?it?likely?a?data
?????*?????????????????????class,?an?implementation?may?look?like:?`{?copy(response?=?it)?}`.
?????*
?????*??@see?Success.metadata
?????*/
????fun?<T,?V>?Observable<T>.execute(
????????mapper:?(T)?->?V,
????????successMetaData:?((T)?->?Any)??=?null,
????????stateReducer:?S.(Async<V>)?->?S
????):?Disposable?{
????????setState?{?stateReducer(Loading())?}
????????return?map?{
????????????????val?success?=?Success(mapper(it))
????????????????success.metadata?=?successMetaData?.invoke(it)
????????????????success?as?Async<V>
????????????}
????????????.onErrorReturn?{?Fail(it)?}
????????????.subscribe?{?asyncData?->?setState?{?stateReducer(asyncData)?}?}
????????????.disposeOnClear()?//ViewModel?clear的時候dispose
????}
}
execute 方法可以把 Observable 的請求過程包裝成 Async,我們都知道訂閱 Observable 需要有 onNext,onComplete,onError 等方法,execute 就是把這些個方法包裝成了統(tǒng)一的 Async 類。前面已經(jīng)說過,Async是sealed class,只有四個子類:Uninitialized, Loading, Success, Fail。這些子類完美的描述了一次請求的過程,并且它們重載了 invoke 操作符(Success 情況下返回請求的數(shù)據(jù),其它情況均為 null)。因此經(jīng)常看到這樣的樣板代碼:
fun?<T>?Observable<T>.execute(????stateReducer:?S.(Async<T>)?->?S
)
/**
?*?根據(jù)上面execute的定義,我們傳遞過去的是State上的以Async<T>為參數(shù)的擴(kuò)展函數(shù)
?*?因此下面的it參數(shù)是指?Async<T>,it()是獲取請求的結(jié)果,tasks?=?it()??:?tasks?表示只在請求?Success時更新State
?**/
fun?refreshTasks()?{
????source.getTasks()
????????//...
????????.execute?{?copy(taskRequest?=?it,?tasks?=?it()??:?tasks,?lastEditedTask?=?null)?}
}
View 的使用
abstract?class?BaseFragment?:?BaseMvRxFragment()?{
????//activityViewModel是MvRx定義的獲取ViewModel的方式
????//按照規(guī)范必須使用activityViewModel、fragmentViewModel、existingViewModel(都是Lazy<T>類)獲取ViewModel
????protected?val?viewModel?by?activityViewModel(TasksViewModel::class)
????//Epoxy的使用
????protected?val?epoxyController?by?lazy?{?epoxyController()?}
????override?fun?onViewCreated(view:?View,?savedInstanceState:?Bundle?)?{
????????//可以觀察State中某個(某幾個)屬性的變化
????????viewModel.selectSubscribe(TasksState::tasks,?TasksState::lastEditedTask)?{?tasks,?lastEditedTask?->
????????????//...
????????}
????????//觀察Async屬性
????????viewModel.asyncSubscribe(TasksState::taskRequest,?onFail?=?{
????????????coordinatorLayout.showLongSnackbar(R.string.loading_tasks_error)
????????})
????}
????//State的改變均會觸發(fā)
????override?fun?invalidate()?{
????????//Epoxy的用法
????????recyclerView.requestModelBuild()
????}
????abstract?fun?epoxyController():?ToDoEpoxyController
}
class?TaskListFragment?:?BaseFragment()?{
????//另一個ViewModel
????private?val?taskListViewModel:?TaskListViewModel?by?fragmentViewModel()
????//Epoxy的使用
????override?fun?epoxyController()?=?simpleController(viewModel,?taskListViewModel)?{?state,?taskListState?->
????????//?We?always?want?to?show?this?so?the?content?won't?snap?up?when?the?loader?finishes.
????????horizontalLoader?{
????????????id("loader")
????????????loading(state.isLoading)
????????}
????????//...
????}
}
按照MvRx的規(guī)范,View通過activityViewModel(ViewModel被置于Activity中), fragmentViewModel(ViewModel被置于 Fragment 中), existingViewModel(從Activity中獲取已存在的 ViewModel) 來獲取ViewModel,這是因為,以這幾種方式獲取ViewModel,MvRx 會幫我們完成如下幾件事:
activityViewModel, fragmentViewModel, existingViewModel其實都是 Kotlin 的Lazy 子類,顯然會是懶加載。但是它不是真正的“懶”,因為在這些子類的構(gòu)造函數(shù)中會添加一個對 View 生命周期的觀察者,在 ON_CREATE 事件發(fā)生時會構(gòu)造出ViewModel,也就是說 ViewModel 最晚到 ON_CREATE 時即被構(gòu)造完成(為了及早發(fā)出網(wǎng)絡(luò)請求等)。
通過反射構(gòu)造出 State,ViewModel。
調(diào)用 ViewModel 的 subscribe 方法,觀察 State 的改變,如果改變則調(diào)用 View 的invalidate 方法。
當(dāng) State 發(fā)生改變時,View 的 invalidate 方法會被調(diào)用。invalidate被調(diào)用僅說明了State 發(fā)生了改變,究竟是哪個屬性發(fā)生的改變并不得而知,按照 MvRx 的“理想”,哪個屬性發(fā)生改變并不重要,只要 View 根據(jù)當(dāng)前的 State“重繪”一下 View 即可。這里“重繪”顯然指的不是簡單地重繪整個界面,應(yīng)該是根據(jù)當(dāng)前 State“描繪”當(dāng)前界面,然后與上次界面作比較,只更新差異部分。顯然這種“理想”太過于高級,需要有一個幫手來完成這項任務(wù),于是就有了 Epoxy(其實是先有的 Epoxy)。
Epoxy 簡單來說就是 RecyclerView的高級助手,我們只需要定義某個數(shù)據(jù)在RecyclerView 的 ItemView 上是如何顯示的,然后把一堆數(shù)據(jù)扔給 Epoxy 就行了。Epoxy會幫我們分析這次的數(shù)據(jù)跟上次的數(shù)據(jù)有什么差別,只更新差別的部分。如此看來Epoxy真的是MvRx的絕佳助手。關(guān)于Epoxy有非常多的內(nèi)容,查看Epoxy——RecyclerView 的絕佳助手了解更多。
Epoxy 雖然“高級”,但也僅僅適用于 RecyclerView。因此可以看到 MvRx 的例子中把所有界面的主要部分都以 RecyclerView 承載,例如,Loading 出現(xiàn)在 RecyclerView 的頭部;如果界面是非滾動的,就把界面作為RecyclerView唯一的元素放入其中,等等。這都是為了使用 Epoxy,使開發(fā)模式更加統(tǒng)一,并且更加接近于完全的響應(yīng)式。但是總有些情形下界面不適合用 RecyclerView 展示,沒關(guān)系,我們還可以單獨(dú)觀察 State 中的某(幾)個屬性的改變(這幾乎與 LiveData 沒有差別)。例如:
//觀察兩個屬性的改變,任意一個屬性方式了改變都會調(diào)用????viewModel.selectSubscribe(TasksState::tasks,?TasksState::lastEditedTask)?{?tasks,?lastEditedTask?->
????????//根據(jù)屬性值做更新
????}
????//觀察Async屬性,可以傳入onSuccess、onFail參數(shù)
????//和上面觀察普通屬性沒有區(qū)別,只是內(nèi)部幫我們判斷了Async是否成功
????viewModel.asyncSubscribe(TasksState::taskRequest,?onFail?=?{
????????coordinatorLayout.showLongSnackbar(R.string.loading_tasks_error)
????})
問題
使用 MvRx 有幾個問題需要注意
State 是 immutable Kotlin data class,Kotlin 幫我們生成了equals方法(即調(diào)用每個屬性的 equals 方法),在 ViewModel 中通過 setState,execute 方法更新State時,只有更新后的 State 確實與上一次的 State 不相等時,View 才會收到通知。經(jīng)常犯的錯誤是這樣的:
data?class?CheckedData(????val?id:?Int,
????val?name:?String,
????var?checked:?Boolean?=?false
)
//List的equals方法的實現(xiàn)是,項數(shù)相同,并且每項都equals
data?class?SomeState(val?data:?List<CheckedData>?=?emptyList())?:?MvRxState
class?SomeViewModel(initialState:?SomeState)?:?MvRxViewModel<SomeState>(initialState)?{
????fun?setChecked(id:?Int)?{
????????setState?{
????????????copy(data?=?data.find?{?it.id?==?id?}?.checked?=?true)
????????}
????}
}
這樣做是不行的(也是不允許的),SomeState 的 data 雖然改變了,但對比上一次的SomeState,它們是相等的,因為前后兩個 SomeState 的 data 指向了同一塊內(nèi)存,必然是相等的,因此不會觸發(fā) View 更新。需要這么做:
fun?<T>?List<T>.update(newValue:?(T)?->?T,?finder:?(T)?->?Boolean)?=?indexOfFirst(finder).let?{?index?->????if?(index?>=?0)?copy(index,?newValue(get(index)))?else?this
}
fun?<T>?List<T>.copy(i:?Int,?value:?T):?List<T>?=?toMutableList().apply?{?set(i,?value)?}
//最好修改為如下定義,防止直接修改checked屬性
data?class?CheckedData(
????val?id:?Int,
????val?name:?String,
????//只讀的
????val?checked:?Boolean?=?false
)
class?SomeViewModel(initialState:?SomeState)?:?MvRxViewModel<SomeState>(initialState)?{
????fun?setChecked(id:?Int)?{
????????setState?{
????????????copy(data?=?data.update({?it.copy(checked?=?true)?},?{?it.id?==?id?}))
????????}
????}
}
這樣前后兩個 SomeState 的 data 指向不同的內(nèi)存,并且這兩個 data 確實不同,會觸發(fā)View 更新。
緊接著上一點來說,對于 State 而言,如果改變的值與上次的值相同是不會引起 View更新的,這是很合理的行為。但是,如果確實需要在State不變的情況下更新View(例如 State 中包含的某個屬性更新頻繁,你不想創(chuàng)造太多新對象;或者某些屬性只能在原來的對象上更新,例如 SparseArray,查看源碼后發(fā)現(xiàn),壓根兒就不能在State 的屬性中使用 SparseArray),那么 MvRx 的確沒有辦法。別忘了,MvRx 與Android Architecture Components 是并行不悖的,你總是可以使用 LiveData 去實現(xiàn)。對于 MutableLiveData 而言,設(shè)置相同的值還是會通知其觀察者,是MvRx 很好的補(bǔ)充。(但是,并不推薦這么做,因為使用 LiveData 會破壞 State 的不可變性,等于你繞開了 MvRx,用另外一種方式去傳遞數(shù)據(jù),這不利于數(shù)據(jù)的統(tǒng)一,也不利于數(shù)據(jù)界面的一致,不到萬不得已不推薦這么做。)
MvRx 構(gòu)建初始的 initialState 和 ViewModel 都使用的是反射,并且 MvRx 支持通過 Fragment 的 arguments 構(gòu)造 initialState,然而,大多數(shù)時候,ViewModel 的initialState是確定的,完全沒有必要通過反射獲取。如果使用 MvRx 規(guī)范中的fragmentViewModel 等方式獲取,反射是不可避免的,如果追求性能的話,可以通過拷貝fragmentViewModel的代碼,去除其中的反射,構(gòu)建自己的獲取ViewModel的方法。
雖說 MvRx 為 ViewModel 的構(gòu)建提供了工廠方法,并且這些工廠方法主要目的也是為了依賴注入,但實際上如果真的結(jié)合dagger依賴注入的話,你會發(fā)現(xiàn)構(gòu)造ViewModel 變得比較麻煩。而且這種做法并沒有利用 dagger multiBindings 的優(yōu)勢。實際上dagger可以為ViewModel提供非常友好且便利的ViewModelProvider.Factory類(這在Android Architecture Components的sample中已經(jīng)有展示),但是MvRx卻沒有提供一種方法來使用自定義的ViewModelProvider.Factory類(見Issues)。
在我看來,MvRx 最大的特點是響應(yīng)式,最大的問題也是響應(yīng)式。因為這種開發(fā)模式,與我們之前培養(yǎng)的命令式的開發(fā)思維是沖突的,開始的時候總會有種不適應(yīng)感。最重要的是切換我們的思維方式。
總結(jié)
總的來說,MvRx 提供了一種 Android 更純粹響應(yīng)式開發(fā)的可能性。并且以 Airbnb 的實踐來看,這種可能性已經(jīng)被擴(kuò)展到相當(dāng)廣的范圍。MvRx 最適合于那些復(fù)雜的RecyclerView 界面,通過結(jié)合 Epoxy,不僅可以大大提高開發(fā)效率,而且其提供的響應(yīng)式思想可以大大簡化我們的思維。其實,有了 Epoxy 的幫助,絕大部分界面都可以放入RecyclerView 中。對于不適宜使用 RecyclerView 的界面,或者 RecyclerView 之外的一些界面元素,MvRx 至少也提供了與 Android Architecture Components 相似的能力,并且其與 RxJava 的結(jié)合更加的友好。
MvRx 的出現(xiàn)非常符合安迪-比爾定律,硬件的升級遲早會被軟件給消耗掉,或者換種更積極的說法啊,正是因為硬件的發(fā)展才給了軟件開發(fā)更多的創(chuàng)造力。想想 MvRx,由于 State是 Immutable 的,每次更新 View 必然會產(chǎn)生新的 State;想實現(xiàn)真正的響應(yīng)式,也必然需要浪費(fèi)更多的計算力,去幫我們計算界面真正更新的部分(實際上我們是可以提前知曉的)。但我覺得這一切都是值得的,畢竟這些許的算力對于現(xiàn)在的手機(jī)來說不值一提,但是對于“人”的效率的提升卻是巨大的。還是那句話,最關(guān)鍵的因素還是人啊!
總結(jié)
以上是生活随笔為你收集整理的Airbnb开源框架,真响应式架构——MvRx的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [BZOJ]2142 礼物 扩展Luca
- 下一篇: 【STM32】输入捕获实验原理