Android 百度面试Evenbus3.0源码解析 3分钟手写最牛框架 看完成大神

 

1.Evenbus的新旧版本使用区别

2.Evenbus源码解读之注解框架

3.Evenbus源码解读之索引用法

4.Evenbus源码解读之粘性事件 实现

5.Evenbus源码解读之不同线程通信

 

 

新版本和旧版本的区别:

    接收到消息之后的处理方式,在2.x版本中,注册这些消息的监听需要区分是否监听黏性(sticky)事件,监听EventBus事件的模块需要实现以onEvent开头的方法。如今3.0改为在方法上添加注解的形式: 

EventBus的四种线程模型(ThreadMode) 

POSTING(默认):如果使用事件处理函数指定了线程模型为POSTING,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起应用程序无响应(ANR)。

MAIN:事件的处理会在UI线程中执行。事件处理时间不能太长,长了会ANR的。

BACKGROUND:如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。

ASYNC:无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行,同样,此事件处理函数中禁止进行UI更新操作。

粘性事件 

   之前说的使用方法,都是需要先注册(register),再post,才能接受到事件;如果你使用postSticky发送事件,那么可以不需要先注册,也能接受到事件,也就是一个延迟注册的过程。 

   普通的事件我们通过post发送给EventBus,发送过后之后当前已经订阅过的方法可以收到。但是如果有些事件需要所有订阅了该事件的方法都能执行呢?例如一个Activity,要求它管理的所有Fragment都能执行某一个事件,但是当前我只初始化了3个Fragment,如果这时候通过post发送了事件,那么当前的3个Fragment当然能收到。但是这个时候又初始化了2个Fragment,那么我必须重新发送事件,这两个Fragment才能执行到订阅方法。 

   粘性事件就是为了解决这个问题,通过 postSticky 发送粘性事件,这个事件不会只被消费一次就消失,而是一直存在系统中,知道被 removeStickyEvent 删除掉。那么只要订阅了该粘性事件的所有方法,只要被register 的时候,就会被检测到,并且执行。订阅的方法需要添加 sticky = true 属性。

 

粘性事件 的用法,可以先发事件,后面啥时候注册啥时候实现收到消息

 粘性事件处理:粘性事件是你可以不需要先注册, 也能接受到事件。就是组件在发布完事件后其他组件再实例化仍然能处理该事件

 

下面4个方法会同时接受到,可以通过事件的类型或者名字区别 

 

 

我们可以看到注解@Subscribe有三个参数,threadMode为回调所在的线程,priority为优先级,sticky为是否接收黏性事件

//3.0版本

@Subscribe(threadMode = ThreadMode.POSTING, priority = 0, sticky = true)

public void handleEvent(DriverEvent event) {

    Log.d(TAG, event.info);

}

//跳转到父类中继续查找 findState.moveToSuperclass();

有这样一个场景:我们的EventModel A 继承自EventModel B,并实现了接口EventInterface C,此时在订阅类中,三个订阅方法参数分别为:EventModel A, EventModel B , EventInterface;最后在发布事件 EventModel A, 此时的结果应该是:三个订阅方法都能够收到事件。但是如果我们只想让参数为EventModel A订阅方法收到事件,应该怎么做? 详情看代码描述

 @Subscribe(threadMode = ThreadMode.MAIN , sticky = true)

    public void onEventObject(EventModelA object) {

        Log.e("-----------","我是子类");

    }

 

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)

    public void onEventObject(EventModelB event) {

        Log.e("-----------","我是父类");

    }

 

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)

    public void onEventObject(EventInterfaceC event) {

        Log.e("-----------","我是父接口");

    }

 

EventBus.getDefault().postSticky(new EventModelA ());

 

关于EventBus的添加,该功能是在EventBus3.0之后添加的新功能,据说能大幅提升性能?为什么能提升性能呢?

  1. EventBus索引的添加:可以自己直接定义.和不是索引后面的方法一样,前面不一样

