EventBus使用及源码分析
EventBus是为Android和Java设计的事件总线框架,主要为了实现组件间的通信。
一、 简单使用
官方文档有详细的使用说明。最简单的使用包括三个步骤:
- 定义事件类,如:
public static class MessageEvent { /* Additional fields if needed */ }
- 监听事件:
@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);
}
- 发送事件
EventBus.getDefault().post(new MessageEvent());
这样就可以完成基本的通信功能了。
二、扩展使用
1. 指定订阅者方法的线程、优先级、粘性事件
@Subscribe
注解可以指定threadMode
(线程模式)是一个枚举类,共有5个可选值,如下:
- POSTING
发送者和订阅者的订阅方法在同一个线程中 - MAIN
同步方法,post之后立马在主线程执行 - MAIN_ORDERED
在post时把消息加入队列,再按照顺序在主线程执行 - BACKGROUND
如果发送者不在主线程,就直接在这个线程执行,如果在主线程就新开一个线程执行 - 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字段如下:
Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType
建立事件到订阅该事件类型的订阅的映射Map<Object, List<Class<?>>> typesBySubscriber
建立订阅者到该订阅者订阅的所有事件类型的映射
(为什么要用两个map?)
EventBus
通过在内部维护subscriptionsByEventType
,typesBySubscriber
等map来存储订阅关系,当有订阅者注册或者解注册,就会更新这些map从而更新订阅关系,当发送一个事件时,EventBus
就根据事件类型找到订阅这个类型事件的方法,再把这个事件分发给他们(也就是调用这些方法)。从而完成事件总线的任务。
1. getDefault()
静态的 getDefault()
方法拿到单例
double check + volatile
单例。
2. register(Object subscriber)
通过register(Object subscriber)注册订阅者。
既然新添加了订阅者,就必须要更新订阅关系,也就是更新subscriptionsByEventType
和typesBySubscriber
这两个map。当找到这个订阅者所有的标注了@Subscribe的方法之后,就遍历这些方法,并调用subscribe(Object subscriber, SubscriberMethod subscriberMethod)
方法更新map,如下:
这个方法的流程非常清晰
- 先更新
subscriptionsByEventType
。拿到事件类型之后,根据subscriber
和subscriberMethod
新建一个Subscription
订阅,从subscriptionsByEventType
中找到订阅这个事件类型的订阅集合,如果为空就新建一个条目,不为空就按照这个订阅的级别放进订阅集合中(按照级别从高到低的顺序,方便以后事件的分发和拦截) - 再更新
typesBySubscriber
。根据订阅者找到它订阅的事件类型,把这个事件类型加进去即可。 - 粘性事件的map更新,先跳过。
3. unregister(Object subscriber)
通过unregister(Object subscriber)
解除订阅者的订阅。
解除订阅者同样要更新订阅关系,主要工作还是更新subscriptionsByEventType
和 typesBySubscriber
这两个map容器。
- 先更新
subscriptionsByEventType
。找到这个订阅者的所有订阅的事件类型,再遍历事件类型并调用unsubscribeByEventType(subscriber, eventType)
完成subscriptionsByEventType
的更新。
- 再更新
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)
方法服务,如果在初始化设置了eventInheritance
为true
,那么发送子类事件,订阅了父类事件的订阅者也能接收到,因此需要找到所有需要通知的事件类型(也就是该事件类型的所有父类),如果没有设置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)
就是把Subscription
和event
封装成PendingPost
对象加入链表尾端(封装采用池化处理,防止大量重复创建对象),内部维护handlerActive
标志位,表示handler
是否是激活状态(当handler
正在处理它的队列的时候,是激活状态,一旦事件队列处理完了,他就不处于激活状态),所以加入队列之后会去检测这个标志位,如果没有处于激活状态,则要激活,并且发送一个空消息去驱动handler
的handleMessage()
方法执行:
我们看一下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
基本相同,但是这中模式下的Runnable
的run()
不会去循环拿事件,会直接丢入线程池中,保证异步:
5.subscriberMethodFinder.findSubscriberMethods(subscriberClass)
这个方法用于找到subscriberClass
中所有被@Subscribe
注解的方法:
流程很明白,先从缓存拿,如果没有就看是否使用apt,如果使用直接从新类中拿,否则通过反射获取。
先看findUsingReflection(Class<?> subscriberClass)
方法:
这个方法的思路是从基类开始递归寻找父类中被@Subscribe
注解的方法,并全部加入到findState
中,最后从findState
中取出来寻找的结果List<SubscriberMethod>
再看findUsingInfo(Class<?> subscriberClass)
方法:
这个方法尝试从生成的类中拿到被@Subscribe
注解的方法,如果失败的话,则会采用降级策略,使用反射查找。
至此,EventBus
工作原理及流程基本分析完毕了。
四、 从源码中学到了什么?
- 大量重复使用反射的地方考虑使用缓存和apt技术生成新的类,减少运行时反射的使用,提高性能。
- 通过内部维护观察者和观察对象的映射关系,实现观察者模式。
EventBus
具有很强的可扩展性,通过builder
模式配置各种配置,甚至是日志的TAG也是可配置的。- 兼容
Java
环境和Android
环境,运行时通过判断Android系统类是否在内存中从而确定是那种环境。(和retrofit2
的处理方式很像) - 线程创建和使用全部基于线程池,提高性能
- 频繁创建和销毁的对象使用对象池(如:
PendingPost
类的管理),提高性能。 - 主线程handler执行多个任务时可以采用超时重新调度的策略(如
HandlerPoster
类的handleMessage(Message msg)
方法),避免主线程做太多事情造成掉帧或者出现ANR。