Airbnb开源框架,真响应式架构——MvRx

640?wx_fmt=png


今日科技快讯


2月21日凌晨,三星美国旧金山举行Galaxy Unpacked 2019新品发布会。正式发布年度旗舰Galaxy S10系列手机以及折叠手机Galaxy Fold,还推出了首款5G手机。

Galaxy Fold此前曾在2018年11月开发者大会上亮相,但当时并没宣布具体参数,今天这是它终于定妆出场。它的形态是两折的,仿佛一个钱包,外表是一块4.6英寸的狭长屏幕,打开后,内部是一块7.3英寸的屏幕。三星Galaxy S10系列一共包含三款机型,分别是S10、S10+、S10e。相比去年的S9系列,S10系列外观最大的变化在于采用Dynamic AMOLED屏幕设计,网友俗称“挖孔屏”,其中S10为6.1英寸2K分辨率双曲全视屏、S10+为6.4英寸2K分辨率双曲全视屏,S10e由于定位相比前两者更低,所以仍然使用1080P非双曲全视屏。


作者简介


明天就是周六啦,提前祝大家周末愉快!

本篇来自 珞泽珈群 的投稿文章,和大家分享了一个 Airbnb 的开源库,响应式架构的MvRx,希望对大家有所帮助!

 珞泽珈群 的博客地址

https://www.jianshu.com/u/679d8deee5ae


前言


Airbnb 最近开源了一个库,他们称之为 Android 界的 Autopilot——MvRx(ModelView ReactiveX 的缩写,读作 mavericks)。这个库并不“单纯”,它其实是一个架构,已经被应用在了 Airbnb 几乎所有的产品上。地址是:

https://github.com/airbnb/MvRx

这个库综合运用了以下几种技术

  • Kotlin (MvRx is Kotlin first and Kotlin only)

  • Android Architecture Components

  • RxJava

  • React (概念上的)

  • Epoxy (可选但推荐)

光看这个清单,也知道事情并不简单。利用这个库我们可以方便地构建出 MVVM 架构的APP,让开发更加的简单、高效。


真响应式架构


响应式(React)架构并没有什么定义,只是我觉得这么描述 MvRx 比较准确。这里所说的响应式架构是指,数据响应式以及界面响应式。数据响应式大体指数据以流的形式呈现(RxJava 那套东西),界面响应式大体指数据驱动界面更新,界面显示与数据状态保持一致。

以如上的定义来看,在 RxJava 的帮助下,几乎所有架构都可以实现数据响应式,因为数据响应式实际上是 Model 层的设计。但是界面响应式则基本上没有哪个框架实现了,最接近的应该是Android Architecture Components,但是Android Architecture Components 并没有保证界面与数据状态的一致,我们通过 LiveData 通知界面更新,只是把数据带给了界面,界面显示与数据状态并不一定是一致的(例如,LiveData 携带了下一页的数据,界面只是把该数据加到了 RecyclerView 的后面,数据并没有完全代表了当前界面的状态)。而 MvRx 真正实现了界面的响应式,所以我称之为真响应式架构。

如果你了解过 Flutter,那么 MvRx 很容易理解,因为两者都采用了响应式构建的思想,以下是关于 Flutter 的描述,把它替换为 MvRx 也基本上适用。

Flutter 组件采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用组件 (widget) 构建你的 UI。 组件描述了在给定其当前配置和状态时他们显示的样子。当组件状态改变,组件会重构它的描述 (description),Flutter 会对比之前的描述,以确定底层渲染树从当前状态转换到下一个状态所需要的最小更改。

由于 Flutter 的实现不受原生的限制,它完全用另外一套方式实现了界面的渲染,并且响应式在设计之初就是 Flutter 的核心,所以在 Flutter 中任何组件(可以理解为 Android 中的 View)都是响应式的,都可以确定它从当前状态转换到下一个状态所需要的最小更改,显然这一点在原生 Android 上是实现不了的。而 MvRx 在原生 Android 的基础上几乎实现了所有界面的响应式,这一点还是非常厉害的。

命令式 MVP 与响应式 MVVM