添加索引的方法是:

 

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build(); eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {

        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);

        if (subscriberMethods != null) {

            return subscriberMethods;

        }

        //判断是否添加索引

        if (ignoreGeneratedIndex) {

          //没有添加索引

            subscriberMethods = findUsingReflection(subscriberClass);

        } else { //默认没有添加索引

            subscriberMethods = findUsingInfo(subscriberClass);

        }

        if (subscriberMethods.isEmpty()) {

            throw new EventBusException("Subscriber " + subscriberClass

                    + " and its super classes have no public methods with the @Subscribe annotation");

        } else {

            METHOD_CACHE.put(subscriberClass, subscriberMethods);

            return subscriberMethods;

        }

    }

 

我们可以知道,其实消耗时间的重头部分就是在注册的时候,因为注册的时候(没添加索引),需要通过Java的反射获取注册类的订阅方法相关信息,这部分是非常耗时间的,它发生在运行过程。但是添加完了索引之后,会在编译的时候生成一个类,该类包含了注册类的相关信息,发生在编译过程。

 

流程

1.regist 得到subscriberMethods 方法存放在map集合中

2.post方法:通过key值遍历,找到对应的方法

3.invokeSubscriber 根据当前的线程状态和订阅方法,通过调用反射来触发指定的观察者的订阅方法

 

evenbu和广播的区别?

BroadcastReceiver是什么鬼?在Android中广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器)。

  EventBus又是什么鬼呢?EventBus是一个发布 / 订阅的事件总线。简单点说,就是两人约定好怎么通信,一人发布消息,另外一个约定好的人立马接收到你发的消息。EventBus就可以帮减少很多事,不管你在任何地方任何位置发布一个事件,接收者都能立马接收到你的消息,不用你考虑android子线程操作UI线程的问题。

 

  一、广播作为Android组件间的通信方式,可以使用以下场景:

  1、同一app内部的同一组件内的消息通信(单个或多个线程之间);

  2、同一app内部的不同组件之间的消息通信(单个进程);

  3、同一app具有多个进程的不同组件之间的消息通信;

  4、不同app之间的组件之间消息通信;

  5、Android系统在特定情况下与App之间的消息通信。

 

  二、以上的场景,在实际应用中的适用性:

  1、同一app内部的同一组件内的消息通信(单个或多个线程之间),实际应用中肯定是不会用到广播机制的(虽然可以用),无论是使用扩展变量作用域、基于接口的回调还是Handler-post/Handler-Message等方式,都可以直接处理此类问题,若适用广播机制,显然有些“杀鸡牛刀”的感觉;

  2、同一app内部的不同组件之间的消息通信(单个进程),对于此类需求,在有些教复杂的情况下单纯的依靠基于接口的回调等方式不好处理,此时可以直接使用EventBus等,相对而言,EventBus由于是针对统一进程,用于处理此类需求非常适合,且轻松。

  3、其他情形,由于涉及不同进程间的消息通信,此时根据实际业务使用广播机制会显得非常适宜。

 

三、BroadcastReceiver的具体实现流程如下:

1、广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;

  2、广播发送者通过binder机制向AMS发送广播;

  3、AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;

  4、消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。

 

  四、使用EventBus框架具体流程如下:

  1、初始化时注册EventBus.getDefault().register(this);

  2、用完之后注销EventBus.getDefault().unregister(this);

  3、中间过程主要就是消息推送和接收,通过EventBus.getDefault().post(param)推送,通过onEventMainThread(param),onEventPostThread(param),onEventBackgroundThread(param),onEventAsync(param)接收并处理。

 

由此看来,广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。显然,整体流程与EventBus非常类似。

以上就是BroadcastReceiver和EventBus区别介绍,希望对你有帮助。

 

EventBus不论从使用方式和实现方式上都是非常值得我们学习的开源项目,可以说是目前消息通知里最好用的项目。但是业内对EventBus的主要争论点是在于EventBus使用反射会出现性能问题,实际上在EventBus里我们可以看到不仅可以使用注解处理器预处理获取订阅信息,EventBus也会将订阅者的方法缓存到METHOD_CACHE里避免重复查找,所以只有在最后invoke()方法的时候会比直接调用多出一些性能损耗。

 

