• Android组件内核之间组件间通信方案(四)下篇


    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680
    本篇文章将继续从以下两个内容来介绍通信方案:

    • [ViewModel 与 View 的通信]
    • [ EventBus源码分析]

    一、ViewModel 与 View 的通信

    以一个用户信息修改界面为例,在请求服务器之前,必须先校验用户数据,而 Presenter 或 ViewModel 的职责就是显示和取消 Loading,以及将校验或服务器的返回结果展示到界面上。此外,如果一个 Dialog 正在显示,当配置变更后也应该恢复 Dialog。

     
    7273107-811787ff545742b0.png
    image

    Presenter 和 ViewModel 不应持有 View 的引用。

    在 MVP 架构中,我们经常需要定义一些契约类接口(Contract),View 实现 Contract.View 接口,Presenter 实现 Contract.Presenter 接口,在 Presenter 中不持有 Activity/Fragment 的引用,只持有 View 实例,这样可以方便地调用 View 接口暴露的方法。
    例如 EditProfileContract.kt

    interface EditProfileContract {
    
        interface view {
    
            fun setProgress(show: Boolean)
    
            fun showEmptyFirstNameError()
    
            fun showEmptyLastNameError()
        }
    
        interface presenter {
    
            fun saveProfile(firstName: String, lastName: String, bio: String, email: String, city: City, gender: String)
        }
    }
    
    

    但是,在 MVVM 架构中,ViewModel 不再持有 View 的引用,而是通过 LiveDataRxJava 向 View 层暴露数据。一旦 View 订阅了 ViewModel,它就开始接收数据更新。这看似很完美,但当 ViewModel 想要更新 View 状态,比如显示和取消 Loading,将数据校验或服务器结果反馈到 UI 界面上,会变得非常困难。

    解决方案

    ViewModel 中的 LiveData 或 Observable 越少越好。因此我们最好找到一种方法,可以封装需要传递给 View 层的数据和信息。在多数情况下,ViewModel 需要向 View 层暴露以下三种数据:

    • Data
    • Status
    • State
      下面将依次介绍。

    Data

    Data -- 就是需要在 View 上展示的内容,比如用户信息的 User 实体类,或社交 Feed 流中的列表项。

    val user = MutableLiveData<User>()
    val feeds = MutableLiveData<List<Feed>>()
    
    

    Status

    Status -- 可以是任何仅需传递一次的信息,如校验错误,网络异常,或者服务器错误。
    Status.Kt

    enum class Status {
        SUCCESS,
        ERROR,
        NO_NETWORK,
        EMPTY_FIRST_NAME,
        EMPTY_LAST_NAME,
        EMPTY_CITY,
        INVALID_URI
    }
    
    

    LiveData 没有提供任何开箱即用的方法,但在 Google 的官方示例中,有一个 SingleLiveEvent 的实现,可以解决这个问题。

    一个生命周期感知的被观察者,仅在订阅后发送新的更新,常用于导航和 Snackbar 消息等事件。
    这可以避免一些常见问题:在配置变更(如屏幕旋转)期间,如果观察者处于活动动态,SingleLiveEvent 将会发送更新事件。
    它继承于 MutableLiveData,是一个被观察者,即使对外暴露了 SingleLiveEvent#setValue()SingleLiveEvent#call() 方法,
    注意:只有一个观察者会受到更新通知。

    新建一个 SingleLiveEvent 用来向 View 层暴露 Status 数据。
    EditProfileViewModel.Kt

    private val status = SingleLiveEvent<Status>()
    
    fun getStatus(): LiveData<Status> {
        return status
    }
    
    fun handleImage(intent: Intent?) {
        intent?.data?.let {
            avatar.value = it.toString()
        } ?: run { status.value = Status.INVALID_URI }
    }
    
    

    View 只关心 Status 数据,并根据不同的状态或错误执行对应的逻辑。如下实例,我们能很方便地根据每个错误显示不同的 Toast 或 Snackbar。
    EditProfileFragment.Kt

    viewModel.getStatus().observe(this, Observer { handleStatus(it) })
    
    private fun handleStatus(status: Status?) {
        when (status) {
            Status.EMPTY_FIRST_NAME -> Toast.makeText(activity, "Please enter your first name!", Toast.LENGTH_SHORT).show()
            Status.EMPTY_LAST_NAME -> Toast.makeText(activity, "Please enter your last name", Toast.LENGTH_SHORT).show()
            Status.EMPTY_CITY -> Toast.makeText(activity, "Please choose your home city", Toast.LENGTH_SHORT).show()
            Status.INVALID_URI -> Toast.makeText(activity, "Unable to load the photo", Toast.LENGTH_SHORT).show()
            Status.SUCCESS -> {
                startActivity(HomeFragment.newIntent(activity))
                activity.finish()
            }
            else -> Toast.makeText(activity, "Something went wrong, please try again!", Toast.LENGTH_SHORT).show()
        }
    }
    
    

    State

    State -- 即 UI 状态,比如加载进度条和 Dialog 等,每次开始订阅 ViewModel 的数据时,ViewModel 应该把这些 UI 状态通知给 View 层。一种简单的做法是,我们可以创建一个数据类来保存这些状态。
    EditProfileState.Kt

    data class EditProfileState(
        var isProgressIndicatorShown: Boolean = false,
        var isCityDialogShown: Boolean = false,
        var isGenderDialogShown: Boolean = false)
    
    

    然后在 ViewModel 中创建一个 MutableLiveData,用来包装这个 EditProfileState。由于 ViewModel 只会暴露 LiveData 给 View 层,因此我们应该提供 setter 方法,便于 View 更新此状态。
    EditProfileViewModel.kt

    private val state = MutableLiveData<EditProfileState>()
    
    fun getState(): LiveData<EditProfileState> {
        return state
    }
    
    fun setProgressIndicator(isProgressIndicatorShown: Boolean) {
        state.value?.isProgressIndicatorShown = isProgressIndicatorShown
    }
    
    fun setCityDialogState(isCityDialogShown: Boolean) {
        state.value?.isCityDialogShown = isCityDialogShown
    }
    
    fun setGenderDialogState(isGenderDialogShown: Boolean) {
        state.value?.isGenderDialogShown = isGenderDialogShown
    }
    
    

    最后,根据上面的 State 状态数据,决定 Dialog 的显示和取消。
    EditProfileFragment.Kt

    viewModel.getState().observe(this, Observer { handleState(it) })
    
    private fun handleState(state: EditProfileState?) {
        if (state?.isCityDialogShown == true) {
            showCitySelectionDialog()
            return
        }
        if (state?.isGenderDialogShown == true) {
            showGenderSelectionDialog()
            return
        }
    }
    

    二、EventBus源码分析

    2.1EventBus简介

    2.1.1EventBus

    EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件。事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等。

    传统的事件传递方式包括:Intent、Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。可简化 Activities, Fragments, Threads, Services 等组件间的消息传递,可替代Intent、Handler、BroadCast、接口等传统方案,更快,代码更小,50K 左右的 jar 包,代码更优雅,彻底解耦。

    2.1.2概念

    事件(Event):又可称为消息,本文中统一用事件表示。其实就是一个对象,可以是网络请求返回的字符串,也可以是某个开关状态等等。事件类型(EventType)指事件所属的 Class。

    事件分为一般事件和 Sticky 事件,相对于一般事件,Sticky 事件不同之处在于,当事件发布后,再有订阅者开始订阅该类型事件,依然能收到该类型事件最近一个Sticky事件。

    订阅者(Subscriber):订阅某种事件类型的对象。当有发布者发布这类事件后,EventBus 会执行订阅者的onEvent 函数,这个函数叫事件响应函数。订阅者通过register 接口订阅某个事件类型,unregister 接口退订。订阅者存在优先级,优先级高的订阅者可以取消事件继续向优先级低的订阅者分发,默认所有订阅者优先级都为0。

    发布者(Publisher):发布某事件的对象,通过 post 接口发布事件。

    2.1.3、订阅者、发布者、EventBus 关系图
    EventBus 负责存储订阅者、事件相关信息,订阅者和发布者都只和 EventBus 关联。

     
    19956127-4cd14714930c50c9.png
     

    事件响应流程

    订阅者首先调用 EventBus的 register 接口订阅某种类型的事件,当发布者通过 post 接口发布该类型的事件时,EventBus 执行调用者的事件响应函数。

     
    19956127-b4fa47914f616261.png
     

    2.2、EventBus优势

    2.2.1、对比Java监听器接口(Listener Interfaces)

    在Java中,特别是Android,一个常用的模式就是使用“监听器(Listeners)”接口。在此模式中,一个实现了监听器接口的类必须将自身注册到它想要监听的类中去。这就意味着监听与被监听之间属于强关联关系。这种关系就使得单元测试很难进行开展。

    2.2.2、对比本地广播管理器(LocalBroadcastManager)

    另一项技术就是在组件间通过本地广播管理器(LocalBroadcastManager)进行消息的发送与监听。虽然这对于解耦有很好的帮助,但它的API不如EventBus那样简洁。此外,如果你不注意保持Intent extras类型的一致,它还可能引发潜在的运行时/类型检测错误。

    使用EventBus不仅使代码变得清晰,而且增强了类型安全(type-safe)。当用Intent传递数据时,在编译时并不能检查出所设的extra类型与收到时的类型一致。所以一个很常见的错误便是你或者你团队中的其他人改变了Intent所传递的数据,但忘记了对全部的接收器(receiver)进行更新。这种错误在编译时是无法被发现的,只有在运行时才会发现问题。

    而使用EventBus所传递的消息则是通过你所定义的Event类。由于接收者方法是直接与这些类实例打交道,所以所有的数据均可以进行类型检查,这样任何由于类型不一致所导致的错误都可以在编译时刻被发现。

    另外就是你的Event类可以定义成任何类型。通常会为了表示事件而显式地创建明确命名的类,你也通过EventBus发送/接收任何类。通过这种方法,你就不必受限于那些只能添加到Intent extras中的简单数据类型了。例如,你可以发送一个和ORM模型类实例,并且在接收端直接处理与ORM操作相关的类实例。

    2.3 EventBus使用

    2.3.1基本使用

     
    19956127-d6d4c63c1842dc40.png
     

    2.3.2具体案例

     
    19956127-032e4050f5d49569.png
     
     
    19956127-3389bf29cc43bdee.png
     
     
    19956127-b773dc116a7b59a3.png
     

    2.3.3 onEvent函数使用解析

    前一篇给大家装简单演示了EventBus的onEventMainThread()函数的接收,其实EventBus还有另外有个不同的函数,他们分别是:

    1、onEvent
    2、onEventMainThread
    3、onEventBackgroundThread
    4、onEventAsync

    这四种订阅函数都是使用onEvent开头的,它们的功能稍有不同,在介绍不同之前先介绍两个概念:告知观察者事件发生时通过EventBus.post函数实现,这个过程叫做事件的发布,观察者被告知事件发生叫做事件的接收,是通过下面的订阅函数实现的。
    onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
    onEventMainThread****:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中更新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
    onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
    onEventAsync****:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.

    2.3.4 EvetntBus3.0使用解析

    注册一般是在 onCreateonStart 里注册,尽量不要在 onResume,可能出现多次注册的情况,比如下面这个异常:

     
    19956127-c23bbf8c3d3b9481.png
     

    取消注册 要写到 onDestroy 方法里,不要写到 onStop 里,有时会出现异常的哦

    EventBus 3 和之前版本的 EventBus 不兼容,这里采用注解的方法来接收事件,四种注解 @Subscrible、@Subscrible(threadMode = ThreadMode.ASYNC)、@Subscribe(threadMode = ThreadMode.BACKGROUND)、@Subscribe(threadMode = ThreadMode.MAIN)分别对应之前的 onEvent()、onEventAsync()、onEventBackground()、onEventMainThread()。

    EventBus 3 采用注解后,方法名没有限制了,参数只有一个,和发送者 post 的参数对应配对,在未声明 threadMode 时,默认的线程模式为 ThreadMode.POSTING,只有在该模式下才可以取消线程。

    由于可在任何地方都可以 post 一个事件,那么在不同线程之间传递事件,比如在工作线程传递一个事件更新UI线程中的一个控件,则需要注意 threadMode 的切换。

    如果遇到订阅事件无法执行的情况,分析后发现是订阅事件的 Activity 还未执行的原因。找到原因就好办了,这时候就需要用到 postSticky。

     
    19956127-f7d328612b217dda.png
     
     
    19956127-c675d02fbc169029.png
     
     
    19956127-a6d78e9d669d955c.png
     
     
    19956127-ece200eefbd6fc13.png
     

    2.4EventBus源码解析

    2.4.1 register

    EventBus.getDefault().register(this); EventBus.getDefault()其实就是个单例,和我们传统的getInstance一个意思:

     
    19956127-5d48632003d378b4.png
     

    使用了双重判断的方式,防止并发的问题,还能极大的提高效率。

    register公布给我们使用的有4个:

     
    19956127-ae6b3bac11526938.png
     
     
    19956127-545ff29547339324.png
     

    调用内部类SubscriberMethodFinder的findSubscriberMethods方法,传入了subscriber 的class,以及methodName,返回一个List<SubscriberMethod>。

    那么不用说,肯定是去遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,最后返回一个List。

     
    19956127-bcdda2b989d3e194.png
     
     
    19956127-84921e4d0d78a29c.png
     

    继续回到register:

     
    19956127-4419aa34459ded98.png
     
     
    19956127-6dd9ba6d1c4fddca.png
     
     
    19956127-44985598d2aecbcb.png
     

    到此,我们register就介绍完了。

    你只要记得一件事:扫描了所有的方法,把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ;value:CopyOnWriteArrayList<Subscription> )中;

    eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切。

    2.4.2 post

     
    19956127-f8a76cba411dc22a.png
     
     
    19956127-7502fd1d15dccab3.png
     
     
    19956127-08b534a1fa780fde.png
     
     
    19956127-4a21157637480121.png
     

    到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。分析这么久,一句话就说完了~~

    其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。

     
    19956127-73712f635d69fbf6.png
     
     
    19956127-7cc2d137c3fd6722.png
     
     
    19956127-583b8b39882058f7.png
     

    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680
    参考https://www.cnblogs.com/fuyaozhishang/p/9944653.html
    https://www.jianshu.com/p/36cb80026dc9

  • 相关阅读:
    进制转换
    客户信息管理系统
    ORACLE PL/SQL编程
    Oracle性能优化
    Django-admin
    PyCharm激活
    Java容器源码攻坚战--第一战:Iterator
    Java总结之映射家族--Map概览
    Java总结之容器家族--Collection
    2.安卓基础之Activity启动方式
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11953151.html
Copyright © 2020-2023  润新知