目录
一 EventBus 3.1.1
1.1.简介
1.2.实现
1.2.1.配置(EventBusBuilder)
1.2.2.声明事件类型
1.2.3.在订阅者类中定义消息处理方法(事件回调方法)
1.2.4.在订阅者类中注册解绑EventBus
1.2.5.被订阅者类发送事件对象
1.3.线程模型(ThreadMode)
1.3.1.ThreadMode.POSTING
1.3.2.ThreadMode.MAIN
1.3.3.ThreadMode.BACKGROUND
1.3.4.ThreadMode.ASYNC
1.4.粘性事件(StickyEvent)
1.4.1.场景分析:
1.4.2.StickyEvent:
1.4.3.实现:
1.4.4.应用案例:
1.5.无订阅者事件(NoSubscriberEvent)
1.6.事件优先级(priority)
1.7.终止事件传递
1.8.EventBus processor使用
1.8.1.配置索引:在build.gradle中添加如下配置
1.8.2.使用索引:
1.9.混淆
二 EventBus 3.1.1源码解析(原理)
2.1.构造
2.1.1.getDefault():DCL双检锁单例模式
2.1.2.EventBus():无参构造方法
2.1.3.builder():自定义建造者
2.1.4.EventBus 最终构建过程
2.2.注册
2.2.1.获取订阅者subscriber的Class类型
2.2.2.通过Class subscriberClass解析出订阅者声明的所有事件处理方法、方法参数类型
2.2.3.subscribe(subscriber, subscriberMethod)方法的实现
2.3.发送消息
2.3.1.post
2.3.2.postSticky
2.3.3.postSingleEvent
2.3.4.postSingleEventForEventType
2.3.5.postToSubscription
2.3.6.invokeSubscriber
2.4.解绑
三 EventBus和OTTO对比
简介:
EventBus是一个基于观察者模式的事件发布 / 订阅的消息总线框架。目的用于简化(Android)组件、线程间通讯,可轻易切换、开辟线程,简洁,方便,体积小,效率高 ……
EventBus3.0跟先前版本的区别在于加入了annotation @Subscribe,取代了以前约定命名的方式。
原理:
EventBus中,当被观察者(事件对象)产生或变更的时候,会通过EventBus把 事件对象“通知给”观察者(订阅者对象)(触发事件回调方法)。整个过程中EventBus充当了中介的角色,将原本 被观察者(事件对象)所需要承担的责任抽离出来由自己承担,缓存并管理 观察者 列表,并与 被观察者(事件对象)建立对应关系,并控制时机,将被 观察者(事件对象)“通知给” 观察者(订阅者对象)。
EventBus中 被观察者 和 观察者 是通过 声明的 事件对象类型(可以理解为一种协议) 进行匹配的,即EventBus将特定的 事件对象 发送给能接收该 事件类型 的 订阅者对象(订阅者在注册EventBus的时候,就已经告诉EventBus,自己需要哪类事件了)。
优:
代码够简洁,消息发布者和订阅者之间的耦合度够低!并且可以动态设置事件处理线程以及优先级。快速,轻量!
劣:
每个事件都要创建一个事件类,加大维护成本(可做封装优化,后面会介绍方案)。
说明:
由于EventBus使用了反射,关于性能问题 ……
EventBus不仅可以使用注解处理器预处理获取订阅信息,也会将订阅者的方法缓存到METHOD_CACHE里避免重复查找,只有在最后invoke()方法的时候会比直接调用多出一些性能损耗,其实可以忽略不计。
结构图:
SDK:
EventBus :http://greenrobot.org/eventbus/
GitHub:https://github.com/greenrobot/EventBus
version:
http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.greenrobot%22%20AND%20a%3A%22eventbus%22
https://mvnrepository.com/
注意:
com.google.common.eventbus. EventBus
org.greenrobot.eventbus.EventBus
两个EventBus不一样
(1)订阅者类中需要注册和解绑EventBus
(2)声明事件类型
(3)在事件产生或者变更的类中直接post事件对象。
EventBusBuilder提供了EventBus的配置接口,指定了EventBus的一些行为,用于输出log,查错,调试等。比如配置日志输出模式(开发版本/发布版本),配置错误处理模式(开发版本遇错误崩溃 / 发布版本遇错误崩溃)等等。
// 修改默认实现的配置:debug模式下要抛出异常 EventBus.builder().throwSubscriberException(BuildConfig.DEBUG) .installDefaultEventBus();必须在第一次EventBus.getDefault()之前配置,且只能设置一次。建议在application.onCreate()调用。
installDefaultEventBus这个方法用来将一个自定义的EventBusBuider对象配置到EventBus中去,并替换掉defaultInstance。
注意:这里需要在Default EventBus 对象还没生成的时候执行这段代码。如果已经有默认的EventBus对象存在了再installDefaultEventBus就会抛异常。所以最好在app启动的时候加入代码!
事件处理函数的访问权限必须为public!!!
在3.0之前,事件处理的方法也只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分别代表四种线程模型;在3.0之后,消息处理的方法可以随便取名,只需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING)
说明:
事件发布线程中执行(默认线程模型):事件在哪个线程发布(post),事件接收方法就在哪个线程中执行,即 发布事件 和 接收处理事件 在同一个线程。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险,甚至有可能会引起ANR。
场景:
对于是否在主线程执行无要求,但若 Post 线程为主线程,不能处理耗时操作
// Called in the same thread (default) @Subscribe(threadMode = ThreadMode.POSTING) public void onMessageEvent(MessageEvent messageEvent) { doSomethingWith(event); }说明:
主线程(UI线程)中执行:如果事件发布线程就是主线程,则直接调用订阅者的事件回调方法,否则将通过主线程的 Handler 发送消息,通知订阅者在主线程中处理事件回调。
场景:
该线程模式可以用来更新UI,但是 不能处理耗时操作
// Called in Android UI's main thread @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent messageEvent) { doSomethingWith(event); }说明:
后台线程中执行:如果事件发布线程不是主线程,则直接在该线程执行;否则,切换到后台单例线程执行事件回调方法,且多个方法公用同个后台线程,按顺序执行。
场景:
禁止进行UI更新操作,或禁止在主线程(UI线程)中执行,同样 避免处理耗时操作!
// Called in the background thread @Subscribe(threadMode = ThreadMode. BACKGROUND) public void onMessageEvent(MessageEvent messageEvent) { doSomethingWith(event); }说明:
异步线程执行:开辟新独立线程,EventBus内部使用了线程池,但是要尽量避免大量长时间运行的异步线程,限制并发线程数量可以通过EventBusBuilder修改,默认使用Executors.newCachedThreadPool()。
场景:
用来执行耗时操作,例如网络访问。
// Called in a separate thread @Subscribe(threadMode = ThreadMode. ASYNC) public void onMessageEvent(MessageEvent messageEvent) { doSomethingWith(event); }1.背景描述:
正常事件发布处理流程应该是订阅者先在EventBus中注册,并告诉EventBus自己想订阅哪些类型的事件。之后当事件发布时,才有可能通过EventBus找到匹配的订阅者。然而,会有一种场景是事件先发布,订阅者之后才注册,这种情况下,事件发布时是找不到订阅者的 …… StickyEvent就是为了解决上述问题而生的。
2.具体应用场景:
(1)缓存:EventBus会将StickyEvent特定类型的最后一个实体保留在内存中,并可供明确查询。
(2)替代Intent传递数据给新的Activity
StickyEvent类似广播分类中的粘性广播,同类Event会一直在内存中保存最新的实体,覆盖原有实体。当检查到有可匹配的订阅者在EventBus注册的时候,EventBus会找到订阅者并将最新的实体“通知给”订阅者。如果没有可匹配的订阅者注册,Event消息实体会一直保留在内存中。
如此一来,订阅在发布事件之后,同样可以收到事件,且EventBus会在订阅者订阅之后立即找到它并回调其粘性事件处理方法。订阅/解除订阅和普通事件一样,但是声明事件回调方法稍有不同,需要注解中添加sticky = true。
在订阅在发布事件之后的场景中,必须使用postSticky方法发布事件,且必须使用注解中添加sticky = true的事件处理方法接收sticky事件。
实现原理:
postSticky中用了一个stickyEvents (ConcurrentHashMap< event.getClass(), event>)缓存了最新的事件实体。
public void postSticky(Object event) { synchronized (stickyEvents) { stickyEvents.put(event.getClass(), event); } // Should be posted after it is putted, in case the subscriber wants to remove immediately post(event); }声明事件处理方法:
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true) // 在ui线程执行 public void stickyEventMethod(MessageEvent event) { // ...... }事件发送:
EventBus.getDefault().postSticky(new MessageEvent ());由于StickyEvent属于常驻事件,会常驻内存,所以有时候需要手动移除!
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class); // Better check that an event was actually posted before if(stickyEvent != null) { // "Consume" the sticky event EventBus.getDefault().removeStickyEvent(stickyEvent); //or EventBus.getDefault().removeAllStickyEvents(); // Now do something with it }针对1.5.1 具体应用场景(2):替代Intent实现Activity间数据传递
(1)声明一个新的Activity B,并定义好StickyEvent事件处理方法,在onStart、onStop方法中注册 / 解注 EventBus。
public class ActivityB extends AppCompatActivity { @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Subscribe(sticky = true) public void dealSticky(StickyMessageEvent event) { Log.i("DemoActivity", "dealSticky method" + event); } @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } }(2)在原Activity A中启动新Activity B之前,发布包含数据信息的StickyMessageEvent,之后启动Activity B。
EventBus.getDefault().postSticky(new StickyMessageEvent()); Intent intent = new Intent(); intent.setClass(this, DemoActivity.class); startActivity(intent);(3)在Activity B中正常接收到StickyMessageEvent,解析其中数据信息进行处理 ……
没有订阅者的消息默认会被重新包装为NoSubscriberEvent,并打印Logcat:
No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
可以做一些全局捕获处理,也可以通过EventBusBuilder设置是否默认发送NoSubscriberEvent,默认是打开的。(当设置为false,上述logcat就不会打印了)
EventBus.builder().sendNoSubscriberEvent(false).installDefaultEventBus();priority越大,级别越高,越优先获得消息
@Subscribe(threadMode = ThreadMode.MAIN,priority = 100)发送有序广播可以终止广播的继续往下传递,EventBus也实现了此功能
// 优先级高的订阅者可以终止事件往下传 EventBus.getDefault().cancelEventDelivery(event);EventBus使用了annotation机制,在编译期解析注解代码,添加索引类,自动生成相应代码。EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期读取 @Subscribe 注解并解析,处理其中所包含的信息,然后生成索引类来保存所有订阅者关于订阅的信息,这样就比在运行时使用反射来获得这些订阅者的信息速度要快。
1.此时编译一次,自动生成生成索引类。在\build\generated\source\apt\PakageName\下看到通过注解分析生成的索引类,这样我们便可以在初始化EventBus时应用我们生成的索引了 —— MyEventBusIndex.java。
2.添加索引到EventBus(Application中)
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build(); EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();3.添加多个索引类到EventBus(Application中)
EventBus.builder() .addIndex(new MyEventBusAppIndex()) .addIndex(new MyEventBusLibIndex()).installDefaultEventBus();通常我们调用EventBus.getDefault()获取EventBus
保证getDefault()得到的都是同一个实例,且线程安全
// 静态的单例实例 static volatile EventBus defaultInstance; // 返回单例 public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }使用默认构建器DEFAULT_BUILDER构造。
// 默认的构建者 private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder(); public EventBus() { this(DEFAULT_BUILDER); }构造方法public,每一个EventBus都是独立地、处理它们自己的事件,因此可以存在多个EventBus。
通过getDefault()方法获取的实例是默认的EventBus,是单例,无论在什么时候通过这个方法获取的实例都是同一个实例。
该方法用于自定义建造者 EventBusBuilder 来建造不同特性的EventBus。
// 也可以使用下面的方法获取一个构建者,然后使用它来个性化定制EventBus public static EventBusBuilder builder() { return new EventBusBuilder(); }1.subscriptionsByEventType:
这是一个HashMap!缓存的是每类事件的订阅者(注册对象以及方法(一个Subscription包含一个对象,一个方法!!!))列表。
声明:
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
说明:
(1)subscriptionsByEventType:以EventType(事件类型)为key,以订阅者(Subscription)为value,事件发送之后,在这里寻找订阅者。
(2)CopyOnWriteArrayList是一个线程安全的容器。
(3)Subscription是一个封装类,封装了订阅者、订阅方法这两个类。
final Object subscriber; final SubscriberMethod subscriberMethod;2.typesBySubscriber
这是一个HashMap,缓存的是订阅者所订阅的消息类型列表。
声明:
private final Map<Object, List<Class<?>>> typesBySubscriber;
说明:
typesBySubscriber:以订阅者对象为key,以EventType(事件类型)列表为value,在进行register或unregister操作的时候,会操作这个map。
3.stickyEvents
这是一个ConcurrentHashMap,缓存的是每一类粘性事件 最新的事件实体对象。
声明:
private final Map<Class<?>, Object> stickyEvents;说明:
stickyEvents:以StickyEventType(粘性事件类型)为key,以对应的最新的事件实体对象为value。Map中key / value唯一,说明粘性事件是会被覆盖的。这样可以节省内存开销。
4.mainThreadPoster
这是一个HandlerPost,对应处理EventBus线程模式:MAIN 和 MAIN_ORDERED。mainThreadPoster 简单说就是把事件切换到主线程执行【详解部分见下文】。
声明:
private final Poster mainThreadPoster;
说明:
(1)Poster
是一个定义行为(即把事件对象 post 给订阅者的行为)的接口:
interface Poster { /** * Enqueue an event to be posted for a particular subscription. * @param subscription Subscription which will receive the event. * @param event Event that will be posted to subscribers. */ void enqueue(Subscription subscription, Object event); }(2)mainThreadPoster通过mainThreadSupport初始化
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;(3)mainThreadSupport
是一个接口,就定义了两个方法,一:判断当前线程是否是主线程;二:由EventBus参数生成一个Poster实例
其具体行为是由 实现类 AndroidHandlerMainThreadSupport 来规定的,其中createPoster方法返回了一个HandlerPoster实例。
public interface MainThreadSupport { boolean isMainThread(); Poster createPoster(EventBus eventBus); class AndroidHandlerMainThreadSupport implements MainThreadSupport { private final Looper looper; public AndroidHandlerMainThreadSupport(Looper looper) { this.looper = looper; } @Override public boolean isMainThread() { return looper == Looper.myLooper(); } @Override public Poster createPoster(EventBus eventBus) { return new HandlerPoster(eventBus, looper, 10); } } }(4)HandlerPoster
实质是一个Handler,同时实现了Poster接口。
public class HandlerPoster extends Handler implements Poster { // PendingPostQueue队列,待发送的post队列 private final PendingPostQueue queue; // 规定最大的运行时间,因为运行在主线程,不能阻塞主线程 private final int maxMillisInsideHandleMessage; private final EventBus eventBus; private boolean handlerActive; // 省略... }内部有一个PendingPostQueue,这是一个队列,保存了PendingPost,PendingPost封装了待发送的event和subscription,方便在线程中进行信息的交互。在postToSubscription方法中,当前线程如果是主线程的时候,会调用HandlerPoster#enqueue方法:
void enqueue(Subscription subscription, Object event) { // 将subscription和event打包成一个PendingPost PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { // 入队列 queue.enqueue(pendingPost); if (!handlerActive) { handlerActive = true; // 发送消息,在主线程运行 if (!sendMessage(obtainMessage())) { throw new EventBusException( "Could not send handler message"); } } } }首先会从PendingPostPool中获取一个可用的PendingPost,接着把该PendingPost放进PendingPostQueue,发送消息(sendMessage),那么由于该HandlerPoster在初始化的时候获取了UI线程的Looper,所以它的handleMessage()方法运行在UI线程:
HandlerPoster操作PendingPostQueue存取以及触发运行都是在主线程,从而实现了在主线程回调事件处理方法的效果。
@Override public void handleMessage(Message msg) { boolean rescheduled = false; try { // 省略... while (true) { // 不断从队列中取出pendingPost PendingPost pendingPost = queue.poll(); // 省略... eventBus.invokeSubscriber(pendingPost); // 省略... } } finally { handlerActive = rescheduled; } }里面调用到了EventBus#invokeSubscriber方法,在这个方法里面,将PendingPost解包,进行正常的事件分发。
备注:
【看源码】handleMessage对事件链表的处理是不断循环获取链表中的PendingPost,当链表中的事件处理完毕后退出while循环。在规定的时间内maxMillisInsideHandleMessage没有将事件处理完,则继续调用sendMessage。继而重新回调handleMessage方法,事件会继续执行。这是为了避免队列过长或者事件耗时较长时,while循环阻塞主线程造成卡顿。
(5)PendingPost:
PendingPost构建了一个链表结构:封装了事件对象event,事件接收者对象Subscription,指向下一个链表节点的next引用。该类声明了一个静态的pendingPostPool集合,该集合缓存了PendingPost对象。
class PendingPost { private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>(); // 具体的事件对象 Object event; // 订阅者对象及方法封装 Subscription subscription; // next指针 PendingPost next; // 省略... }(6)PendingPostQueue
PendingPostQueue存储了PendingPost链表的头和尾,并声明了构建链表的逻辑方法。一句话:PendingPostQueue构建了PendingPost链表。
final class PendingPostQueue { // 链表头部引用 private PendingPost head; // 链表尾部引用 private PendingPost tail; // 省略... }声明了链表构建方法,该方法将EventBus#post的事件形成了一个待发送的链表,后面通过在不同线程提取链表节点并触发invoke,实现了在不同线程间,灵活传递事件(消息)的目的。
synchronized void enqueue(PendingPost pendingPost) { if (pendingPost == null) { throw new NullPointerException("null cannot be enqueued"); } if (tail != null) { tail.next = pendingPost; tail = pendingPost; } else if (head == null) { head = tail = pendingPost; } else { throw new IllegalStateException("Head present, but no tail"); } // 唤醒 notifyAll(); }5.backgroundPoster
这是一个BackgroundPoster,对应处理EventBus线程模式:BACKGROUND。
声明:
private final BackgroundPoster backgroundPoster;
说明:
BackgroundPoster继承自Runnable,与HandlerPoster相似的,它内部都有PendingPostQueue这个队列,当调用到它的enqueue的时候,会将subscription和event打包成PendingPost。
enqueue通过Executor来运行run()方法,run()方法内部也是调用到了EventBus#invokeSubscriber方法。
BackgroundPoster操作PendingPostQueue存取以及触发运行都是在非UI线程,从而实现了在非UI线程回调事件处理方法的效果。
public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); // 如果后台线程还未运行,则先运行 if (!executorRunning) { executorRunning = true; // 会调用run()方法 eventBus.getExecutorService().execute(this); } } } @Override public void run() { try { try { while (true) { // 不断从队列中取出pendingPost PendingPost pendingPost = queue.poll(1000); // 省略... eventBus.invokeSubscriber(pendingPost); } } catch (InterruptedException e) { // 省略... } } finally { executorRunning = false; } }6.asyncPoster
这是一个AsyncPoster,对应处理EventBus线程模式:ASYNC。
声明:
private final AsyncPoster asyncPoster;
说明:
与BackgroundPoster类似,它也是一个Runnable,实现原理与BackgroundPoster大致相同,但有一个不同之处,就是它内部不用判断之前是否已经有一条线程已经在运行了,它每次post事件都会使用新的一条线程。
public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); eventBus.getExecutorService().execute(this); } @Override public void run() { PendingPost pendingPost = queue.poll(); if(pendingPost == null) { throw new IllegalStateException("No pending post available"); } eventBus.invokeSubscriber(pendingPost); }7.subscriberMethodFinder
这是一个方法解析器,用来查找和缓存订阅者事件处理方法信息的类。
声明:
private final SubscriberMethodFinder subscriberMethodFinder;
说明:
SubscriberMethodFinder是一个方法解析器,根据订阅者信息(Class Info)找到事件处理方法并返回给List<SubscriberMethod>。
8.EventBusBuilder(重要)
这是EventBus的建造者,EventBusBuilder指定了EventBus的一些行为,用于输出log,查错,调试等。EventBus使用了建造者模式(在写一个需要自定义配置的框架的时候,这种实现方法非常普遍,将配置解耦出去,使代码结构更清晰)。
声明:
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
说明:
(1)EventBus.getDefault() 时,通过this( DEFAULT_BUILDER ); 初始化一个默认的EventBusBuilder对象给EventBus,来分别初始化EventBus的一些配置。So!可以通过给EventBusBuilder设置不同的属性,进而获取有着不同功能的EventBus。
(2)EventBus的一些参数配置:
logSubscriberExceptions 指定了收到SubscriberExceptionEvent类型消息时抛出异常的行为,默认为true,打印log,如果为false不输出log
logNoSubscriberMessages指定了发送了没有订阅者的消息的行为,默认为true,打印log,如果为false不输出log
sendSubscriberExceptionEvent指定是否发送SubscriberExceptionEvent,默认为true
sendNoSubscriberEvent指定了没有订阅者时的行为,默认为true,发送
throwSubscriberException指定了订阅方法执行异常时是否抛出异常,默认为false
eventInheritance指定是否发消息给这个事件的父事件对应的订阅者,默认为true。如果为false,只会发消息给类型完全一致的订阅者
ignoreGeneratedIndex这个变量是用来设置EventBus用什么方式来获取订阅方法。 true代表的是不优先使用索引,用反射的方式,false代表的是优先使用索引。默认是false。
if (ignoreGeneratedIndex) { subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); }EventBusBuilder类中有一个addIndex方法用来添加索引!!!
public EventBusBuilder addIndex(SubscriberInfoIndex index) { if(subscriberInfoIndexes == null) { subscriberInfoIndexes = new ArrayList<>(); } subscriberInfoIndexes.add(index); return this; }findUsingReflection利用反射来获取订阅类中的订阅方法信息findUsingInfo从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息
findUsingInfo执行效率更高一些,默认使用这种。
strictMethodVerification指定是否严格校验,默认为false。严格校验会直接抛出异常
(3)案例:
// 默认地,EventBus会考虑事件的超类,即事件如果继承自超类,那么该超类也会作为事件发送给订阅者。 // 如果设置为false,则EventBus只会考虑事件类本身。 boolean eventInheritance = true; public EventBusBuilder eventInheritance(boolean eventInheritance) { this.eventInheritance = eventInheritance; return this; } // 当订阅方法是以onEvent开头的时候,可以调用该方法来跳过方法名字的验证,订阅这类会保存在List中 List<Class<?>> skipMethodVerificationForClasses; public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) { if (skipMethodVerificationForClasses == null) { skipMethodVerificationForClasses = new ArrayList<>(); } skipMethodVerificationForClasses.add(clazz); return this; } //更多的属性配置请参考源码,均有注释 //..
通过建造者模式来手动创建一个EventBus:
EventBus eventBus = EventBus.builder().eventInheritance(false) .sendNoSubscriberEvent(true) .skipMethodVerificationFor(MainActivity.class).build();public void register(Object subscriber) {
// 首先获得订阅者的class对象 Class<?> subscriberClass = subscriber.getClass(); /** * 通过subscriberMethodFinder来找到订阅者订阅了哪些事件.返回一个SubscriberMethod对象的List * SubscriberMethod里包含了: * (1)这个方法的Method对象 * (2)这个方法响应订阅是在哪个线程的ThreadMode * (3)这个方法订阅的事件类型eventType * (4)这个方法订阅的优先级priority * (5)这个方法是否接收粘性sticky事件的boolean值. */ List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { // 订阅 subscribe(subscriber, subscriberMethod); } } }
SubscriberMethodFinder是一个方法解析器,根据订阅者信息(Class Info)找到事件处理方法并返回给List<SubscriberMethod>。
背景知识:
在3.0版本中,EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe()注解并解析,处理其中所包含的信息,然后生成java类来保存所有订阅者关于订阅的信息,这样会比在运行时使用反射来获得这些订阅者的信息速度要快。
相关信息详见1.8. EventBus processor使用
MyEventBusIndex:
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;(1)SUBSCRIBER_INDEX是一个HashMap,来保存订阅类的信息,其中包括了订阅类的class对象,是否需要检查父类,以及缓存订阅方法信息的SubscriberMethodInfo数组
(2)SubscriberMethodInfo中又保存了订阅方法的方法名,订阅的事件类型,触发线程,是否接收sticky事件以及优先级priority等信息。
上述其实包含了注册(register())所需要的所有信息,只需要在配置EventBus的时候,通过1.8. EventBus processor使用 的方式将编译期生成的MyEventBusIndex配置进去,这样SubscriberMethodFinder就可以通过MyEventBusIndex直接查找出订阅类的信息。这种方式可以作为EventBus的可选配置,SubscriberMethodFinder同样提供了通过注解来获得订阅类信息的方法。
总体说来,注册做了三件事情:
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
1.findSubscriberMethods()
该方法处理逻辑很简单:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { // 先从METHOD_CACHE取看是否有缓存,key:保存订阅类的类名,value:保存类中订阅的方法数据, List<SubscriberMethod> subscriberMethods = METHOD_CACHE .get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } // 是否忽略注解器生成的MyEventBusIndex类 if (ignoreGeneratedIndex) { // 利用反射来读取订阅类中的订阅方法信息 subscriberMethods = findUsingReflection(subscriberClass); } else { // 从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息 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缓存 METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }2.findUsingReflection
利用反射机制获取 ……
// 首先获得订阅者的class对象 private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) { // FindState 用来做订阅方法的校验和保存 FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { // 通过反射来获得订阅方法信息 findUsingReflectionInSingleClass(findState); // 查找父类的订阅方法 findState.moveToSuperclass(); } // 获取findState中的SubscriberMethod(也就是订阅方法List)并返回 return getMethodsAndRelease(findState); }这里通过FindState类来做订阅方法的校验和保存,用HashMap保存,并通过SubscriberMethodFinder全局缓存:FIND_STATE_POOL静态数组来保存FindState对象,避免重复创建过多的对象【FindState对象可复用】。最终是通过findUsingReflectionInSingleClass()来具体获得相关订阅方法的信息的,该方法处理逻辑大概描述就是:
(1)获取订阅类声明的所有方法;
(2)然后对获取到的方法全部遍历一遍:
1)获取方法的修饰符:即方法前面的public、private等关键字。
2)如果该类方法使用了@subscribe标注、方法中只有一个参数、且方法修饰符为public。findState.checkAdd(method, eventType) 如果之前没有存在过则返回true
3)判断@Subscribe标注中的threadMode对应的值,默认模式ThreadMode.POSTING
4)创建一个SubscriberMethod对象,该对象很简单就是保存有方法、方法参数类型、线程模式、订阅的优先级、sticky标志位。与Retrofit类似只是这里创建了一个SubscriberMethod对象。并将该对象添加到FindSate的List集合中。
具体请见源码!
3.findUsingInfo()
通过索引类MyEventBusIndex获取 ……
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { // 准备一个FindState,该FindState保存了订阅者类的信息 FindState findState = prepareFindState(); // 对FindState初始化 findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { // 获得订阅者的信息 findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo .getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { // 如果订阅者索引信息获取不到,重新利用反射获取 findUsingReflectionInSingleClass(findState); } // 移动到父类继续查找 findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }(1)从SubscriberMethodFinder全局缓存:FIND_STATE_POOL中获取一个FindState对象。
(2)将findState.clazz域中使用了@subscribe标注、方法中只有一个参数、且方法修饰符为public的方法,创建一个SubscriberMethod对象,并添加到findState的List集合中。
(3)将findState.clazz域更新为clazz = clazz.getSuperclass(); 如果该超类名字以java. javax. android.开头则clazz变成null;不再往上寻找父类;
(4)拷贝一份findState的List集合并返回,最后回收findState对象,回收的只是释放这个对象内部的变量的资源占用,但这个对象还是存在的,放回对象池中,下次可以再取出来用;
现在,我们订阅类的所有SubscriberMethod都已经被保存了,最后再通过getMethodsAndRelease()返回List<SubscriberMethod>至此,我们已经获得订阅类的订阅方法信息即:SubscriberMethod对象。
主要是建立订阅者,订阅者事件处理方法,订阅事件类型之间直接的关联关系,实现真正意义上的注册!
1.获取方法参数类型,即接收事件类型EventType。注意:使用@Subscribe标注的方法有且仅有一个参数
2.把订阅者对象subscriber和其事件处理方法SubscriberMethod对象封装并生成一个Subscription对象。
3.把 2 生成的Subscription缓存到subscriptionsByEventType【HashMap,缓存每类事件的订阅者(Subscription)列表】中
1)从subscriptionsByEventType集合中获取当前事件对应的Subscription对象集合【CopyOnWriteArrayList】
2)如果集合为空则创建一个Subscription类型的CopyOnWriteArrayList,并将刚创建的Subscription对象添加进去,之后把CopyOnWriteArrayList缓存进subscriptionsByEventType中;
3)如果集合不为空且刚创建的Subscription对象已经存在该集合中则抛出异常,即同一个对象不能注册两次!
4)将 2 生成的Subscription对象按照优先级存入Subscription对象集合中,该集合中的元素都是按照优先级从高到低存放。
4.将订阅者对象以及订阅的事件保存到typesBySubscriber【HashMap,缓存的是订阅者所订阅的消息类型列表】
1)从typesBySubscriber获取本次的订阅者对象subscriber对应的接收事件类型集合
2)如果集合【ArrayList】为空,则新建一个ArrayList,然后当前事件类型添加到该集合中,最后将整个集合添加进typesBySubscriber集合中。
3)如果集合【ArrayList】不为空,则直接添加到接收事件类型集合中。
5.判断订阅者方法是否接受粘性事件(sticky = true),如果是,则立即从stickyEvent寻找对应EventType的最新事件实体对象,随即分发执行。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 获取订阅的事件类型 Class<?> eventType = subscriberMethod.eventType; // 创建Subscription对象 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); // 从subscriptionsByEventType里检查是否已经添加过该Subscription,如果添加过就抛出异常 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType .get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } // 根据优先级priority来添加Subscription对象 int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } // 将订阅者对象以及订阅的事件保存到typesBySubscriber里. List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); // 如果接收sticky事件,立即分发sticky事件 if (subscriberMethod.sticky) { // eventInheritance 表示是否分发订阅了响应事件类父类事件的方法 if (eventInheritance) { Set<Map.Entry<Class<?>, Object>> entries = stickyEvents .entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
1.currentPostingThreadState:一个包含了PostingThreadState的ThreadLocal对象。ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,且数据是不会与其他线程共享的。其内部原理是通过生成一个泛型对象的数组【这里是PostingThreadState类型】,在不同的线程会有不同的数组索引值,通过这样就可以做到每个线程通过get() 方法获取的时候,取到的只能是自己线程所对应的数据。 这里取到的就是每个线程的PostingThreadState状态。
post线程状态通过ThreadLocal维护,保证线程隔离,不需要加锁,提高运行效率。
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override protected PostingThreadState initialValue() { return new PostingThreadState(); } };
2.获取PostingThreadState对象,其中保存了当前线程需要发送的事件队列,并将当前事件加入事件队列。
3.循环事件队列,依次发送每个事件。
public void post(Object event) {
// 得到当前线程的Posting状态 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; } } }
粘性事件先把事件缓存到stickyEvents【这是一个ConcurrentHashMap,缓存的是每一类粘性事件 最新的事件实体对象】,之后调用post发送。
public void postSticky(Object event) {
ynchronized (stickyEvents) { stickyEvents.put(event.getClass(), event); } // Should be posted after it is putted, in case the subscriber wants to remove immediately post(event); }
默认地会搜索出它的父类,并且把父类也作为事件之一发送给订阅者,接着调用了postSingleEventForEventType,把事件、postingState、事件的类传递进去。
1.eventInheritance表示只要满足订阅事件是该事件的父类或者实现了该事件同样接口或接口父类 中的任何一个条件的订阅者都会来处理该事件,默认值为true。
2.eventInheritance = true:查找事件类所有的父类型以及接口类型,循环事件类以及每一个父类型以及接口类型,调用postSingleEventForEventType,把事件、postingState、对应的类型(事件类型,父类型,接口类型)传递进去。意思是让能接受事件类型以及其 父类型,接口类型的订阅者,都把该事件处理一遍。
3.eventInheritance = false:调用一次postSingleEventForEventType,只让能接受事件类型的订阅者去处理事件。
4.如果没有发现订阅者,根据配置条件【通过EventBusBuilder】,打印"No subscribers registered for event ";或被重新包装成NoSubscriberEvent并发送,可以做一些全局捕获处理。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; // 是否触发订阅了该事件(eventClass)的父类,以及接口的类的响应方法. if (eventInheritance) { // 查找eventClass类所有的父类以及接口 List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); // 循环postSingleEventForEventType for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); // 只要右边有一个为true,subscriptionFound就为true subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { // post单个 subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } // 如果没发现 if (!subscriptionFound) { if (logNoSubscriberMessages) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { // 发送一个NoSubscriberEvent事件,如果我们需要处理这种状态,接收这个事件就可以了 post(new NoSubscriberEvent(this, event)); } } }在postSingleEventForEventType()方法里去进行事件的分发:
1.subscriptionsByEventType通过eventClass获取与事件对应的Subscription集合
2.遍历并postToSubscription把事件对象分发给每个订阅者
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; // 获取订阅了这个事件的Subscription列表. synchronized (this) { subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; // 是否被中断 boolean aborted = false; try { // 分发给订阅者 postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }发送给订阅者!
根据订阅者事件处理方法声明的不同的threadMode,在不同的线程里invoke()事件处理方法。
1.POSTING:直接在当前线程执行,调用invokeSubscriber方法
2.MAIN(UI线程执行):如果当前线程就是UI线程则直接调用invokeSubscriber方法,否则将任务交给mainThreadPoster ——HandlerPoster(this, Looper.getMainLooper(), 10)对象异步执行,最终会调用invokeSubscriber(PendingPost pendingPost)方法;
3.BACKGROUND(后台线程执行):即非UI线程中执行,如果当前线程是非UI线程则直接调用invokeSubscriber方法,否则将任务交给backgroundPoster —— BackgroundPoster(this)对象异步执行,最终会调用invokeSubscriber(PendingPost pendingPost)方法;
4.ASYNC(异步线程):将任务交给 asyncPoster—— AsyncPoster(this)对象异步执行,最终会调用invokeSubscriber(PendingPost pendingPost)方法;
threadMode详见1.4. EventBus3.0 线程模型。
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); } }invokeSubscriber实际上就是通过反射调用了订阅者的订阅函数并把event对象作为参数传入。
void invokeSubscriber(Subscription subscription, Object event) {
try { subscription.subscriberMethod.method.invoke( subscription.subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } }
做一些清理工作,最终分别从typesBySubscriber和subscriptionsByEventType里分别移除订阅者以及相关信息。注意不要重复解绑!
public synchronized void unregister(Object subscriber) { List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }Otto 是基于 Guava 的增强的事件总线,Guava 是google一个很强大的java开源库。
同:
1、都是事件总线框架,满足消息/事件传递的同时,也实现了组件间的解耦. 2、注册的共同点都是采用method方法进行一个集成。
3、都采用注解的方式来标注订阅方法(旧版本的EventBus通过固定方法名标记订阅者)
4、大部分规则相同,比如订阅方法只能有一个参数。
5、都不适用进程间通信
异:
1、OTTO更加轻量级,结构简单。EventBus稍微复杂一些。
2、OTTO默认在主线程中使用,不能在其他线程使用,通过设置ThreadEnforcer可以在任意线程使用,但是消息传递不能指定目标线程,EventBus实现了4种ThreadMode,线程之间消息传递非常灵活。 3、EventBus支持粘性事件,而OTTO不支持。即先发消息,再注册订阅者仍然能够收到消息。
4、OTTO有默认的生产者方法,可以产生默认消息,EventBus没有。
