Android真響應式開發-MvRx
前言
Airbnb 最近開源了一個庫,他們稱之為Android界的Autopilot—— ofollow,noindex">MvRx (讀作mavericks)。這個庫其實並不“單純”,它其實是一個架構,已經被應用在了Airbnb幾乎所有的產品上。
這個庫綜合運用了以下幾種技術
- Kotlin (MvRx is Kotlin first and Kotlin only)
- Android Architecture Components
- RxJava
- React (概念上的)
- Epoxy (可選)
(除了Epoxy,你最好對上面的技術比較熟悉,不然可能不知所云)
光看這個清單,也知道事情並不簡單。利用這個庫我們可以方便地構建出MVVM架構的APP,讓開發更加的簡單、高效。
1. 真響應式開發
響應式(React)開發現在很流行,響應式開發的定義其實我也不知道,但是其核心內容很簡單,就是完全通過資料去驅動UI的顯示,我們要做的就是更新資料,對應的UI就會正確地顯示出來(多麼美好的願景)。
但是一直到Android Architecture Components出來之前,我認為Android並沒有成熟的響應式開發方案。的確利用DataBinding庫,我們實現一些響應式的概念,但是DataBinding庫還是有些弱,並不能真正地實現響應式,還是要有很多命令式的方式去更新UI。直到17年Google寄出了Android Architecture Components,Android才有了成熟的響應式開發方案。
1.1 命令式MVP與響應式MVVM
MVP模式在MVC模式被Android界否定之後就一直很流行,現在依然如此。因為它比較好理解。其核心思想是,通過介面隔離資料與顯示,資料的變動通過介面回撥的方式去通知介面更新。這正是典型的命令式M-V(資料-顯示)連結。這種模式並沒有什麼大問題,只是有一些不太方便之處,主要體現在M-V的緊密連結,導致複用比較困難,要麼View層需要定義不必要的介面(這樣Presenter可以複用),要麼就需要為幾乎每個View都定義一個對應的Presenter,想想都心累。
MVVM模式在我看來是對MVP模式的些許改良,不同於MVP通過介面的方式來隔離資料與顯示,MVVM是使用觀察者的方式來隔離資料與顯示,這已經接近響應式的理念。以Android Architecture Components構建的MVVM模式為例,View通過觀察LiveData來驅動介面更新,如果從ViewModel的角度來看,這就是響應式,只是在View層,介面的更新往往還是需要命令式的方式(結合DataBinding的話,會好一些)。MVVM帶來的主要好處是打破了M-V的緊密連結,ViewModel複用變得很簡單,View層需要什麼資料觀察什麼資料即可。
以我的實踐來看Android Architecture Components構建的MVVM的主要問題是,RxJava與LiveData的銜接並不方便(關於這個問題,我有很多要吐槽的,但是這跟這篇文章並不相關,所以略略略),還有就是按照Google給出的sample,資料載入的狀態需要和資料本身打包在一起,然後通過LiveData傳遞出去,這真的不是一個好的想法。我在實踐中是在Subscriber的onSubscribe,onNext,onError方法中分別對不同的MutableLiveData賦值,然後在View中去觀察這些MutableLiveData來更新介面的。說實話,這很醜陋,但是比Google給出的sample要方便許多。
1.2 MvRx的真響應式MVVM
MvRx構建的MVVM模式,完美地解決了上述的問題。MvRx放棄了LiveData,使用State來通知View層資料的改變(當然仍然是可感知生命週期的)。MvRx可以方便地把RxJava Observable的請求過程包裝成Ansyc類,不僅可以改變State來通知View層,而且也包含了資料載入的狀態(成功、失敗、載入中等)。如果結合Airbnb的另一個開源庫Epoxy,那麼幾乎可以做到真正的響應式,即View層在資料改變時僅僅描述當前資料狀態下介面的樣子,Epoxy可以幫我們實現與之前資料狀態的比較,然後找出差別,僅更新那些有差別的View部分。這是對MvRx的大致描述。下面來看看MvRx是如果使用的。
2. MvRx的使用
2.1 MvRx的重要概念
State
包含介面顯示的所有資料,實現類需是繼承自 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
State的作用是承載資料,並且應該包含有介面顯示的所有資料。當然可以對介面進行拆分,使用多個ViewModel,也就是多個State共同決定介面的顯示。
可以把MvRx的State類比成Architecture Components中的LiveData,它們的相同點是都可以被View觀察,不同點是,State的改變會觸發View的 invalidate()
方法,從而通知介面重繪。
ViewModel
就像Architecture Components中的ViewModel一樣,MvRx的ViewModel包含有除了介面顯示之外的業務邏輯。此外,最關鍵的一點是,ViewModel還包含有一個State,ViewModel可以改變State的狀態,然後View可以觀察State的狀態。實現類需繼承 BaseMvRxViewModel
,並且必須向 BaseMvRxViewModel
傳遞 initialState
(代表了View的初始狀態)。像是這樣
class TasksViewModel(initialState: TasksState) : BaseMvRxViewModel<TasksState>(initialState)
View
一般而言是一個繼承自 BaseMvRxFragment
的Fragment。 BaseMvRxFragment
實現了介面 MvRxView
,這個介面除了是一個 LifecycleOwner
外,還有一個 invalidate()
方法,每當ViewModel的state發生改變時 invalidate()
方法都會被呼叫。View也可以觀察State中的某個或某幾個屬性的變化,View是沒辦法改變State狀態的,只有ViewModel可以改變State的狀態。
Async
代表了資料載入的狀態。 Async
是一個Kotlin sealed class,它有四種類型: Uninitialized
, Loading
, Success
, Fail
(包含了一個名為 error
的屬性,可以獲取錯誤型別)。 Async
過載了操作符 invoke
,除了在 Success
返回資料外,其它情況下都返回null:
var foo = Loading() println(foo()) // null foo = Success<Int>(5) println(foo()) // 5 foo = Fail(IllegalStateException("bar")) println(foo()) // null
在ViewModel中可以通過擴充套件函式 execute
把 Observable<T>
的請求過程包裝成 Asnyc<T>
,這可以方便地表示資料獲取的狀態。
以上四個核心概念是怎麼聯絡到一起的呢?請看下圖:

MvRx
圖中沒有包含 Asnyc
, State
可包含若干個 Asnyc
,用來表示資料載入的狀態,便於顯示Loading或者載入錯誤資訊。
按照理想情形,View不需要主動觀察State,State的任意改變都會呼叫View的invalidate方法,在invalidate方法中根據當前的State(在View中通過ViewModel的withState方法獲取State)直接重繪一下View即可。然而這太過於理想,實際上可以通過selectSubscribe,asyncSubscribe等方法觀察State中某個屬性的改變,根據特定的屬性更新View的特定部分。
以上是MvRx的四個核心概念。下面以官方sample為例,展示一下MvRx應該怎樣使用。
2.2 如何使用
ToDo Sample,拜Google所賜,每個架構都得實現一下這個sample。架構界的Hello World。介面張這個樣子。

ToDo
以下以首介面為例,介紹應該如何使用MvRx。
2.2.1 State的使用
//待辦事的定義,包含有id, title, description以及是否完成標誌complete 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, //代表請求的狀態 val isLoading: Boolean = false, //是否顯示Loading val lastEditedTask: String? = null //上次編輯的待辦事ID ) : MvRxState
State包含了這個介面要顯示的所有資料。
2.2.2 ViewModel的使用
具體的業務邏輯並不重要,主要看ViewModel是如何定義的。
/** * 必須有一個initialState * source是資料來源,可以是資料庫,也可以是網路請求等(例子中是資料庫) **/ class TasksViewModel(initialState: TasksState, private val source: TasksDataSource) : MvRxViewModel<TasksState>(initialState) { //工廠方法,必須實現MvRxViewModelFactory介面,主要用途是通過依賴注入傳入一些引數來構造ViewModel companion object : MvRxViewModelFactory<TasksState> { /** * 工廠方法,必須實現MvRxViewModelFactory介面,主要用途是通過依賴注入傳入一些引數來構造ViewModel * TasksState是MvRx幫我們構造的(通過反射) **/ @JvmStatic override fun create(activity: FragmentActivity, state: TasksState): BaseMvRxViewModel<TasksState> { //這裡並沒有使用依賴注入,直接獲取資料庫 val database = ToDoDatabase.getInstance(activity) val dataSource = DatabaseDataSource(database.taskDao(), 2000) return TasksViewModel(state, dataSource) } } init { //方便除錯,State狀態改變時打印出來 logStateChanges() //初始載入任務 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的狀態 setState { copy(tasks = tasks.upsert(task) { it.id == task.id }, lastEditedTask =task.id) } //因為是資料庫操作,一般不會失敗,所以沒有理會資料操作的狀態 source.upsertTask(task) } //標記完成 fun setComplete(id: String, complete: Boolean) { setState { val task = tasks.findTask(id) ?: return@setState this if (task.complete == complete) return@setState this copy(tasks = tasks.copy(tasks.indexOf(task), task.copy(complete = complete)), lastEditedTask = id) } 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實現了業務邏輯,其核心作用就是與Model層(這裡的source)溝通,並更新State。這裡有幾點需要說明:
- 按照MvRx的要求,ViewModel可以沒有工廠方法,這樣的話MvRx會通過反射構造出ViewModel(當然這一般不可能,畢竟ViewModel一般都包含Model層)。如果ViewModel包含有除initialState之外的其它構造引數,則需要我們實現工廠方法。如上所示,必須實現
MvRxViewModelFactory<T>
介面,並且介面方法create
必須被標記為@JvmStatic
,這是因為MvRx是通過反射來呼叫的工廠方法,標記為靜態方法會使建立ViewModel更加的高效。 - 更新State有兩種方法,
setState
或者execute
。setState
很好理解,直接更新State即可。因為State是immutable Kotlin data class,所以一般而言都是通過data class的copy
方法返回新的State。execute
是一個擴充套件方法,其定義如下
/** * Helper to map an observable to an Async property on the state object. */ 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 { // This will ensure that Loading is dispatched immediately rather than being posted to `backgroundScheduler` before emitting Loading. setState { stateReducer(Loading()) } return observeOn(backgroundScheduler) .subscribeOn(backgroundScheduler) .map { val success = Success(mapper(it)) success.metadata = successMetaData?.invoke(it) success as Async<V> } .onErrorReturn { Fail(it) } .subscribe { asyncData -> setState { stateReducer(asyncData) } } .disposeOnClear() }
execute
方法可以把 Observable
的請求過程包裝成 Async
,我們都知道訂閱 Observable
需要有 onNext
, onComplete
, onError
等方法, execute
就是把這些個方法包裝成了統一的 Async
類。前面已經說過, Async
是sealed class,只有四個子類: Uninitialized
, Loading
, Success
, Fail
。這些子類完美的描述了一次請求的過程,並且它們過載了 invoke
操作符( Success
情況下返回請求的資料,其它情況均為 null
)。因此經常看到這樣的樣板程式碼:
fun <T> Observable<T>.execute( stateReducer: S.(Async<T>) -> S ) /** * 根據上面execute的定義,我們傳遞過去的是State上的以Async<T>為引數的擴充套件函式 * 因此下面的it引數是指 Async<T>,it()是獲取請求的結果,tasks = it() ?: tasks 表示只在請求 Success時更新State **/ fun refreshTasks() { source.getTasks() //... .execute { copy(taskRequest = it, tasks = it() ?: tasks, lastEditedTask = null) } }
2.2.3 View的使用
abstract class BaseFragment : BaseMvRxFragment() { //activityViewModel是MvRx定義的獲取ViewModel的方式 //按照規範必須使用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的改變均會觸發 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的規範,View通過代理 activityViewModel
, fragmentViewModel
, existingViewModel
獲取ViewModel,這是因為,以這幾種方式獲取ViewModel,MvRx會幫我們完成如下幾件事:
-
activityViewModel
,fragmentViewModel
,existingViewModel
其實都是Kotlin的Lazy
子類,顯然會是懶載入。但是它不是真正的“懶”,因為在這些子類的建構函式中會新增一個對View生命週期的觀察者,在ON_CREATE
事件發生時會構造出ViewModel,也就是說ViewModel最晚到ON_CREATE
時即被構造完成。 - 通過反射構造出State,ViewModel。
- 呼叫ViewModel的
subscribe
方法,觀察State的改變,如果改變則呼叫View的invalidate
方法。
當State發生改變時,View的 invalidate
方法會被呼叫。 invalidate
被呼叫僅說明了State發生了改變,究竟是哪個屬性發生的改變並不得而知,按照MvRx的“理想”,哪個屬性發生改變並不重要,只要View根據當前的State“重繪”一下View即可。顯然這裡指的不是簡單地重繪整個介面,應該是根據當前State“描繪”當前介面,然後能比較與上次介面的差異,只更新差異部分。顯然這種“理想”太過於高階,需要有一個幫手來完成這項任務,於是就有了Epoxy(其實是先有的Epoxy)。
Epoxy簡單來說就是一個高階的RecyclerView,我們只需要定義某個資料在RecyclerView的ItemView上是如何顯示的即可,然後把一堆資料扔給Epoxy就行了。Epoxy會幫我們分析這次的資料跟上次的資料有什麼差別,只更新差別的部分。如此看來Epoxy真的是MvRx的絕佳助手。關於Epoxy有非常多的內容,去 Epoxy 瞭解更多。
Epoxy雖然“高階”,但也僅僅適用於RecyclerView。因此可以看到MvRx的例子中把所有介面的主要部分都以RecyclerView作為承載,例如,Loading出現在RecyclerView的頭部;如果介面是非滾動的,就把介面作為RecyclerView唯一的元素放入其中,等等。這都是為了使用Epoxy,使開發模式更加統一,並且更加接近於完全的響應式。但是總有些情形下介面不適合用RecyclerView展示,沒關係,我們還可以單獨觀察State中的某(幾)個屬性的改變(這幾乎與LiveData沒有差別)。例如:
//觀察兩個屬性的改變,任意一個屬性方式了改變都會呼叫 viewModel.selectSubscribe(TasksState::tasks, TasksState::lastEditedTask) { tasks, lastEditedTask -> //根據屬性值做更新 } //觀察Async屬性,可以傳入onSuccess、onFail引數 //和上面觀察普通屬性沒有區別,只是內部幫我們判斷了Async是否成功 viewModel.asyncSubscribe(TasksState::taskRequest, onFail = { coordinatorLayout.showLongSnackbar(R.string.loading_tasks_error) })
3. 問題
使用MvRx有幾個問題需要注意:
- State是immutable Kotlin data class,Kotlin幫我們生成了equals方法,在ViewModel中通過
setState
,execute
方法更新State時,只有更新後的State確實與上一次的State不相等時,View才會收到通知。經常犯的錯誤是這樣的:
data class CheckedData( val id: Int, val name: String, var checked: Boolean = false ) data class SomeState(val data: List<CheckedData> = emptyList()) : MvRxState class SomeViewModel(initialState: SomeState) : MvRxViewModel<BookSeriesState>(initialState) { fun setChecked(id: Int) { setState { copy(data = data.find { it.id == id }?.checked = true) } } }
這樣做是不行的,SomeState的data雖然改變了,但對比上一次的SomeState,它們是相等的,因為前後兩個SomeState的data指向了同一塊記憶體,必然是相等的,因此不會觸發View更新。需要這麼做:
fun <T> List<T>.update(value: T, finder: (T) -> Boolean) = indexOfFirst(finder).let { index -> if (index >= 0) copy(index, value) 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<BookSeriesState>(initialState) { fun setChecked(id: Int) { setState { val oldOne = data.find { it.id == id } ?: return@setState this val newOne = oldOne.copy(checked = true) copy(data = data.update(newOne) { it.id == id }) } } }
這樣前後兩個SomeState的data指向不同的記憶體,並且這兩個data確實不同,會觸發View更新。
- 緊接著上一點來說,對於State而言,如果改變的值與上次的值相同是不會引起View更新的,這是很合理的行為。但是,如果確實需要在State不變的情況下更新View(例如State中包含的某個屬性建立新物件的代價比較高,或者更新頻繁,你不想創造太多新物件),那麼MvRx的確沒有辦法。別忘了,MvRx與Android Architecture Components並行不悖,你總是可以使用LiveData去實現。對於MutableLiveData而言,設定相同的值還是會通知其觀察者,是MvRx很好的補充。
- MvRx構建初始的initialState和ViewModel都使用的是反射,並且MvRx支援通過Fragment的arguments構造initialState,然而,大多數時候,ViewModel的initialState是確定的,完全沒有必要通過反射獲取。如果使用MvRx規範中的
fragmentViewModel
等方式獲取,反射是不可避免的,如果追求效能的話,可以通過拷貝fragmentViewModel
的程式碼,去除其中的反射,構建自己的獲取ViewModel的方法。 - 雖說MvRx為ViewModel的構建提供了工廠方法,並且這些工廠方法主要目的也是為了依賴注入,但實際上如果真的結合dagger依賴注入的話,你會發現構造ViewModel變得比較麻煩。而且這種做法並沒有利用dagger multiBinding的優勢。實際上dagger可以為ViewModel提供非常友好且便利的
ViewModelProvider.Factory
類(這在Android Architecture Components的sample中已經有展示),但是MvRx卻沒有提供一種方法來使用自定義的ViewModelProvider.Factory
類(見 Issues ),我的做法還是定義自己的fragmentViewModel
等方法,去除反射,並且加入自己的ViewModelFactory
:
inline fun <T, reified VM : MvRxViewModel<S>, reified S : MvRxState> T.fragmentViewModel( viewModelClass: KClass<VM> = VM::class, crossinline keyFactory: () -> String = { viewModelClass.java.name } ) where T : BaseInjectableFragment, T : MvRxView = LifecycleAwareLazy(this) { ViewModelProviders.of(this, viewModelFactory).get(keyFactory(), viewModelClass.java) //.apply { subscribe(requireActivity(), subscriber = { postInvalidate() }) } //由於上述的subscribe被限制使用,這裡不能像原來那樣自動觀察State的改變,只能在View中手動觀察,並不是什麼大問題,可以寫在Fragment的基類中 } abstract class BaseInjectableFragment : BaseMvRxFragment() { @Inject lateinit var viewModelFactory: ViewModelFactory override fun onAttach(context: Context?) { super.onAttach(context) val activity = context as BaseActivity activity.daggerComponent.inject(this) } } @Singleton class ViewModelFactory @Inject constructor( private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>> ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { var creator: Provider<out ViewModel>? = creators[modelClass] if (creator == null) { for ((key, value) in creators) { if (modelClass.isAssignableFrom(key)) { creator = value break } } } if (creator == null) { throw IllegalArgumentException("unknown model class $modelClass") } try { return creator.get() as T } catch (e: Exception) { throw RuntimeException(e) } } } class MyViewModel @Inject constructor( private val repository: MyRepository ) : MvRxViewModel<MyState>(MyState())
完全去除了構造ViewModel時的反射,並且利用dagger方便地獲取ViewModel。只是有一點,不再支援通過Fragment的arguments初始化State。關於這個問題,首先我沒遇到過這種情形,其次,即使真的需要也可以選擇使用MvRx原來的方式獲取ViewModel,或者在Fragment中獲取arguments之後,再通知ViewModel改變其State。
- 上面說的問題主要是使用不便或者效能不佳,並不影響功能實現,這個問題真的是影響功能實現了。ViewModel中的各種
subscribe
方法最終都會呼叫subscribeLifecycle
方法:
private fun <T> Observable<T>.subscribeLifecycle( lifecycleOwner: LifecycleOwner? = null, subscriber: (T) -> Unit ): Disposable { if (lifecycleOwner == null) { return observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber).disposeOnClear() } val lifecycleAwareObserver = MvRxLifecycleAwareObserver( lifecycleOwner, //注意alwaysDeliverLastValueWhenUnlocked被設定為true alwaysDeliverLastValueWhenUnlocked = true, onNext = Consumer<T> { subscriber(it) } ) return observeOn(AndroidSchedulers.mainThread()).subscribeWith(lifecycleAwareObserver).disposeOnClear() }
如上, MvRxLifecycleAwareObserver
的 alwaysDeliverLastValueWhenUnlocked
方法被設定為 true
,光看這個屬性的名字就大概知道其意義了,也就是說 lifecycleOwner
從 locked
到 unlocked
的時候(常見的就是息屏亮屏,從後臺到前臺等),State的上一次的值就會被重新發射出去。這其實並沒有什麼,相同的State,按照理想情況,只是View重新描繪了一番,最後不會發生介面更新。但是,如果如果這個介面的更新指的是Toast一下呢?那必然每次 unlocked
的時候都會Toast。因為這是個private方法,所以沒有辦法改變它的實現,只能在View觀察State的時候記錄上次的State,防止相等的兩個State觸發介面的更新。關於這個問題我已經提了 Issues ,但是並沒有人理我......
- 在我看來,MvRx最大的特點是響應式,最大的問題也是響應式。因為這種開發模式,與我們之前培養的開發思維其實是衝突的,開始的時候總會有種不適應感。最重要的是切換我們的思維方式。
總結
總的來說,MvRx提供了一種Android更純粹響應式開發的可能性。並且以Airbnb的實踐來看,這種可能性已經被擴充套件到相當廣的範圍。MvRx最適合於那些複雜的RecyclerView介面,通過結合Epoxy,不僅可以大大提高開發效率,而且其提供的響應式思想可以大大簡化我們的思維。對於不適宜使用RecyclerView的介面,MvRx至少也提供了與Android Architecture Components相似的能力,並且其與RxJava的結合更加的友好。再退一步而言,MvRx與Android Architecture Components至少是並行不悖的,遇到MvRx比較麻煩的時候,我們可以考慮使用LiveData等方案。所以我的建議是,如果你使用的是Android Architecture Components架構的話,可以比較方便的切換到MvRx,關鍵是你也不會損失什麼,並且你保留了更加純粹響應式開發的可能性。
MvRx的出現非常符合安迪-比爾定律,硬體的升級遲早會被軟體給消耗掉,或者換種更積極的說法啊,正是因為硬體的發展才給了軟體開發更多的創造力。想想MvRx,由於State是Immutable的,每次更新View必然會產生新的State;想實現真正的響應式,也必然需要浪費更多的計算力,去幫我們計算介面真正更新的部分(實際上我們是可以提前知曉的)。但我覺得這一切都是值得的,畢竟這些許的算力對於現在的手機來說不值一提,但是對於“人”的效率的提升卻是巨大的。還是那句話,最關鍵的因素還是人啊!