EventBus使用及源码分析

EventBus使用及源码分析

EventBus是为Android和Java设计的事件总线框架,主要为了实现组件间的通信。

一、 简单使用

官方文档有详细的使用说明。最简单的使用包括三个步骤:

  1. 定义事件类,如:
    public static class MessageEvent { /* Additional fields if needed */ }
  2. 监听事件:
@Subscribe
public void onMessageEvent(MessageEvent event) {/* Do something */};

注册:

@Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }
  1. 发送事件
EventBus.getDefault().post(new MessageEvent());

这样就可以完成基本的通信功能了。

二、扩展使用

1. 指定订阅者方法的线程、优先级、粘性事件

@Subscribe注解可以指定threadMode(线程模式)是一个枚举类,共有5个可选值,如下:

  1. POSTING
    发送者和订阅者的订阅方法在同一个线程中
  2. MAIN
    同步方法,post之后立马在主线程执行
  3. MAIN_ORDERED
    在post时把消息加入队列,再按照顺序在主线程执行
  4. BACKGROUND
    如果发送者不在主线程,就直接在这个线程执行,如果在主线程就新开一个线程执行
  5. ASYNC
    总是保持和发送者不在一个线程中执行

@Subscribe注解还可以指定priority(优先级),数字越大代表优先级越高,在同一种threadMode下,高优先级的订阅方法比低优先级的订阅方法先执行。
@Subscribe注解还可以指定sticky(是否为粘性事件)。当一个组件还没有被创建时,如果发送了粘性事件给他,那么当它被创建的时候会收到这个事件。

2. 更改默认配置

EventBus的扩展性很强,几乎所有的默认配置(比如:logger,线程池等)都可以更改,只是都设置了默认值,在不配置的情况下也可以正确使用。更改配置通过EventBus.builder()实现,更改完成后通过istallDefaultEventBus()方法把配置装在EventBus的单例上(该方法只能调用一次,多次会报错,并且要在使用EventBus单例之前调用,因此应该在Application中调用)。

EventBus.builder()
                .addIndex(new MyEventBusIndex()) // 生成新的类,减少反射使用,提高性能
                .eventInheritance(false) // 发送子类事件,订阅了父类事件的订阅者也能接收到
                .installDefaultEventBus(); //装载

3. 使用apt

3.0版本之前的EventBus大量使用发射(为了提高性能也有结合缓存),3.0之后引入注解处理器,在编译时生成新的Java类将运行时需要发射的信息存储在这个类中,避免运行时大量的反射。如果失败能自动回退到反射的方式,只是比较慢而已。
首先在module的build.gradle中添加注解处理器的依赖

dependencies {
    
    implementation 'org.greenrobot:eventbus:3.1.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'

}

再为EventBus提供所生成类的类名:

android {
    defaultConfig {
        
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : applicationId + '.MyEventBusIndex' ]
            }
        }
    }
}

我们将类名指定为包名+.MyEventBusIndex,这样在编译时apt扫描将生成这个类:
在这里插入图片描述
在这里插入图片描述
然后在Application中初始化,

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

如果有多个类,就需要多次调用addIndex()方法。

三、源码分析

EventBus如何做到当事件发出时通知所有的订阅者,又是如何做线程切换的?
为了理清楚思路,我们从一个具体的实例出发:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_main);
        EventBus.getDefault ().register (this);

        findViewById (R.id.main).setOnClickListener (v -> EventBus.getDefault ().post (new MainEvent ("Jack")));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy ();
        EventBus.getDefault ().unregister (this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMainEvent(MainEvent event){
        Log.d (TAG, "onMainEvent: " + event.getName ());
    }

    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onSubEvent(SubEvent event){
        Log.d (TAG, "onSubEvent: " + event.getMsg ());
        Log.d (TAG, "onSubEvent: thread id is:" + Thread.currentThread ().getId ());
    }

    @Subscribe
    public void onMainEventChild(MainEventChild event){
        Log.d(TAG, "onMainEventChild: ");
    }

    public static class MainEvent{
        private String name;

        public MainEvent(String name){
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public static class MainEventChild extends MainEvent{

        private int id;

        public MainEventChild(String name) {
            super(name);
        }

        public MainEventChild(String name, int id) {
            super(name);
            this.id = id;
        }
    }

    public static final class SubEvent{
        private List<String> msg;

        public SubEvent(List<String> msg) {
            this.msg = msg;
        }

        public List<String> getMsg() {
            return msg;
        }
    }
}

首先我们要知道一个类Subscription
在这里插入图片描述
MainActivity中,MainActivity.class就是subscriber,而onMainEvent(MainEvent event)方法、onSubEvent(SubEvent event)方法和onMainEventChild(MainEventChild event)就是subscriberMethod(Eventbus做了简单的封装),那么MainActivity中就有三个订阅Subscription实例。
然后看EventBus.java,这里面有几两个重要的map字段如下:
在这里插入图片描述

  1. Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType建立事件到订阅该事件类型的订阅的映射
  2. Map<Object, List<Class<?>>> typesBySubscriber建立订阅者到该订阅者订阅的所有事件类型的映射

(为什么要用两个map?)
EventBus通过在内部维护subscriptionsByEventTypetypesBySubscriber等map来存储订阅关系,当有订阅者注册或者解注册,就会更新这些map从而更新订阅关系,当发送一个事件时,EventBus就根据事件类型找到订阅这个类型事件的方法,再把这个事件分发给他们(也就是调用这些方法)。从而完成事件总线的任务。

1. getDefault()

静态的 getDefault()方法拿到单例
在这里插入图片描述
在这里插入图片描述
double check + volatile单例。

2. register(Object subscriber)

通过register(Object subscriber)注册订阅者。
在这里插入图片描述
既然新添加了订阅者,就必须要更新订阅关系,也就是更新subscriptionsByEventTypetypesBySubscriber这两个map。当找到这个订阅者所有的标注了@Subscribe的方法之后,就遍历这些方法,并调用subscribe(Object subscriber, SubscriberMethod subscriberMethod)方法更新map,如下:
在这里插入图片描述
在这里插入图片描述
这个方法的流程非常清晰

  1. 先更新subscriptionsByEventType。拿到事件类型之后,根据subscribersubscriberMethod新建一个Subscription订阅,从subscriptionsByEventType中找到订阅这个事件类型的订阅集合,如果为空就新建一个条目,不为空就按照这个订阅的级别放进订阅集合中(按照级别从高到低的顺序,方便以后事件的分发和拦截)
  2. 再更新typesBySubscriber。根据订阅者找到它订阅的事件类型,把这个事件类型加进去即可。
  3. 粘性事件的map更新,先跳过。

3. unregister(Object subscriber)

通过unregister(Object subscriber)解除订阅者的订阅。
在这里插入图片描述
解除订阅者同样要更新订阅关系,主要工作还是更新subscriptionsByEventTypetypesBySubscriber这两个map容器。

  1. 先更新subscriptionsByEventType。找到这个订阅者的所有订阅的事件类型,再遍历事件类型并调用unsubscribeByEventType(subscriber, eventType)完成subscriptionsByEventType的更新。
    在这里插入图片描述
  2. 再更新typesBySubscriber。直接把以subscriber为key的条目移除即可。

4. post(Object event)

通过post(Object event)将事件发送给订阅者,这个分发过程由EventBus完成。
先看一个EventBus的内部类PostingThreadState
在这里插入图片描述
这个类主要用来存储一个线程中事件的状态。
EventBus有一个ThreadLocal<PostingThreadState> currentPostingThreadState 字段,用来记录各个线程当前的状态,如下:
在这里插入图片描述
post()的过程其实就是拿到一个事件,根据事件类型找所有订阅这个事件的订阅,再调用每一个订阅的方法即可。只不过EventBus为了实现更多功能,内部做了很多细节的设置。
在这里插入图片描述
先拿到当前线程中的PostingThreadState,取出事件队列,把这个事件加进去。这时事件队列中至少有一个事件,因此PostingThreadState的所有字段都需要进行更新,所以先进行PostingThreadState的状态更新,然后遍历事件队列中所有的事件并调用postSingleEvent(Object event, PostingThreadState postingState)方法:
在这里插入图片描述
这个方法主要是为了调用postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)方法服务,如果在初始化设置了eventInheritancetrue,那么发送子类事件,订阅了父类事件的订阅者也能接收到,因此需要找到所有需要通知的事件类型(也就是该事件类型的所有父类),如果没有设置eventInheritance字段,默认是false,就会直接调用postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)方法。如果没找到订阅了这个事件类型的订阅,那就需要按照初始化是的配置进行相应的操作。看一下postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)方法:
在这里插入图片描述
这个方法很简单,找到这个事件类型的所有订阅,调用postToSubscription(Subscription subscription, Object event, boolean isMainThread) 方法,同时也要进行PostingThreadState的状态更新。看一下postToSubscription(Subscription subscription, Object event, boolean isMainThread) 方法:
在这里插入图片描述
就是根据不同的订阅模式分别处理。
先看POSTING模式,直接调用invokeSubscriber(Subscription subscription, Object event)方法:
在这里插入图片描述
这个方法则是直接反射调用订阅方法,也就是@Subscribe注解的方法。这样,从发送事件到通知到订阅方法的过程就完成了。
然后是MAIN模式,如果当前在主线程,和POSTING模式的处理方式一样,但是如果不再主线程,就要调用mainThreadPoster.enqueue(subscription, event)方法,mainThreadPoster是一个Poster接口类型的字段:
在这里插入图片描述
在这里插入图片描述
EventBus构造方法中mainThreadPoster被赋值:
在这里插入图片描述
MainThreadSupport也是接口,主要是为了区分Android环境和Java环境:
在这里插入图片描述
这个mainThreadSupport字段被赋值为builder.getMainThreadSupport(),看一下这个方法:
在这里插入图片描述
通过Logger.AndroidLogger.isAndroidLogAvailable()判断是不是Android环境,如果是就返回一个MainThreadSupport.AndroidHandlerMainThreadSupport实例并传入主线程的Looper。具体判断方法是通过查看是否有"android.util.Log"这个类来实现的,如果有,说明是Android环境:
在这里插入图片描述
所以最终的mainThreadPoster字段的类型是HandlerPoster
在这里插入图片描述
在这里插入图片描述
HandlerPoster本质是一个主线程的handler,内部维护PendingPostQueue(一个PendingPost链表),调用enqueue(Subscription subscription, Object event)就是把Subscriptionevent封装成PendingPost对象加入链表尾端(封装采用池化处理,防止大量重复创建对象),内部维护handlerActive标志位,表示handler是否是激活状态(当handler正在处理它的队列的时候,是激活状态,一旦事件队列处理完了,他就不处于激活状态),所以加入队列之后会去检测这个标志位,如果没有处于激活状态,则要激活,并且发送一个空消息去驱动handlerhandleMessage()方法执行:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看一下handleMessage(Message msg)方法:
在这里插入图片描述
根据handler机制这个方法执行在主线程,所以一定要在处理事件是保证不能阻塞主线程,因此在处理过程中用时超过10ms就要退出方法并通过发送空消息的方式重新激活handleMessage(Message msg)方法。对于每一个pendingPost调用eventBus.invokeSubscriber(pendingPost)方法,如下:
在这里插入图片描述
在这个方法中拿到订阅,再反射调用订阅方法,也就是@Subscribe注解的方法。这样,从发送事件到通知到订阅方法的过程就完成了。

MAIN_ORDERED模式则是直接把事件放进队列中,等待handler的调度,具体过程和MAIN模式不在主线程的处理方式相同。

BACKGROUND模式下,如果不是在主线程就直接执行,如果在主线程就调用backgroundPoster.enqueue(subscription, event)方法。backgroundPoster字段是BackgroundPoster类型的实例,而BackgroundPoster其实是一个Runnable
内部维护一个PendingPostQueue队列和状态标志位,enqueue(Subscription subscription, Object event)封装PendingPost并加入事件队列,如果没有激活,要激活,并把这个Runnable丢入线程池中,run()方法开启死循环处理事件队列中的事件(可以看出BACKGROUND模式的事件订阅方法激活一次其实是运行在同一个子线程中的)。
在这里插入图片描述
ASYNC模式,与BACKGROUND基本相同,但是这中模式下的Runnablerun()不会去循环拿事件,会直接丢入线程池中,保证异步:
在这里插入图片描述

5.subscriberMethodFinder.findSubscriberMethods(subscriberClass)

这个方法用于找到subscriberClass中所有被@Subscribe注解的方法:
在这里插入图片描述
流程很明白,先从缓存拿,如果没有就看是否使用apt,如果使用直接从新类中拿,否则通过反射获取。
先看findUsingReflection(Class<?> subscriberClass)方法:
在这里插入图片描述
这个方法的思路是从基类开始递归寻找父类中被@Subscribe注解的方法,并全部加入到findState中,最后从findState中取出来寻找的结果List<SubscriberMethod>
在这里插入图片描述
再看findUsingInfo(Class<?> subscriberClass)方法:
在这里插入图片描述
这个方法尝试从生成的类中拿到被@Subscribe注解的方法,如果失败的话,则会采用降级策略,使用反射查找。
至此,EventBus工作原理及流程基本分析完毕了。

