EventBus 是一款用于 Android 的事件发布-订阅总线,由 GreenRobot 开发,Gihub 地址是:EventBus。它简化了应用程序内各个组件之间进行通讯的复杂度,尤为是碎片之间进行通讯的问题,能够避免因为使用广播通讯而带来的诸多不便。java
首先是 EventBus 的三个重要角色git
onEvent()
、onEventMainThread()
、onEventBackgroundThread()
和 onEventAsync()
,而在3.0以后事件处理的方法名能够随意取,不过须要加上注解@subscribe
,而且指定线程模型,默认是POSTING
。EventBus.getDefault()
就能够获得一个EventBus对象,而后再调用 post(Object)
方法发布事件便可。其次是 EventBus 的四种线程模型(EventBus3.0),分别是:github
在使用以前先要引入以下依赖:数组
implementation 'org.greenrobot:eventbus:3.1.1'
复制代码
而后,咱们定义一个事件的封装对象。在程序内部就使用该对象做为通讯的信息:缓存
public class MessageWrap {
public final String message;
public static MessageWrap getInstance(String message) {
return new MessageWrap(message);
}
private MessageWrap(String message) {
this.message = message;
}
}
复制代码
而后,咱们定义一个 Activity 要拿过来测试事件发布的效果:安全
@Route(path = BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY1)
public class EventBusActivity1 extends CommonActivity<ActivityEventBus1Binding> {
@Override
protected void doCreateView(Bundle savedInstanceState) {
// 为按钮添加添加单击事件
getBinding().btnReg.setOnClickListener(v -> EventBus.getDefault().register(this));
getBinding().btnNav2.setOnClickListener( v ->
ARouter.getInstance()
.build(BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY2)
.navigation());
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onGetMessage(MessageWrap message) {
getBinding().tvMessage.setText(message.message);
}
}
复制代码
这里咱们当按下按钮的时候向 EventBus 注册监听,而后按下另外一个按钮的时候跳转到拎一个 Activity,并在另外一个 Activity 发布咱们输入的事件。在上面的 Activity 中,咱们会添加一个监听的方法,即 onGetMessage()
,这里咱们须要为其加入注解 @Subscribe
并指定线程模型为主线程 MAIN
。最后,就是在 Activity 的 onDestroy()
方法中取消注册该 Activity。数据结构
下面是另外一个 Activity 的定义,在这个 Activity 中,咱们当按下按钮的时候从 EditText 中取出内容并进行发布,而后咱们退出到以前的 Activity,以测试是否正确监听到发布的内容:async
@Route(path = BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY2)
public class EventBusActivity2 extends CommonActivity<ActivityEventBus2Binding> {
@Override
protected void doCreateView(Bundle savedInstanceState) {
getBinding().btnPublish.setOnClickListener(v -> publishContent());
}
private void publishContent() {
String msg = getBinding().etMessage.getText().toString();
EventBus.getDefault().post(MessageWrap.getInstance(msg));
ToastUtils.makeToast("Published : " + msg);
}
}
复制代码
根据测试的结果,咱们的确成功地接收到了发送的信息。ide
所谓的黏性事件,就是指发送了该事件以后再订阅者依然可以接收到的事件。使用黏性事件的时候有两个地方须要作些修改。一个是订阅事件的地方,这里咱们在先打开的 Activity 中注册监听黏性事件:函数
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onGetStickyEvent(MessageWrap message) {
String txt = "Sticky event: " + message.message;
getBinding().tvStickyMessage.setText(txt);
}
复制代码
另外一个是发布事件的地方,这里咱们在新的开的 Activity 中发布黏性事件。即调用 EventBus 的 postSticky()
方法来发布事件:
private void publishStickyontent() {
String msg = getBinding().etMessage.getText().toString();
EventBus.getDefault().postSticky(MessageWrap.getInstance(msg));
ToastUtils.makeToast("Published : " + msg);
}
复制代码
按照上面的模式,咱们先在第一个 Activity 中打开第二个 Activity,而后在第二个 Activity 中发布黏性事件,并回到第一个 Activity 注册 EventBus。根据测试结果,当按下注册按钮的时候,会当即触发上面的订阅方法从而获取到了黏性事件。
在 @Subscribe
注解中总共有3个参数,上面咱们用到了其中的两个,这里咱们使用如下第三个参数,即 priority
。它用来指定订阅方法的优先级,是一个整数类型的值,默认是 0,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接受到事件。
为了对优先级进行测试,这里咱们须要对上面的代码进行一些修改。这里,咱们使用一个布尔类型的变量来判断是否应该取消事件的分发。咱们在一个较高优先级的方法中经过该布尔值进行判断,若是未 true
就中止该事件的继续分发,从而经过低优先级的订阅方法没法获取到事件来证实优先级较高的订阅方法率先获取到了事件。
这里有几个地方须要注意:
ThreadMode
参数的时候,它们的优先级才会与priority
指定的值一致;ThreadMode
参数为POSTING
的时候,它才能中止该事件的继续分发。因此,根据以上的内容,咱们须要对代码作以下的调整:
// 用来判断是否须要中止事件的继续分发
private boolean stopDelivery = false;
@Override
protected void doCreateView(Bundle savedInstanceState) {
// ...
getBinding().btnStop.setOnClickListener(v -> stopDelivery = true);
}
@Subscribe(threadMode = ThreadMode.POSTING, priority = 0)
public void onGetMessage(MessageWrap message) {
getBinding().tvMessage.setText(message.message);
}
// 订阅方法,须要与上面的方法的threadMode一致,而且优先级略高
@Subscribe(threadMode = ThreadMode.POSTING, sticky = true, priority = 1)
public void onGetStickyEvent(MessageWrap message) {
String txt = "Sticky event: " + message.message;
getBinding().tvStickyMessage.setText(txt);
if (stopDelivery) {
// 终止事件的继续分发
EventBus.getDefault().cancelEventDelivery(message);
}
}
复制代码
即咱们在以前的代码之上增长了一个按钮,用来将 stopDelivery
的值置为 true
。该字段随后将会被用来判断是否要终止事件的继续分发,由于咱们须要在代码中中止事件的继续分发,因此,咱们须要将上面的两个订阅方法的 threadMode
的值都置为ThreadMode.POSTING
。
按照,上面的测试方式,首先咱们在当前的 Activity 注册监听,而后跳转到另外一个 Activity,发布事件并返回。第一次的时候,这里的两个订阅方法都会被触发。而后,咱们按下中止分发的按钮,并再次执行上面的逻辑,此时只有优先级较高的方法获取到了事件并将该事件终止。
上面的内容是 EventBus 的基本使用方法,相关的源码参考:Github。
在分析 EventBus 源码的时候,咱们先从获取一个 EventBus 实例的方法入手,而后再分别看一下它的注册、取消注册、发布事件以及触发观察方法的代码是如何实现的。在下面的文章中咱们将会回答如下几个问题:
@Subscribe
注解的时候指定的 ThreadMode
是如何实如今不一样线程间传递数据的?在建立 EventBus 实例的时候,一种方式是按照咱们上面的形式,经过 EventBus 的静态方法 getDefault()
来获取一个实例。getDefault()
自己会调用其内部的构造方法,经过传入一个默认 的EventBusBuilder
来建立 EventBus。此外,咱们还能够直接经过 EventBus 的 builder()
方法获取一个 EventBusBuilder
的实例,而后经过该构建者模式来个性化地定制本身的 EventBus。即:
// 静态的单例实例
static volatile EventBus defaultInstance;
// 默认的构建者
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
// 实际上使用了DCL双检锁机制,这里简化了一下
public static EventBus getDefault() {
if (defaultInstance == null) defaultInstance = new EventBus();
return defaultInstance;
}
public EventBus() {
this(DEFAULT_BUILDER);
}
// 调用getDefault的时候,最终会调用该方法,使用DEFAULT_BUILDER建立一个实例
EventBus(EventBusBuilder builder) {
// ...
}
// 也可使用下面的方法获取一个构建者,而后使用它来个性化定制EventBus
public static EventBusBuilder builder() {
return new EventBusBuilder();
}
复制代码
当调用 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);
}
}
}
复制代码
这里的 SubscriberMethod
封装了订阅方法(使用 @Subscribe
注解的方法)类型的信息,它的定义以下所示。从下面能够的代码中咱们能够看出,实际上该类就是经过几个字段来存储 @Subscribe
注解中指定的类型信息,以及一个方法的类型变量。
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
// ...
}
复制代码
register()
方法经过 subscriberMethodFinder
实例的 findSubscriberMethods()
方法来获取该观察者类型中的全部订阅方法,而后将全部的订阅方法分别进行订阅。下面咱们先看下查找订阅者的方法。
下面是 SubscriberMethodFinder
中的 findSubscriberMethods()
方法:
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(...);
} else {
// 将获取到的订阅方法放置到缓存当中
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
复制代码
这里咱们先从缓存当中尝试获取某个观察者中的全部订阅方法,若是没有可用缓存的话就从该类中查找订阅方法,并在返回结果以前将这些方法信息放置到缓存当中。这里的 ignoreGeneratedIndex
参数表示是否忽略注解器生成的 MyEventBusIndex
,该值默认为 false
。而后,咱们会进入到下面的方法中获取订阅方法信息:
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
// 这里经过FindState对象来存储找到的方法信息
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
// 这里是一个循环操做,会从当前类开始遍历该类的全部父类
while (findState.clazz != null) {
// 获取订阅者信息
findState.subscriberInfo = getSubscriberInfo(findState); // 1
if (findState.subscriberInfo != null) {
// 若是使用了MyEventBusIndex,将会进入到这里并获取订阅方法信息
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
// 未使用MyEventBusIndex将会进入这里使用反射获取方法信息
findUsingReflectionInSingleClass(findState); // 2
}
// 将findState.clazz设置为当前的findState.clazz的父类
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
复制代码
在上面的代码中,会从当前订阅者类开始直到它最顶层的父类进行遍从来获取订阅方法信息。这里在循环的内部会根据咱们是否使用了 MyEventBusIndex
走两条路线,对于咱们没有使用它的,会直接使用反射来获取订阅方法信息,即进入2处。
下面是使用反射从订阅者中获得订阅方法的代码:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 获取该类中声明的全部方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
// 对方法进行遍历判断
for (Method method : methods) {
int modifiers = method.getModifiers();
// 这里会对方法的修饰符进行校验
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
// 这里对方法的输入参数进行校验
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
// 获取方法的注解,用来从注解中获取注解的声明信息
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
// 获取该方法的第一个参数
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
// 最终将封装以后的方法塞入到列表中
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(...);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(...);
}
}
}
复制代码
这里会对当前类中声明的全部方法进行校验,并将符合要求的方法的信息封装成一个SubscriberMethod
对象塞到列表中。
直到了如何拿到全部的订阅方法以后,咱们回到以前的代码,看下订阅过程当中的逻辑:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
// 将全部的观察者和订阅方法封装成一个Subscription对象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod); // 1
// 尝试从缓存中根据事件类型来获取全部的Subscription对象
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); // 2
if (subscriptions == null) {
// 指定的事件类型没有对应的观察对象的时候
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException(...);
}
}
// 这里会根据新加入的方法的优先级决定插入到队列中的位置
int size = subscriptions.size(); // 2
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 这里又会从“订阅者-事件类型”列表中尝试获取该订阅者对应的全部事件类型
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); // 3
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// 若是是黏性事件还要进行以下的处理
if (subscriberMethod.sticky) { // 4
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处将观察者和订阅方法封装成一个 Subscription
对象。而后,在2处用到了 CopyOnWriteArrayList
这个集合,它是一种适用于多读写少场景的数据结构,是一种线程安全的数组型的数据结构,主要用来存储一个事件类型所对应的所有的 Subscription
对象。EventBus在这里经过一个 Map<Class<?>, CopyOnWriteArrayList<Subscription>>
类型的哈希表来维护这个映射关系。而后,咱们的程序执行到2处,在这里会对 Subscription
对象的列表进行遍历,并根据订阅方法的优先级,为当前的 Subscription
对象寻找一个合适的位置。3的地方主要的逻辑是获取指定的观察者对应的所有的观察事件类型,这里也是经过一个哈希表来维护这种映射关系的。而后,在代码 4 处,程序会根据当前的订阅方法是不是黏性的,来决定是否将当前缓存中的信息发送给新订阅的方法。这里会经过 checkPostStickyEventToSubscription()
方法来发送信息,它内部的实现的逻辑和 post()
方法相似,咱们再也不进行说明。
取消注册的逻辑比较比较简单,基本上就是注册操做反过来——将当前订阅方法的信息从缓存中踢出来,咱们再也不进行分分析。下面咱们分析另外一个比较重要的地方,即发送事件相关的逻辑。
通知的逻辑相对来讲会比较复杂一些,由于这里面涉及一些线程之间的操做。咱们看下下面的代码吧:
public void post(Object event) {
// 这里从线程局部变量中取出当前线程的状态信息
PostingThreadState postingState = currentPostingThreadState.get();
// 这里是以上线程局部变量内部维护的一个事件队列
List<Object> eventQueue = postingState.eventQueue;
// 将当前要发送的事件加入到队列中
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
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); // 1
}
} finally {
// 恢复当前线程的信息
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
复制代码
这里的 currentPostingThreadState
是一个 ThreadLocal
类型的变量,其中存储了对应于当前线程的 PostingThreadState
对象,该对象中存储了当前线程对应的事件列表和线程的状态信息等。从上面的代码中能够看出,post()
方法会在1处不断从当前线程对应的队列中取出事件并进行发布。下面咱们看如下这里的 postSingleEvent()
方法。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
// 这里向上查找该事件的全部父类
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
// 对上面的事件进行处理
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
// 找不到该事件的异常处理
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent
&& eventClass != NoSubscriberEvent.class
&& eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
复制代码
在上面的代码中,咱们会根据 eventInheritance
的值决定是否要同时遍历当前事件的全部父类的事件信息并进行分发。若是设置为 true
就将执行这一操做,并最终使用 postSingleEventForEventType
对每一个事件类型进行处理。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
// 获取指定的事件对应的全部的观察对象
CopyOnWriteArrayList<Subscription> subscriptions;
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;
}
复制代码
在上面的代码中,咱们会经过传入的事件类型到缓存中取寻找它对应的所有的 Subscription
,而后对获得的 Subscription
列表进行遍历,并依次调用 postToSubscription()
方法执行事件的发布操做。下面是 postToSubscription()
方法的代码,这里咱们会根据订阅方法指定的 threadMode
信息来执行不一样的发布策略。
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 MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
invokeSubscriber(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(...);
}
}
复制代码
在上面的方法中,会根据当前的线程状态和订阅方法指定 的 threadMode
信息来决定合适触发方法。这里的 invokeSubscriber()
会在当前线程中当即调用反射来触发指定的观察者的订阅方法。不然会根据具体的状况将事件加入到不一样的队列中进行处理。这里的mainThreadPoster
最终继承自 Handler
,当调用它的 enqueue()
方法的时候,它会发送一个事件并在它自身的 handleMessage()
方法中从队列中取值并进行处理,从而达到在主线程中分发事件的目的。这里的 backgroundPoster
实现了 Runnable
接口,它会在调用 enqueue()
方法的时候,拿到 EventBus 的 ExecutorService
实例,并使用它来执行本身。在它的 run()
方法中会从队列中不断取值来进行执行。
以上就是Android中的EventBus的源码分析,这里咱们回答以前提出的几个问题来做结:
@Subscribe
注解的时候指定的 ThreadMode
是如何实如今不一样线程间传递数据的?要求主线程中的事件经过 Handler
来实如今主线程中执行,非主线程的方法会使用 EventBus 内部的 ExecutorService
来执行。实际在触发方法的时候会根据当前线程的状态和订阅方法的 ThreadMode
指定的线程状态来决定什么时候触发方法。非主线程的逻辑会在 post()
的时候加入到一个队列中被随后执行。
内部使用了缓存,确切来讲就是维护了一些映射的关系。可是它的缓存没有像 Guava 同样使用软引用之类方式进行优化,即一直是强引用类型的。
黏性事件会经过 EventBus 内部维护的一个事件类型-黏性事件
的哈希表存储,当注册一个观察者的时候,若是发现了它内部有黏性事件监听,会执行 post()
相似的逻辑将事件当即发送给该观察者。
若是您喜欢个人文章,能够在如下平台关注我: