盒子
盒子
文章目录
  1. 基本介绍
  2. 基本使用
  3. 基本原理
  4. 参考文献

EventBus 3.0 最佳实践和原理浅析

基本介绍

EventBus,中文名叫事件总线,他的定义如下:

Android optimized event bus that simplifies communication between Activities, Fragments, Threads, Services, etc.

可以看出,EventBus能够简化同一个进程的组件(比如Activity, Fragment, 线程, Service)之间的通信。

EventBus只支持同一个进程内的通信,如果要跨进程通信,请使用HermesEventBus

2016年2月份发布了EventBus 3.0,相比原来有两大优化点:

  • EventBus 2.x中,订阅方法的方法名一定要以onEvent开头,比如onEventMainThread表示订阅方法执行在主线程;而EventBus 3.0中,订阅方法的方法名随意,只需要在前面加上注解:@Subscribe。
  • 通过注解方式声明订阅方法,速度相比2.x会变慢,通过引入注解处理器(annotation processor),在编译期间建立订阅方法的索引,性能有明显提升。

根据官方说明,注解的引入让EventBus 3.0相比2.x性能变差3-5倍,但是引入索引,3.0相比2.x性能提高至少3倍。

如果不引入索引,只需要在build.gradle加入:

compile 'org.greenrobot:eventbus:3.0.0'

在proguard添加:

-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}

但是引入索引能够大幅提高性能,因此一般建议加入,在build.gradle加入:

android {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [eventBusIndex: "com.xiazdong.eventbusdemo.EventBusIndex"] //生成的索引文件名
}
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

然后在Application的onCreate()中加入:

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

编译之后,索引文件在build/generated/source/apt/debug/com/xiazdong/eventbusdemo/EventBusIndex.java

基本使用

EventBus的使用一共3个步骤:

  • 定义事件(Event),事件就是EventBus中相互传递的对象(类似于Intent中的Bundle)。
  • 发送事件(Publisher)。
  • 订阅事件,实现订阅方法(Subscriber)。

首先是定义事件:

public class MessageEvent {
public String name;
public int age;
public MessageEvent(String name, int age) {
this.name = name;
this.age = age;
}
}

这样事件传递的对象就是MessageEvent,里面包含name和age。

接着,发送事件:

EventBus.getDefault().post(new MessageEvent("xiazdong", 25));

这个post()方法可以在任意线程调用。

EventBus还支持粘性事件,一般来说,在post()之前订阅该事件才会收到事件,但是粘性事件为:发送事件(postSticky())之后再订阅事件也能收到该事件。

最后订阅事件:

public class XActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
EventBus.getDefault().register(this); //注册了才能接收订阅
}
@Subscribe(threadMode = ThreadMode.MAIN, priority = 5, sticky = false)
public void onMessageReceived(MessageEvent event) {
Log.i(TAG, event.name + "," + event.age);
}
@Override
protected void onDestroy() {
EventBus.getDefault().unregister(this); //取消注册
}
}

onMessageReceived()就是订阅方法,threadMode有四种取值:

  • ThreadMode.POSTING:默认值,post()在哪个线程调用,订阅方法就执行在哪个线程,不能执行耗时操作。
  • ThreadMode.MAIN:订阅方法执行在主线程,用于更新UI。
  • ThreadMode.BACKGROUND:如果post()在主线程调用,则订阅方法执行在某个固定后台线程(标记为BACKGROUND的订阅方法都执行在该线程);如果post()在子线程调用,则订阅方法在该线程调用。不能执行耗时操作。
  • ThreadMode.ASYNC:新建线程执行订阅方法,可以执行耗时操作。

priority是优先级,值越大优先级越高,默认值为0。

如果是粘性事件,需要加:sticky = true。

EventBus这种通信方式的好处是:传递的内容可以是任意的,不像Intent中的Bundle只支持基本类型和实现了Parcelable的类型。

LocalBroadcastManager(本地广播)是Android Support包提供的类,用于同一进程内的通信,是阉割版的EventBus,性能和加入编译时索引的EventBus差不多,相比EventBus有几个缺点:

  • 通过Intent的Bundle传递数据,数据类型只能是基本类型或实现Parcelable接口。EventBus能传递任何对象。
  • onReceive()只能在主线程运行。EventBus的订阅方法能通过配置运行在主线程或子线程。
  • 使用麻烦。

因此建议使用EventBus。

基本原理

我们先从编译时生成的索引类(这里类名为EventBusIndex)开始讲起,该类是的作用是:收集所有订阅方法的信息,并保存到SUBSCRIBER_INDEX变量中,该变量是Map,key为类名,value为SubscriberInfo对象(实际为实现SubscriberInfo接口的SimpleSubscriberInfo对象)。需要注意:

  • SimpleSubscriberInfo对象包含了:类名、该类中所有的订阅方法(SubscriberMethodInfo对象)、该类的父类的SubscriberInfo对象。
  • 一个SubscriberMethodInfo对象包含了一个订阅方法的信息,包含了方法名、threadMode值、事件类型(即订阅方法的参数类型)、priority值、sticky值。
  • EventBusIndex的getSubscriberInfo()能通过订阅者类返回SubscriberInfo对象。

我们先分析Application中的语句:

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

EventBus类是核心类,整个原理的分析全部围绕他,EventBusBuilder类是构造EventBus对象的类。通过为EventBusBuilder设置一些配置(比如是否需要忽略索引类,即ignoreGeneratedIndex变量,默认为false),然后调用build()就构造出了一个EventBus对象。