而且相比旧版的2.x,现在新版的EventBus 3.0,订阅者已经没有固定的处理事件的方法了,onEvent、onEventMainThread、onEventBackgroundThread、onEventAsync都没有了,现在支持处理事件的方法名自定义,但必须public,只有一个参数,然后使用注解@Subscribe来标记该方法为处理事件的方法,ThreadMode和priority也通过该注解来定义。在subscriberMethodFinder中,通过反射的方式寻找事件方法。使用注解,用起来才更爽。

 

问题:

1.Evenbus可以实现进程间的通信吗?

2.Evenbug和广播的区别是什么?

3.Eventbus的发送消息和消息处理是和Eventbus实例有关的, 是无法跨进程传递消息的; 如果涉及到进程间通讯, 还是要使用android系统的接口

4.怎么实现子模块和主模块的通信==粘性事件

5在EventBus中,使用@Subscribe注解的时候指定的ThreadMode是如何实现在不同线程间传递数据的?

6.使用注解和反射的时候的效率问题,是否会像Guava的EventBus一样有缓存优化

7.黏性事件是否是通过内部维护了之前发布的数据来实现的,是否使用了缓存?

8.注解

9.EventBus索引的添加

10.黏性事件是怎么实现的

 

  1. 在EventBus中,使用@Subscribe注解的时候指定的ThreadMode是如何实现在不同线程间传递数据的?

要求主线程中的事件通过Handler来实现在主线程中执行,非主线程的方法会使用EventBus内部的ExecutorService来执行。实际在触发方法的时候会根据当前线程的状态和订阅方法的ThreadMode指定的线程状态来决定何时触发方法。非主线程的逻辑会在post的时候加入到一个队列中被随后执行。

  1. 使用注解和反射的时候的效率问题,是否会像Guava的EventBus一样有缓存优化?

内部使用了缓存,确切来说就是维护了一些映射的关系。但是它的缓存没有像Guava一样使用软引用之类方式进行优化,即一直是强引用类型的。

  1. 黏性事件是否是通过内部维护了之前发布的数据来实现的,是否使用了缓存?

黏性事件会通过EventBus内部维护的一个事件类型-黏性事件的哈希表存储,当注册一个观察者的时候,如果发现了它内部有黏性事件监听,会执行post类似的逻辑将事件立即发送给该观察者。


 

总结:

通过上述代码分析,原理可以总结两点:

1:源码架构:实际上维护的就是两个Map集合,分别是:以订阅方法的参数class为key, 然后存储相关订阅信息,value也就是整个应用以该Class类为参数的订阅类信息;另一个是以订阅类为key, 此时value为该订阅类的所有方法。

2:提高性能:第一点是关闭父类以及接口查找分发事件;第二点 添加索引,索引添加的原理就是提前在编译的时候加载好注册类的相关信息。

 

最后我们从开发者的角度来总结一下EventBus的工作原理

订阅逻辑

1、首先用register()方法注册一个订阅者

2、获取该订阅者的所有订阅的方法

3、根据该订阅者的所有订阅的事件类型,将订阅者存入到每个以 事件类型为key 以所有订阅者为values的map集合中

4、然后将订阅事件添加到以订阅者为key 以订阅者所有订阅事件为values的map集合中

4.1、如果是订阅了粘滞事件的订阅者,从粘滞事件缓存区获取之前发送过的粘滞事件,响应这些粘滞事件。

事件发送逻辑

1、首先获取当前线程的事件队列

2、将要发送的事件添加到事件队列中

3、根据发送事件类型获取所有的订阅者

4、根据响应方法的执行模式,在相应线程通过反射执行订阅者的订阅方法

取消逻辑

1、首先通过unregister方法拿到要取消的订阅者

2、得到该订阅者的所有订阅事件类型

3、遍历事件类型,根据每个事件类型获取到所有的订阅者集合,并从集合中删除该订阅者

4、将订阅者从步骤2的集合中移除

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值