前言
Airbnb 最近开源了一个库,他们称之为Android界的Autopilot——MvRx(读作mavericks)。这个库其实并不“单纯”,它其实是一个架构,已经被应用在了Airbnb几乎所有的产品上。
这个库综合运用了以下几种技术
- Kotlin (MvRx is Kotlin first and Kotlin only)
- Android Architecture Components
- RxJava
- React (概念上的)
- Epoxy (可选)
(除了React、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;想实现真正的响应式,也必然需要浪费更多的计算力,去帮我们计算界面真正更新的部分(实际上我们是可以提前知晓的)。但我觉得这一切都是值得的,毕竟这些许的算力对于现在的手机来说不值一提,但是对于“人”的效率的提升却是巨大的。还是那句话,最关键的因素还是人啊!
网友评论