MVP 模式在 Android 界一直很流行,因为它比较好理解。其核心思想是,通过接口隔离数据与显示,数据的变动通过接口回调的方式去通知界面更新。这正是典型的命令式 M-V(数据-显示)链接。在这种模式下 View 层是完全被动的,完全受控于 Presenter 层的命令。这种模式并没有什么大问题,只是有一些不太方便之处,主要体现在 M-V 的紧密链接,导致复用比较困难,要么 View 层需要定义不必要的接口(这样 Presenter 可以复用),要么就需要为几乎每个 View 都定义一个对应的 Presenter,想想都心累。

不同于 MVP 通过接口的方式来隔离数据与显示,MVVM 是使用观察者的方式来隔离数据与显示。以 Android Architecture Components 构建的 MVVM 模式为例,View 通过观察 LiveData 来驱动界面更新。MVVM 带来的主要好处是打破了 M-V 的紧密链接,ViewModel 复用变得很简单,View 层需要什么数据观察什么数据即可。将 View 抽离为观察者,可以实现响应式 MVVM 架构,只是 View 本身不是响应式的。

以我的实践来看 Android Architecture Components 构建的 MVVM 的主要问题是,RxJava 与 LiveData 的衔接并不方便,还有就是按照 Google 给出的 sample,数据加载的状态需要和数据本身打包在一起,然后通过 LiveData 传递出去,我觉得这不是一个好的做法。我在实践中是在 Observer 的 onSubscribe,onNext,onError 方法中分别对不同的 MutableLiveData 赋值,然后在 View 中去观察这些 LiveData 来更新界面的。说实话,这很丑陋,但是比 Google 给出的 sample 要方便许多。

MvRx 的真响应式 MVVM

MvRx 构建的 MVVM 模式,完美地解决了上述的问题。MvRx 放弃了LiveData,使用State 来通知 View 层数据的改变(当然仍然是可感知生命周期的)。MvRx 可以方便地把RxJava Observable 的请求过程包装成 Ansyc 类,不仅可以改变 State 来通知 View 层,而且也包含了数据加载的状态(成功、失败、加载中等)。如果结合 Airbnb 的另一个开源库 Epoxy,那么几乎可以做到真正的响应式,即View层在数据改变时仅仅描述当前数据状态下界面的样子,Epoxy 可以帮我们实现与之前数据状态的比较,然后找出差别,仅更新那些有差别的 View 部分。这是对 MvRx 的大致描述。下面来看看 MvRx 是如果使用的。


MvRx的使用


MvRx 的重要概念

MvRx 有四个重要的概念,分别是 State、ViewModel、View 和 Async。

  • 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 //MvRxState 仅是一个标记接口

State 的作用是承载数据,并且应该包含有界面显示的所有数据。当然可以对界面进行拆分,使用多个State共同决定界面的显示。

State必须是不可变的(immutable),即State的所有属性必须是val的。只有ViewModel 可以改变 State,改变 State 时一般使用其 copy 方法,创建一个新的 State对象。

可以把 MvRx 的 State 类比成 Architecture Components 中的 LiveData,它们的相同点是都可以被 View 观察,不同点是,State 的改变会触发 View 的 invalidate()方法,从而通知界面重绘。

  • ViewModel

完全继承自 Architecture Components中的 ViewModel,ViewModel 包含有除了界面显示之外的业务逻辑。此外,最关键的一点是,ViewModel 还包含有一个State,ViewModel 可以改变 State 的状态,然后 View 可以观察 State 的状态。实现类需继承 BaseMvRxViewModel,并且必须向 BaseMvRxViewModel 传递 initialState(代表了View 的初始状态)。像是这样

class TasksViewModel(initialStateTasksState) : BaseMvRxViewModel<TasksState>(initialState)
  • View

一般而言是一个继承自 BaseMvRxFragment 的 Fragment。BaseMvRxFragment 实现了接口 MvRxView,这个接口有一个 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>,这可以方便地表示数据获取的状态(下面会有介绍)。

以上四个核心概念是怎么联系到一起的呢?请看下图:

640?wx_fmt=png

