Android真响应式开发——MvRx

作者: 珞泽珈群 | 来源:发表于2018-09-27 16:08 被阅读23次

    前言

    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中可以通过扩展函数executeObservable<T>的请求过程包装成Asnyc<T>,这可以方便地表示数据获取的状态。

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

    MvRx

    图中没有包含AsnycState可包含若干个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。这里有几点需要说明:

    1. 按照MvRx的要求,ViewModel可以没有工厂方法,这样的话MvRx会通过反射构造出ViewModel(当然这一般不可能,毕竟ViewModel一般都包含Model层)。如果ViewModel包含有除initialState之外的其它构造参数,则需要我们实现工厂方法。如上所示,必须实现MvRxViewModelFactory<T>接口,并且接口方法create必须被标记为@JvmStatic,这是因为MvRx是通过反射来调用的工厂方法,标记为静态方法会使创建ViewModel更加的高效。
    2. 更新State有两种方法,setState或者executesetState很好理解,直接更新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会帮我们完成如下几件事:

    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了解更多。
    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有几个问题需要注意:

    1. 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更新。

    1. 紧接着上一点来说,对于State而言,如果改变的值与上次的值相同是不会引起View更新的,这是很合理的行为。但是,如果确实需要在State不变的情况下更新View(例如State中包含的某个属性创建新对象的代价比较高,或者更新频繁,你不想创造太多新对象),那么MvRx的确没有办法。别忘了,MvRx与Android Architecture Components并行不悖,你总是可以使用LiveData去实现。对于MutableLiveData而言,设置相同的值还是会通知其观察者,是MvRx很好的补充。
    2. MvRx构建初始的initialState和ViewModel都使用的是反射,并且MvRx支持通过Fragment的arguments构造initialState,然而,大多数时候,ViewModel的initialState是确定的,完全没有必要通过反射获取。如果使用MvRx规范中的fragmentViewModel等方式获取,反射是不可避免的,如果追求性能的话,可以通过拷贝fragmentViewModel的代码,去除其中的反射,构建自己的获取ViewModel的方法。
    3. 虽说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。

    1. 上面说的问题主要是使用不便或者性能不佳,并不影响功能实现,这个问题真的是影响功能实现了。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()
    }
    

    如上,MvRxLifecycleAwareObserveralwaysDeliverLastValueWhenUnlocked方法被设置为true,光看这个属性的名字就大概知道其意义了,也就是说lifecycleOwnerlockedunlocked的时候(常见的就是息屏亮屏,从后台到前台等),State的上一次的值就会被重新发射出去。这其实并没有什么,相同的State,按照理想情况,只是View重新描绘了一番,最后不会发生界面更新。但是,如果如果这个界面的更新指的是Toast一下呢?那必然每次unlocked的时候都会Toast。因为这是个private方法,所以没有办法改变它的实现,只能在View观察State的时候记录上次的State,防止相等的两个State触发界面的更新。关于这个问题我已经提了Issues,但是并没有人理我......

    1. 在我看来,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;想实现真正的响应式,也必然需要浪费更多的计算力,去帮我们计算界面真正更新的部分(实际上我们是可以提前知晓的)。但我觉得这一切都是值得的,毕竟这些许的算力对于现在的手机来说不值一提,但是对于“人”的效率的提升却是巨大的。还是那句话,最关键的因素还是人啊!

    相关文章

      网友评论

      本文标题:Android真响应式开发——MvRx

      本文链接:https://www.haomeiwen.com/subject/pxweoftx.html