四、 从源码中学到了什么?

  1. 大量重复使用反射的地方考虑使用缓存和apt技术生成新的类,减少运行时反射的使用,提高性能。
  2. 通过内部维护观察者和观察对象的映射关系,实现观察者模式。
  3. EventBus具有很强的可扩展性,通过builder模式配置各种配置,甚至是日志的TAG也是可配置的。
  4. 兼容Java环境和Android环境,运行时通过判断Android系统类是否在内存中从而确定是那种环境。(和retrofit2的处理方式很像)
  5. 线程创建和使用全部基于线程池,提高性能
  6. 频繁创建和销毁的对象使用对象池(如:PendingPost类的管理),提高性能。
  7. 主线程handler执行多个任务时可以采用超时重新调度的策略(如HandlerPoster类的handleMessage(Message msg)方法),避免主线程做太多事情造成掉帧或者出现ANR。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EventBus是一个轻量级的事件发布/订阅库,可以方便地实现组件之间的通信。它可以大大简化应用程序中的消息传递,减少了代码的耦合度,从而提高了应用程序的可维护性和可扩展性。 使用EventBus,你需要在你的项目中添加EventBus库的依赖。在gradle配置文件中添加如下代码: ```groovy dependencies { implementation 'org.greenrobot:eventbus:3.2.0' } ``` 然后,你需要定义你的事件类,例如: ```java public class MessageEvent { public final String message; public MessageEvent(String message) { this.message = message; } } ``` 接下来,订阅者需要在其onCreate()方法中注册到EventBus中: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EventBus.getDefault().register(this); } ``` 然后,你可以在订阅者中定义事件处理方法: ```java @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show(); } ``` 最后,当你想要发送一个事件时,你可以使用EventBus的post()方法: ```java EventBus.getDefault().post(new MessageEvent("Hello world!")); ``` 这个事件将被所有订阅者接收,并且可以在onMessageEvent()方法中处理。需要注意的是,当你不再需要接收事件时,你需要在onDestroy()方法中注销订阅者: ```java @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); } ``` 这就是使用EventBus的基本方法。当然,EventBus还有很多高级用法,比如可以指定事件的优先级、自定义线程模式等等。你可以查看官方文档来了解更多信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值