EventBus.builder()创建了EventBusBuilder对象,addIndex(new EventBusIndex())将索引类对象设置到EventBusBuilder对象的subscriberInfoIndexes变量中,installDefaultEventBus()为根据EventBusBuilder的配置构建一个EventBus对象,并设置到EventBus类的defaultInstance变量中。

未来的EventBus.getDefault()都会获得Application中配置的新的EventBus对象。

EventBus整个注册、发送、接收过程,最关键的变量为:

  • Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType:key为事件类型,value为List<Subscription>,即该事件类型对应的所有订阅方法(顺序按优先级排序),Subscription类包含了订阅者类和一个SubscriberMethod对象(包含了订阅方法的信息,和SubscriberMethodInfo差不多)。
  • Map<Object, List<Class<?>>> typesBySubscriber:key为订阅者类名,value为该订阅者类中所有的事件类型。
  • Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE:key为订阅者类名,value为该类中所有的订阅方法信息。这是缓存类,用于缓存通过索引或反射查找到的结果。
  • SubscriberMethodFinder subscriberMethodFinder:其中的findSubscriberMethods()根据订阅者类获得该类中所有的订阅方法信息。

注册:即调用EventBus的register(),即注册订阅者的所有订阅事件,代码如下:

public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}

该方法先通过subscriberMethodFinder的findSubscriberMethods()根据订阅者类名查找到该类的所有订阅方法信息(List<SubscriberMethod>),然后调用subscribe()把这些信息填充到subscriptionsByEventType和typesBySubscriber中。

SubscriberMethodFinder类中的findSubscriberMethods()代码如下:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//从METHOD_CACHE中查找结果
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); //把结果放入METHOD_CACHE中,避免下次再一次查找
return subscriberMethods;
}
}

该方法先后通过几种方法查找订阅方法信息:

  • METHOD_CACHE:存放以前查找过的结果,避免下次重复查找。
  • 判断ignoreGeneratedIndex:是否忽略生成的索引类,该值默认为false,也建议为false,因此我们只考虑else分支。
  • findUsingInfo():先从索引类中查找结果,如果找不到,再通过反射(findUsingReflectionInSingleClass())查找结果。
  • 如果查找到结果,就放入METHOD_CACHE缓存,避免下次重复查找。

因此索引类的引入主要减少了register()的耗时,即用编译时生成索引的方式代替了方法反射,前面说的EventBus 3.0相比EventBus 2.x速度提升至少3倍说的就是register()方法速度的提升。

接着看unregister(),该方法就是将该订阅者相关的订阅事件信息和订阅方法信息等从subscriptionsByEventType和typesBySubscriber中删除。

最后是post(),该方法用来发送订阅事件,并调用相关订阅者的订阅方法,具体怎么查找订阅者,是通过subscriptionsByEventType查找订阅者的订阅方法。具体在哪个线程调用订阅方法,通过订阅方法的threadMode声明确定。post()方法实现如下:

public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) { //依次发送队列中的事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}

currentPostingThreadState变量是ThreadLocal<PostingThreadState>类型的,即每个线程都有一个PostingThreadState对象,该类结构为:

final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>(); //事件队列
boolean isPosting; //是否正在发送事件
boolean isMainThread; //当前线程是否是主线程
}

post()共分为3步:

  • 把订阅事件信息放入eventQueue中,等待被执行。
  • 判断isPosting变量是否为true(即当前线程是否正在发送事件),如果为false,则置为true并循环遍历eventQueue,并调用postSingleEvent()发送订阅事件;否则直接返回,让当前正在遍历队列的对象执行订阅方法。
  • 最后把isPosting和isMainThread置为false。

postSingleEvent()首先会寻找该订阅事件的所有父类,并依次发送这些事件和该订阅事件,即对每个订阅事件调用postSingleEventForEventType(),在postSingleEventForEventType()中通过subscriptionsByEventType查找到订阅事件对应的所有订阅方法,并对每个订阅方法调用postToSubscription(),该方法是EventBus调配订阅方法在哪个线程执行的核心方法,如下:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}

这里有三种Poster,他们的共同点:内部都有PostPendingQueue和PendingPost,PostPendingQueue是存放PendingPost的队列,PendingPost存放了订阅事件和Subscription对象,即待调用的订阅方法。

他们的不同点:

  • mainThreadPoster是主线程的Handler,即在handleMessage中调用订阅方法;backgroundPoster和asyncPoster是Runnable,他们的run()会遍历PostPendingQueue队列,并调用订阅方法。
  • backgroundPoster:和asyncPoster一起使用EventBus的executorService线程池。内部有executorRunning变量判断run()方法是否正在执行,如果正在执行,就只是把PendingPost放入队列,让当前的run()方法去调用该订阅方法。
  • asyncPoster:使用EventBus的executorService线程池,每次都创建线程并调用订阅方法。

对于threadMode的值分了四种情况:

  • POSTING:invokeSubscriber()就是直接用反射调用订阅方法。
  • MAIN:如果调用post()时就是主线程,那就直接反射调用订阅方法;否则就加入mainThreadPoster的队列。
  • BACKGROUND:如果当前线程是主线程,那么加入backgroundPoster的队列;如果当前线程不是主线程,那么直接调用订阅方法。
  • ASYNC:加入asyncPoster的队列。

参考文献

支持一下
扫一扫,支持xiazdong