前言
相信大家都用过EventBus这个开源库,它基于发布/订阅模式用于组件间的沟通,解耦合避免出现回调地狱,使用起来十分简单也很好用。这样的开源库是很值得我们学习的,今天就来学习一下他的源码与设计思想。

使用方法
使用方法很简单,按照官方文档介绍,分为三个步骤。
步骤1:定义事件
public static class MessageEvent {
}
步骤2:准备订阅者
定义订阅方法,来处理收到的事件。
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
/* Do something */};
并在Activity、Fragment中按照其生命周期进行注册与注销。
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
步骤3:发送事件
将定义好的事件,发送给订阅者。
EventBus.getDefault().post(new MessageEvent());
源码解析
使用方法很简洁,下面就根据使用步骤来解析一下源码。
准备订阅者
准备订阅者这一步骤,分为注册 注销以及准备订阅方法两步。
准备订阅方法
通过使用方法也可以看出,订阅方法是通过注解@Subscribe的方式来实现的。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD})
public @interface Subscribe {
/**
* 线程模式,默认为 POSTING
*/
ThreadMode threadMode() default ThreadMode.POSTING;
/**
* 是否是粘性事件,默认为 false
*/
boolean sticky() default false;
/**
* 事件订阅的优先级,默认为0。
* 当订阅者们处于同一线程模式中,优先级才会起作用,优先级高的订阅者将先收到推送过来的事件。
*/
int priority() default 0;
}
源码中用到了三个元注解,分别是:
@Documented:表示使用该注解的元素应被javadoc或类似工具文档化。@Retention(RetentionPolicy.RUNTIME):表示注解会被保留在class文件中,运行期间也会被识别,所以可以使用反射机制获取注解信息。@Target({ElementType.METHOD}):表示使用范围,该注解作用于描述方法。
每个订阅者都会有一个线程模式,线程模式决定其订阅方法运行在哪个线程中,这几种线程模式,分别为:
POSTING:默认的线程模式,在哪个线程发送事件就在对应的线程处理事件。MAIN:如果是在主线程发送事件,直接在主线程处理事件。反之,如果在子线程中发送事件,则需要切换到主线程来处理事件。(在Android中使用比较多)MAIN_ORDERED:不管在哪个线程发送事件,都会将事件入队列,在主线程上有序执行。BACKGROUND:如果是在子线程中发送事件,则直接在该子线程中处理事件。反之,如果是在主线程中发送事件,则需要将该事件入消息队列,切换到子线程,用线程池来有序处理该事件。(如果不是Android中使用,总是使用该模式)ASYNC:无论是在哪个线程发送事件,都会将该事件入消息队列,通过线程池在子线程上处理事件。如果订阅者方法的执行可能需要一些时间(如网络访问),则应使用此模式。
注册
如上述使用方法中所介绍,只需要一行即可完成订阅者的注册。
EventBus.getDefault().register(this);
EventBus.getDefault()方法其实就是通过单例模式返回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);
}
}
}
方法中的参数subscriber就是我们调用方法是传入的this,所以也就是表示Activity、Fragment。简单概括一下就是:我们通过获取订阅者的类对象,然后找到其订阅方法,调用subscribe订阅方法进行订阅。
所以重点就要看看他是怎么找到订阅方法以及怎么订阅方法里面做了什么?往下走:
找到订阅方法
/**
* 找到订阅方法
*
* @param subscriberClass 订阅者类
* @return 订阅方法列表
*/
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//先从缓存里找订阅方法
//METHOD_CACHE -> Map<Class<?>, List<SubscriberMethod>>: key为订阅者类,value为订阅方法列表
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
//如果缓存里有,直接返回使用
return subscriberMethods;
}
//是否使用 subscriber index,ignoreGeneratedIndex默认为false
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
//如果没有找到任何订阅方法,抛出异常,提醒用户使用 @Subscribe 方法来声明订阅方法
//也就是说,如果用户register注册了,但是没有任何@Subscribe订阅方法,会抛出异常来提示用户
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//如果订阅方法不为空,放入缓存中,以方便下次复用,key为订阅类的类名
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
简单概括一下就是:先根据订阅者类去METHOD_CACHE中查找,找到则直接返回订阅者方法列表,找不到则根据是否使用subscriber index 来决定是否使用 findUsingInfo还是findUsingReflection方法。找到订阅方法列表,加入到METHOD_CACHE中方便下次使用,反之,找不到订阅方法,抛出异常。
接下来看看是怎么找到并返回订阅者列表的,先看看findUsingReflection方法,即直接使用反射,不使用 subscriber index:
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
//查找父类,具体执行要看 skipSuperClasses 标志位
findState.moveToSuperclass();
}
//返回订阅方法列表
return getMethodsAndRelease(findState);
}
/**
* 通过类的反射提取订阅信息
*
* @param findState
*/
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
// getMethods(): 返回由类或接口声明的以及从超类和超接口继承的所有公共方法。
// getDeclaredMethods(): 返回类声明的方法,包括 public, protected, default (package),但不包括继承的方法
// 所以,相对比于 getMethods 方法,该方法速度更加快,尤其是在复杂的类中,如 Activity。
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
try {
methods = findState.clazz.getMethods();
} catch (LinkageError error) {
// super class of NoClassDefFoundError to be a bit more broad...
String msg = "Could not inspect methods of " + findState.clazz.getName();
if (ignoreGeneratedIndex) {
//请考虑使用 EventBus annotation processor 来避免反射
msg += ". Please consider using EventBus annotation processor to avoid reflection.";
} else {
msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
}
//找不到该类中方法,抛出异常
throw new EventBusException(msg, error);
}
// 因为getMethods()方法已经获取了超类的方法,所以这里设置不再去检查超类
findState.skipSuperClasses = true;
}
//遍历找到的方法
for (Method method : methods) {
//获取方法修饰符: public->1;private->2;protected->4;static->8;final->16
int modifiers = method.getModifiers();
//如果是public,且不是 abstract | static 类的
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
//获取方法参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
//获取方法的注解 Subscribe
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
//第一个参数就是事件类型
Class<?> eventType = parameterTypes[0];
//检查是否已经添加了订阅该类型事件的订阅方法,true->没有添加;false->已添加
if (findState.checkAdd(metho

本文详细分析了EventBus的源码,从使用步骤出发,探讨了EventBus如何找到订阅者、注册订阅、发送事件和处理粘性事件的过程。讲解了反射、注解、线程模式、订阅者索引等关键概念,并对比了EventBus2到EventBus3的改进。最后提到了设计模式的应用,如单例模式和建造者模式。
最低0.47元/天 解锁文章

887

被折叠的 条评论
为什么被折叠?