图中没有包含 Asnyc,State 可包含若干个 Asnyc,用来表示数据加载的状态,便于显示Loading 或者加载错误信息等。

按照理想情形,View 不需要主动观察 State,State 的任意改变都会调用 View 的invalidate方法,在 invalidate 方法中根据当前的 State(在 View 中通过 ViewModel 的withState 方法获取 State)直接重绘一下 View 即可。然而这太过于理想,实际上可以通过 selectSubscribe,asyncSubscribe 等方法观察 State 中某个属性的改变,根据特定的属性更新 View 的特定部分。

以上是 MvRx 的四个核心概念。下面以官方 sample 为例,展示一下 MvRx 应该怎样使用。

如何使用

ToDo Sample,架构界的 Hello World。界面长这个样子。

640?wx_fmt=gif

640?wx_fmt=gif

以下以首界面为例,介绍应该如何使用 MvRx。

  • 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 包含了这个界面要显示的所有数据。

  • ViewModel 的使用

具体的业务逻辑并不重要,主要看 ViewModel 是如何定义的。

/**
 * 必须有一个initialState
 * source是数据源,可以是数据库,也可以是网络请求等(例子中是数据库)
 **/

class TasksViewModel(initialState: TasksState, private val source: TasksDataSource) : MvRxViewModel<TasksState>(initialState) {
    //工厂方法,必须实现MvRxViewModelFactory接口
    companion object : MvRxViewModelFactory<TasksViewModel, TasksState> {
        /**
         * 主要用途是通过依赖注入传入一些参数来构造ViewModel
         * TasksState是MvRx帮我们构造的(通过反射)
         **/

        override fun create(viewModelContext: ViewModelContext, state: TasksState): BaseMvRxViewModel<TasksState> {
            //例子中并没有使用依赖注入,而是直接获取数据库
            val database = ToDoDatabase.getInstance(viewModelContext.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 {
            //没有这个任务,拉倒;this指之前的 State,直接返回之前的 State意思就是无需更新
            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。这里有几点需要说明:

  1. 按照 MvRx 的要求,ViewModel 可以没有工厂方法,这样的话 MvRx 会通过反射构造出 ViewModel(当然这一般不可能,毕竟 ViewModel 一般都包含 Model 层)。如果 ViewModel 包含有除 initialState 之外的其它构造参数,则需要我们实现工厂方法。如上所示,必须通过伴生对象实现 MvRxViewModelFactory 接口。

  2. 只能在ViewModel中更新State。更新State有两种方法,setState或者 execute。setState 很好理解,直接更新 State 即可。其定义如下

abstract class BaseMvRxViewModel<S : MvRxState{
    //参数是State上的扩展函数,会接收到上次 State的值
    protected fun setState(reducer: S.() -> S) {
        //...
    }
}

因为 State 是 immutable Kotlin data class,所以一般而言都是通过 data class 的 copy方法返回新的 State。execute 是一个扩展方法,其定义如下

abstract class BaseMvRxViewModel<S : MvRxState{
    /**
     * Helper to map an observable to an Async property on the state object.
     */

    //参数依然是State上的扩展函数
    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 就是把这些个方法包装成了统一的 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) }
}
  • 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(ViewModel被置于Activity中), fragmentViewModel(ViewModel被置于 Fragment 中), existingViewModel(从Activity中获取已存在的 ViewModel) 来获取ViewModel,这是因为,以这几种方式获取ViewModel,MvRx 会帮我们完成如下几件事:

  1. activityViewModel, fragmentViewModel, existingViewModel其实都是 Kotlin 的Lazy 子类,显然会是懒加载。但是它不是真正的“懒”,因为在这些子类的构造函数中会添加一个对 View 生命周期的观察者,在 ON_CREATE 事件发生时会构造出ViewModel,也就是说 ViewModel 最晚到 ON_CREATE 时即被构造完成(为了及早发出网络请求等)。

  2. 通过反射构造出 State,ViewModel。

  3. 调用 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——RecyclerView 的绝佳助手了解更多。

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)
    })


问题


使用 MvRx 有几个问题需要注意

State 是 immutable Kotlin data class,Kotlin 帮我们生成了equals方法(即调用每个属性的 equals 方法),在 ViewModel 中通过 setState,execute 方法更新State时,只有更新后的 State 确实与上一次的 State 不相等时,View 才会收到通知。经常犯的错误是这样的:

data class CheckedData(
    val id: Int,
    val name: String,
    var checked: Boolean = false
)

//List的equals方法的实现是,项数相同,并且每项都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 指向了同一块内存,必然是相等的,因此不会触发 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 指向不同的内存,并且这两个 data 确实不同,会触发View 更新。

紧接着上一点来说,对于 State 而言,如果改变的值与上次的值相同是不会引起 View更新的,这是很合理的行为。但是,如果确实需要在State不变的情况下更新View(例如 State 中包含的某个属性更新频繁,你不想创造太多新对象;或者某些属性只能在原来的对象上更新,例如 SparseArray,查看源码后发现,压根儿就不能在State 的属性中使用 SparseArray),那么 MvRx 的确没有办法。别忘了,MvRx 与Android Architecture Components 是并行不悖的,你总是可以使用 LiveData 去实现。对于 MutableLiveData 而言,设置相同的值还是会通知其观察者,是MvRx 很好的补充。(但是,并不推荐这么做,因为使用 LiveData 会破坏 State 的不可变性,等于你绕开了 MvRx,用另外一种方式去传递数据,这不利于数据的统一,也不利于数据界面的一致,不到万不得已不推荐这么做。)

MvRx 构建初始的 initialState 和 ViewModel 都使用的是反射,并且 MvRx 支持通过 Fragment 的 arguments 构造 initialState,然而,大多数时候,ViewModel 的initialState是确定的,完全没有必要通过反射获取。如果使用 MvRx 规范中的fragmentViewModel 等方式获取,反射是不可避免的,如果追求性能的话,可以通过拷贝fragmentViewModel的代码,去除其中的反射,构建自己的获取ViewModel的方法。

虽说 MvRx 为 ViewModel 的构建提供了工厂方法,并且这些工厂方法主要目的也是为了依赖注入,但实际上如果真的结合dagger依赖注入的话,你会发现构造ViewModel 变得比较麻烦。而且这种做法并没有利用 dagger multiBindings 的优势。实际上dagger可以为ViewModel提供非常友好且便利的ViewModelProvider.Factory类(这在Android Architecture Components的sample中已经有展示),但是MvRx却没有提供一种方法来使用自定义的ViewModelProvider.Factory类(见Issues)。

在我看来,MvRx 最大的特点是响应式,最大的问题也是响应式。因为这种开发模式,与我们之前培养的命令式的开发思维是冲突的,开始的时候总会有种不适应感。最重要的是切换我们的思维方式。


总结


总的来说,MvRx 提供了一种 Android 更纯粹响应式开发的可能性。并且以 Airbnb 的实践来看,这种可能性已经被扩展到相当广的范围。MvRx 最适合于那些复杂的RecyclerView 界面,通过结合 Epoxy,不仅可以大大提高开发效率,而且其提供的响应式思想可以大大简化我们的思维。其实,有了 Epoxy 的帮助,绝大部分界面都可以放入RecyclerView 中。对于不适宜使用 RecyclerView 的界面,或者 RecyclerView 之外的一些界面元素,MvRx 至少也提供了与 Android Architecture Components 相似的能力,并且其与 RxJava 的结合更加的友好。

MvRx 的出现非常符合安迪-比尔定律,硬件的升级迟早会被软件给消耗掉,或者换种更积极的说法啊,正是因为硬件的发展才给了软件开发更多的创造力。想想 MvRx,由于 State是 Immutable 的,每次更新 View 必然会产生新的 State;想实现真正的响应式,也必然需要浪费更多的计算力,去帮我们计算界面真正更新的部分(实际上我们是可以提前知晓的)。但我觉得这一切都是值得的,毕竟这些许的算力对于现在的手机来说不值一提,但是对于“人”的效率的提升却是巨大的。还是那句话,最关键的因素还是人啊!


640?wx_fmt=jpeg

展开阅读全文

没有更多推荐了,返回首页