• EventBus详解 (详解 + 原理)


    一、EventBus的使用介绍

    EventBus简介

    EventBus是一个开源库,由GreenRobot开发而来,是用于Android开发的 “事件发布订阅总线”, 用来进行模块间通信、解藕。它可以使用很少的代码,来实现多组件之间的通信。
    Android系统内置的事件通讯存在缺点:
    Android系统中的事件通信则是 handler (消息机制) 和 BroadCastReceiver (广播机制), 通过它们可以实现组件之间的事件通讯。缺点在于,代码量多、组件之易产生藕合引用。

    EventBus产生的背景

    当我们进行项目开发的时候,经常会遇到组件与组件之间、组件与后台线程之间的通信, 比如:子线程中执行数据请求,数据请求成功后,通过 Handler 或者 BroadCast 来通知UI更新。 两个Fragment之间可以通过Listener进行通信,但是问题来了,当程序越来越大时,就会要写很多的代码, 而且导致代码严重的耦合问题。为此 ,EventBus 应运而生。

    EventBus工作流程图解

    Publisher使用post发出一个Event事件,Subscriber在onEvent()函数中接收事件。

    EventBus 是一款在 Android 开发中使用的发布/订阅事件总线框架,基于观察者模式,将事件的接收者和发送者分开,简化了组件之间的通信,使用简单、效率高、体积小!下边是官方的 EventBus 原理图:

         

    EventBus的优势

    1,简化组件之间的通讯方式
    2,对通信双方进行解藕
    3,使用ThreadMode灵活切换工作线程
    4,速度快、性能好
    5,库比较小,不占内存

    EventBus缺点

    1、使用的时候有定义很多event类
    2、event在注册的时候会调用反射去遍历注册对象的方法在其中找出带有@subscriber标签的方法,性能不高。
    3、需要自己注册和反注册,如果忘了反注册就会导致内存泄漏

    EventBus环境配置

    1,依赖导入
    在app module的builde.gradle文件中导入依赖库:
    imlementation ‘org.greenrobot:eventbus:3.2.0
    2,配置混淆
    必须配置,否则会出现,debug环境正常,release环境接收不到事件的问题
    1. -keepattributes *Annotation*
    2. -keepclassmembers class * {
    3.     @org.greenrobot.eventbus.Subscribe ;
    4. }
    5. -keep enum org.greenrobot.eventbus.ThreadMode { *; }
    6. # Only required if you use AsyncExecutor
    7. -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    8.     (java.lang.Throwable);
    9. }

    EventBus的使用

    EventBus事件三部曲:Subscriber、Event、Publisher。
    Subscriber   —— EventBus的register方法,会接收到一个Object对象。
    Event           —— EventBus的post()方法中传入的事件类型 (可以是任意类型)。
    Publisher     —— EventBus的post()方法。
    1,创建一个事件类
    1. public class EventMessage {
    2.     private int type;
    3.     private String message;
    4.     public EventMessage(int type, String message) {
    5.         this.type = type;
    6.         this.message = message;
    7.     }
    8.     public int getType() {
    9.         return type;
    10.     }
    11.     public void setType(int type) {
    12.         this.type = type;
    13.     }
    14.     public String getMessage() {
    15.         return message;
    16.     }
    17.     public void setMessage(String message) {
    18.         this.message = message;
    19.     }
    20.     @Override
    21.     public String toString() {
    22.         return "type="+type+"--message= "+message;
    23.     }
    24. }
    2,在需要订阅事件的模块中,注册EventBus     
    注意事项:
    1,该方法有且仅有一个参数
    2,必须用public修饰,不能使用static或者abstract
    3,需要添加@Subscribe()注解
    1. public class EventBusActivity extends AppCompatActivity {
    2.     @Override
    3.     protected void onCreate(@Nullable Bundle savedInstanceState) {
    4.         super.onCreate(savedInstanceState);
    5.     }
    6.     
    7.     @Override
    8.     protected void onStart() {
    9.         super.onStart();
    10.         //注册EventBus
    11.         EventBus.getDefault().register(this);
    12.     }
    13.     //接收事件
    14.     @Subscribe(threadMode = ThreadMode.POSTING, sticky = true, priority = 1)
    15.     public void onReceiveMsg(EventMessage message){
    16.         Log.e("EventBus_Subscriber", "onReceiveMsg_POSTING: " + message.toString());
    17.     }
    18.     //接收事件
    19.     @Subscribe(threadMode = ThreadMode.MAIN, sticky = true, priority = 1)
    20.     public void onReceiveMsg1(EventMessage message){
    21.         Log.e("EventBus_Subscriber", "onReceiveMsg_MAIN: " + message.toString());
    22.     }
    23.     //接收事件
    24.     @Subscribe(threadMode = ThreadMode.MAIN_ORDERED, sticky = true, priority = 1)
    25.     public void onReceiveMsg2(EventMessage message){
    26.         Log.e("EventBus_Subscriber", "onReceiveMsg_MAIN_ORDERED: " + message.toString());
    27.     }
    28.     //接收事件
    29.     @Subscribe(threadMode = ThreadMode.BACKGROUND, sticky = true, priority = 1)
    30.     public void onReceiveMsg3(EventMessage message){
    31.         Log.e("EventBus_Subscriber", "onReceiveMsg_BACKGROUND: " + message.toString());
    32.     }
    33.     //接收事件
    34.     @Subscribe(threadMode = ThreadMode.ASYNC, sticky = true, priority = 1)
    35.     public void onReceiveMsg4(EventMessage message){
    36.         Log.e("EventBus_Subscriber", "onReceiveMsg__ASYNC: " + message.toString());
    37.     }
    38.     @Override
    39.     protected void onDestroy() {
    40.         super.onDestroy();
    41.         //取消事件
    42.         EventBus.getDefault().unregister(this);
    43.     }
    44. }
    3,创建订阅者发起通知
    使用eventbus.post(eventMessage) 或者 eventbus.postSticky(eventMessage)来发起事件
    1. @OnClick(R2.id.send_event_common)
    2. public void clickCommon(){
    3.     EventMessage message = new EventMessage(1, "这是一条普通事件");
    4.     EventBus.getDefault().post(message);
    5. }
    6. @OnClick(R2.id.send_event_sticky)
    7. public void clickSticky(){
    8.     EventMessage message = new EventMessage(1, "这是一条黏性事件");
    9.     EventBus.getDefault().postSticky(message);
    10. }

    Subscribe注解介绍

    Subscribe是EventBus自定义的注解,共有三个参数(可选):threadMode、boolean sticky、int priority。 完整的写法如下
     
    1. @Subscribe(threadMode = ThreadMode.MAIN,sticky = true,priority = 1)
    2. public void onReceiveMsg(EventMessage message) {
    3.     Log.e(TAG, "onReceiveMsg: " + message.toString());
    4. }

    1、ThreadMode 模式

    用来设置onReceiveMsg()方法,将在哪种线程环境下被调用,共有五种模式:
    1.1 POSTING: 默认模式
    表示发送事件 post() 发生在哪个线程,接收事件 onReceiveMsg() 就发生在哪个线程环境中。
    使用场景:
    这种模式不需要线程切换的一些判断逻辑, 直接分发至相同的线程环境,速度快、耗时少。
    1. 订阅处:
    2.     @Subscribe()
    3.     public void onReceiveMsg(EventMessage message) {
    4.         Log.e(TAG, "onReceiveMsg: " + message.toString());
    5.         Log.e(TAG, "onReceiveMsg: current thread name ="+Thread.currentThread().getName() );
    6.     }
    7.     发布处:
    8.     private View.OnClickListener mSendListener = new View.OnClickListener() {
    9.         @Override
    10.         public void onClick(View v) {
    11.             Log.e(TAG, "onClick: " );
    12.             new Thread(new Runnable() {
    13.                 @Override
    14.                 public void run() {
    15.                     String name = Thread.currentThread().getName();
    16.                     Log.e(TAG, "run: thread  name = "+name );
    17.                     EventMessage msg = new EventMessage(1,"Hello MainActivity");
    18.                     EventBus.getDefault().post(msg);
    19.                 }
    20.             }).start();
    21.         }
    22.     };

    1.2  MAIN / MAIN_ODERED: 主线程接收事件

    表示无论事件在什么线程环境发布 post(),事件的接收总是在主线程环境执行。
    二者之间的区别:
    1.2.1 对于MAIN模式而言:
    如果post事件也在主线程环境,就会阻塞post事件所在的线程环境,通俗点讲,就是在连续的多个post事件的情况下,只有在接收事件的方法执行完,才会执行下一个post事件。
    如果post事件不在主线程环境,并且在主线程接收事件中存在耗时操作的话,属于是非阻塞的。
    1.2.2 对于MAIN_ORDERED模式而言,无论post事件在哪种线程环境,它的执行流程都是非阻塞的。
    1.3  BACKGROUND:
    不管post事件发生在那个线程环境, 事件接收始终在一个子线程中执行。
    1.4  ASYNC:
    该模式表示,不管post事件处于哪种线程环境,事件接收处理总是在子线程。
               
    2、sticky黏性
    sticky是一个boolean类型,默认值为false,默认不开启黏性sticky特性,那么什么是sticky特性呢?
    上面的例子都是对订阅者 (接收事件) 先进行注册,然后在进行post事件。那么sticky的作用就是:订阅者可以先不进行注册,如果post事件已经发出,再注册订阅者,同样可以接收到事件,并进行处理
    其实就是在sticky场景下,EventBus对事件进行了保存而已。
       
    1. private View.OnClickListener mGoListener = new View.OnClickListener() {
    2.      @Override
    3.      public void onClick(View v) {
    4.           Log.e(TAG, "onClick: post");
    5.           EventMessage message = new EventMessage(233, "post message before");
    6.           EventBus.getDefault().postSticky(message);
    7.      }
    8. };
    9. private View.OnClickListener mRegisterListener = new View.OnClickListener() {
    10.      @Override
    11.      public void onClick(View v) {
    12.           Log.e(TAG, "onClick: start register" );
    13.           //当触发点击事件的时候,才进行注册,这个时候,可同样可以接收到上个点击事件中发出的 事件。
    14.             EventBus.getDefault().register(MainActivity.this);
    15.      }
    16. };

    3、priority

    priority是优先级,是一个int类型,默认值为0。值越大,优先级越高,越优先接收到事件。
    值得注意的是,只有在post事件和事件接收处理,处于同一个线程环境的时候,才有意义。

    二、EventBus的原理

    清晰讲解:EventBus核心原理其实保存这三张图就可以弄懂了,收藏一下 - 知乎

    本文主要是从 EventBus 使用的方式入手,来分析 EventBus 背后的实现原理,以下内容基于eventbus:3.1.1版本,主要包括如下几个方面的内容:

    • Subscribe注解
    • 注册事件订阅方法
    • 取消注册
    • 发送事件
    • 事件处理
    • 粘性事件
    • Subscriber Index
    • 核心流程梳理

    一、Subscribe注解

    EventBus3.0 开始用Subscribe注解配置事件订阅方法,不再使用方法名了,例如:

    1. @Subscribe
    2. public void handleEvent(String event) {
    3. // do something
    4. }

    其中事件类型可以是 Java 中已有的类型或者我们自定义的类型。具体看下Subscribe注解的实现:

    1. @Documented
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Target({ElementType.METHOD})
    4. public @interface Subscribe {
    5. // 指定事件订阅方法的线程模式,即在那个线程执行事件订阅方法处理事件,默认为POSTING
    6. ThreadMode threadMode() default ThreadMode.POSTING;
    7. // 是否支持粘性事件,默认为false
    8. boolean sticky() default false;
    9. // 指定事件订阅方法的优先级,默认为0,如果多个事件订阅方法可以接收相同事件的,则优先级高的先接收到事件
    10. int priority() default 0;
    11. }

    所以在使用Subscribe注解时可以根据需求指定threadModestickypriority三个属性。

    其中threadMode属性有如下几个可选值:

    • ThreadMode.POSTING,默认的线程模式,在那个线程发送事件就在对应线程处理事件,避免了线程切换,效率高。
    • ThreadMode.MAIN,如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
    • ThreadMode.MAIN_ORDERED,无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
    • ThreadMode.BACKGROUND,如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件;如果在子线程发送事件,则直接在发送事件的线程处理事件。
    • ThreadMode.ASYNC,无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。

    二、注册事件订阅方法

    注册事件的方式如下:

    EventBus.getDefault().register(this);
    

    其中getDefault()是一个单例方法,保证当前只有一个EventBus实例:

    1. public static EventBus getDefault() {
    2. if (defaultInstance == null) {
    3. synchronized (EventBus.class) {
    4. if (defaultInstance == null) {
    5. defaultInstance = new EventBus();
    6. }
    7. }
    8. }
    9. return defaultInstance;
    10. }

    继续看new EventBus()做了些什么:

    1. public EventBus() {
    2. this(DEFAULT_BUILDER);
    3. }

    在这里又调用了EventBus的另一个构造函数来完成它相关属性的初始化:

    1. EventBus(EventBusBuilder builder) {
    2. logger = builder.getLogger();
    3. subscriptionsByEventType = new HashMap<>();
    4. typesBySubscriber = new HashMap<>();
    5. stickyEvents = new ConcurrentHashMap<>();
    6. mainThreadSupport = builder.getMainThreadSupport();
    7. mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    8. backgroundPoster = new BackgroundPoster(this);
    9. asyncPoster = new AsyncPoster(this);
    10. indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    11. subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
    12. builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    13. logSubscriberExceptions = builder.logSubscriberExceptions;
    14. logNoSubscriberMessages = builder.logNoSubscriberMessages;
    15. sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    16. sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    17. throwSubscriberException = builder.throwSubscriberException;
    18. eventInheritance = builder.eventInheritance;
    19. executorService = builder.executorService;
    20. }

    DEFAULT_BUILDER就是一个默认的EventBusBuilder

    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
    

    如果有需要的话,我们也可以通过配置EventBusBuilder来更改EventBus的属性,例如用如下方式注册事件:

    1. EventBus.builder()
    2. .eventInheritance(false)
    3. .logSubscriberExceptions(false)
    4. .build()
    5. .register(this);

    有了EventBus的实例就可以进行注册了:

    1. public void register(Object subscriber) {
    2. // 得到当前要注册类的Class对象
    3. Class subscriberClass = subscriber.getClass();
    4. // 根据Class查找当前类中订阅了事件的方法集合,即使用了Subscribe注解、有public修饰符、一个参数的方法
    5. // SubscriberMethod类主要封装了符合条件方法的相关信息:
    6. // Method对象、线程模式、事件类型、优先级、是否是粘性事等
    7. List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    8. synchronized (this) {
    9. // 循环遍历订阅了事件的方法集合,以完成注册
    10. for (SubscriberMethod subscriberMethod : subscriberMethods) {
    11. subscribe(subscriber, subscriberMethod);
    12. }
    13. }
    14. }

    可以看到register()方法主要分为查找和注册两部分,首先来看查找的过程,从findSubscriberMethods()开始:

    1. List findSubscriberMethods(Class subscriberClass) {
    2. // METHOD_CACHE是一个ConcurrentHashMap,直接保存了subscriberClass和对应SubscriberMethod的集合,以提高注册效率,赋值重复查找。
    3. List subscriberMethods = METHOD_CACHE.get(subscriberClass);
    4. if (subscriberMethods != null) {
    5. return subscriberMethods;
    6. }
    7. // 由于使用了默认的EventBusBuilder,则ignoreGeneratedIndex属性默认为false,即是否忽略注解生成器
    8. if (ignoreGeneratedIndex) {
    9. subscriberMethods = findUsingReflection(subscriberClass);
    10. } else {
    11. subscriberMethods = findUsingInfo(subscriberClass);
    12. }
    13. // 如果对应类中没有符合条件的方法,则抛出异常
    14. if (subscriberMethods.isEmpty()) {
    15. throw new EventBusException("Subscriber " + subscriberClass
    16. + " and its super classes have no public methods with the @Subscribe annotation");
    17. } else {
    18. // 保存查找到的订阅事件的方法
    19. METHOD_CACHE.put(subscriberClass, subscriberMethods);
    20. return subscriberMethods;
    21. }
    22. }

    findSubscriberMethods()流程很清晰,即先从缓存中查找,如果找到则直接返回,否则去做下一步的查找过程,然后缓存查找到的集合,根据上边的注释可知findUsingInfo()方法会被调用:

    1. private List findUsingInfo(Class subscriberClass) {
    2. FindState findState = prepareFindState();
    3. findState.initForSubscriber(subscriberClass);
    4. // 初始状态下findState.clazz就是subscriberClass
    5. while (findState.clazz != null) {
    6. findState.subscriberInfo = getSubscriberInfo(findState);
    7. // 条件不成立
    8. if (findState.subscriberInfo != null) {
    9. SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
    10. for (SubscriberMethod subscriberMethod : array) {
    11. if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
    12. findState.subscriberMethods.add(subscriberMethod);
    13. }
    14. }
    15. } else {
    16. // 通过反射查找订阅事件的方法
    17. findUsingReflectionInSingleClass(findState);
    18. }
    19. // 修改findState.clazz为subscriberClass的父类Class,即需要遍历父类
    20. findState.moveToSuperclass();
    21. }
    22. // 查找到的方法保存在了FindState实例的subscriberMethods集合中。
    23. // 使用subscriberMethods构建一个新的List
    24. // 释放掉findState
    25. return getMethodsAndRelease(findState);
    26. }

    findUsingInfo()方法会在当前要注册的类以及其父类中查找订阅事件的方法,这里出现了一个FindState类,它是SubscriberMethodFinder的内部类,用来辅助查找订阅事件的方法,具体的查找过程在findUsingReflectionInSingleClass()方法,它主要通过反射查找订阅事件的方法:

    1. private void findUsingReflectionInSingleClass(FindState findState) {
    2. Method[] methods;
    3. try {
    4. // This is faster than getMethods, especially when subscribers are fat classes like Activities
    5. methods = findState.clazz.getDeclaredMethods();
    6. } catch (Throwable th) {
    7. // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
    8. methods = findState.clazz.getMethods();
    9. findState.skipSuperClasses = true;
    10. }
    11. // 循环遍历当前类的方法,筛选出符合条件的
    12. for (Method method : methods) {
    13. // 获得方法的修饰符
    14. int modifiers = method.getModifiers();
    15. // 如果是public类型,但非abstract、static等
    16. if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
    17. // 获得当前方法所有参数的类型
    18. Class[] parameterTypes = method.getParameterTypes();
    19. // 如果当前方法只有一个参数
    20. if (parameterTypes.length == 1) {
    21. Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
    22. // 如果当前方法使用了Subscribe注解
    23. if (subscribeAnnotation != null) {
    24. // 得到该参数的类型
    25. Class eventType = parameterTypes[0];
    26. // checkAdd()方法用来判断FindState的anyMethodByEventType map是否已经添加过以当前eventType为key的键值对,没添加过则返回true
    27. if (findState.checkAdd(method, eventType)) {
    28. // 得到Subscribe注解的threadMode属性值,即线程模式
    29. ThreadMode threadMode = subscribeAnnotation.threadMode();
    30. // 创建一个SubscriberMethod对象,并添加到subscriberMethods集合
    31. findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
    32. subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
    33. }
    34. }
    35. } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
    36. String methodName = method.getDeclaringClass().getName() + "." + method.getName();
    37. throw new EventBusException("@Subscribe method " + methodName +
    38. "must have exactly 1 parameter but has " + parameterTypes.length);
    39. }
    40. } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
    41. String methodName = method.getDeclaringClass().getName() + "." + method.getName();
    42. throw new EventBusException(methodName +
    43. " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
    44. }
    45. }
    46. }

    到此register()方法中findSubscriberMethods()流程就分析完了,我们已经找到了当前注册类及其父类中订阅事件的方法的集合。接下来分析具体的注册流程,即register()中的subscribe()方法:

    1. private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    2. // 得到当前订阅了事件的方法的参数类型
    3. Class eventType = subscriberMethod.eventType;
    4. // Subscription类保存了要注册的类对象以及当前的subscriberMethod
    5. Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    6. // subscriptionsByEventType是一个HashMap,保存了以eventType为key,Subscription对象集合为value的键值对
    7. // 先查找subscriptionsByEventType是否存在以当前eventType为key的值
    8. CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
    9. // 如果不存在,则创建一个subscriptions,并保存到subscriptionsByEventType
    10. if (subscriptions == null) {
    11. subscriptions = new CopyOnWriteArrayList<>();
    12. subscriptionsByEventType.put(eventType, subscriptions);
    13. } else {
    14. if (subscriptions.contains(newSubscription)) {
    15. throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
    16. + eventType);
    17. }
    18. }
    19. // 添加上边创建的newSubscription对象到subscriptions中
    20. int size = subscriptions.size();
    21. for (int i = 0; i <= size; i++) {
    22. if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
    23. subscriptions.add(i, newSubscription);
    24. break;
    25. }
    26. }
    27. // typesBySubscribere也是一个HashMap,保存了以当前要注册类的对象为key,注册类中订阅事件的方法的参数类型的集合为value的键值对
    28. // 查找是否存在对应的参数类型集合
    29. List> subscribedEvents = typesBySubscriber.get(subscriber);
    30. // 不存在则创建一个subscribedEvents,并保存到typesBySubscriber
    31. if (subscribedEvents == null) {
    32. subscribedEvents = new ArrayList<>();
    33. typesBySubscriber.put(subscriber, subscribedEvents);
    34. }
    35. // 保存当前订阅了事件的方法的参数类型
    36. subscribedEvents.add(eventType);
    37. // 粘性事件相关的,后边具体分析
    38. if (subscriberMethod.sticky) {
    39. if (eventInheritance) {
    40. // Existing sticky events of all subclasses of eventType have to be considered.
    41. // Note: Iterating over all events may be inefficient with lots of sticky events,
    42. // thus data structure should be changed to allow a more efficient lookup
    43. // (e.g. an additional map storing sub classes of super classes: Class -> List).
    44. Set, Object>> entries = stickyEvents.entrySet();
    45. for (Map.Entry, Object> entry : entries) {
    46. Class candidateEventType = entry.getKey();
    47. if (eventType.isAssignableFrom(candidateEventType)) {
    48. Object stickyEvent = entry.getValue();
    49. checkPostStickyEventToSubscription(newSubscription, stickyEvent);
    50. }
    51. }
    52. } else {
    53. Object stickyEvent = stickyEvents.get(eventType);
    54. checkPostStickyEventToSubscription(newSubscription, stickyEvent);
    55. }
    56. }
    57. }

    这就是注册的核心流程,所以subscribe()方法主要是得到了subscriptionsByEventTypetypesBySubscriber两个 HashMap。我们在发送事件的时候要用到subscriptionsByEventType,完成事件的处理。当取消 EventBus 注册的时候要用到typesBySubscribersubscriptionsByEventType,完成相关资源的释放。

    三、取消注册

    接下来看,EventBus 如何取消注册:

    EventBus.getDefault().unregister(this);
    

    核心的方法就是unregister()

    1. public synchronized void unregister(Object subscriber) {
    2. // 得到当前注册类对象 对应的 订阅事件方法的参数类型 的集合
    3. List> subscribedTypes = typesBySubscriber.get(subscriber);
    4. if (subscribedTypes != null) {
    5. // 遍历参数类型集合,释放之前缓存的当前类中的Subscription
    6. for (Class eventType : subscribedTypes) {
    7. unsubscribeByEventType(subscriber, eventType);
    8. }
    9. // 删除以subscriber为key的键值对
    10. typesBySubscriber.remove(subscriber);
    11. } else {
    12. logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    13. }
    14. }

    内容很简单,继续看unsubscribeByEventType()方法:

    1. private void unsubscribeByEventType(Object subscriber, Class eventType) {
    2. // 得到当前参数类型对应的Subscription集合
    3. List subscriptions = subscriptionsByEventType.get(eventType);
    4. if (subscriptions != null) {
    5. int size = subscriptions.size();
    6. // 遍历Subscription集合
    7. for (int i = 0; i < size; i++) {
    8. Subscription subscription = subscriptions.get(i);
    9. // 如果当前subscription对象对应的注册类对象 和 要取消注册的注册类对象相同,则删除当前subscription对象
    10. if (subscription.subscriber == subscriber) {
    11. subscription.active = false;
    12. subscriptions.remove(i);
    13. i--;
    14. size--;
    15. }
    16. }
    17. }
    18. }

    所以在unregister()方法中,释放了typesBySubscribersubscriptionsByEventType中缓存的资源。

    四、发送事件

    当发送一个事件的时候,我们可以通过如下方式:

    EventBus.getDefault().post("Hello World!")
    

    可以看到,发送事件就是通过post()方法完成的:

    1. public void post(Object event) {
    2. // currentPostingThreadState是一个PostingThreadState类型的ThreadLocal
    3. // PostingThreadState类保存了事件队列和线程模式等信息
    4. PostingThreadState postingState = currentPostingThreadState.get();
    5. List eventQueue = postingState.eventQueue;
    6. // 将要发送的事件添加到事件队列
    7. eventQueue.add(event);
    8. // isPosting默认为false
    9. if (!postingState.isPosting) {
    10. // 是否为主线程
    11. postingState.isMainThread = isMainThread();
    12. postingState.isPosting = true;
    13. if (postingState.canceled) {
    14. throw new EventBusException("Internal error. Abort state was not reset");
    15. }
    16. try {
    17. // 遍历事件队列
    18. while (!eventQueue.isEmpty()) {
    19. // 发送单个事件
    20. // eventQueue.remove(0),从事件队列移除事件
    21. postSingleEvent(eventQueue.remove(0), postingState);
    22. }
    23. } finally {
    24. postingState.isPosting = false;
    25. postingState.isMainThread = false;
    26. }
    27. }
    28. }
    29. 所以post()方法先将发送的事件保存的事件队列,然后通过循环出队列,将事件交给postSingleEvent()方法处理:

      1. private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
      2. Class eventClass = event.getClass();
      3. boolean subscriptionFound = false;
      4. // eventInheritance默认为true,表示是否向上查找事件的父类
      5. if (eventInheritance) {
      6. // 查找当前事件类型的Class,连同当前事件类型的Class保存到集合
      7. List> eventTypes = lookupAllEventTypes(eventClass);
      8. int countTypes = eventTypes.size();
      9. // 遍历Class集合,继续处理事件
      10. for (int h = 0; h < countTypes; h++) {
      11. Class clazz = eventTypes.get(h);
      12. subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
      13. }
      14. } else {
      15. subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
      16. }
      17. if (!subscriptionFound) {
      18. if (logNoSubscriberMessages) {
      19. logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
      20. }
      21. if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
      22. eventClass != SubscriberExceptionEvent.class) {
      23. post(new NoSubscriberEvent(this, event));
      24. }
      25. }
      26. }

      postSingleEvent()方法中,根据eventInheritance属性,决定是否向上遍历事件的父类型,然后用postSingleEventForEventType()方法进一步处理事件:

      1. private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) {
      2. CopyOnWriteArrayList subscriptions;
      3. synchronized (this) {
      4. // 获取事件类型对应的Subscription集合
      5. subscriptions = subscriptionsByEventType.get(eventClass);
      6. }
      7. // 如果已订阅了对应类型的事件
      8. if (subscriptions != null && !subscriptions.isEmpty()) {
      9. for (Subscription subscription : subscriptions) {
      10. // 记录事件
      11. postingState.event = event;
      12. // 记录对应的subscription
      13. postingState.subscription = subscription;
      14. boolean aborted = false;
      15. try {
      16. // 最终的事件处理
      17. postToSubscription(subscription, event, postingState.isMainThread);
      18. aborted = postingState.canceled;
      19. } finally {
      20. postingState.event = null;
      21. postingState.subscription = null;
      22. postingState.canceled = false;
      23. }
      24. if (aborted) {
      25. break;
      26. }
      27. }
      28. return true;
      29. }
      30. return false;
      31. }

      postSingleEventForEventType()方法核心就是遍历发送的事件类型对应的Subscription集合,然后调用postToSubscription()方法处理事件。

      五、处理事件

      接着上边的继续分析,postToSubscription()内部会根据订阅事件方法的线程模式,间接或直接的以发送的事件为参数,通过反射执行订阅事件的方法。

      1. private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
      2. // 判断订阅事件方法的线程模式
      3. switch (subscription.subscriberMethod.threadMode) {
      4. // 默认的线程模式,在那个线程发送事件就在那个线程处理事件
      5. case POSTING:
      6. invokeSubscriber(subscription, event);
      7. break;
      8. // 在主线程处理事件
      9. case MAIN:
      10. // 如果在主线程发送事件,则直接在主线程通过反射处理事件
      11. if (isMainThread) {
      12. invokeSubscriber(subscription, event);
      13. } else {
      14. // 如果是在子线程发送事件,则将事件入队列,通过Handler切换到主线程执行处理事件
      15. // mainThreadPoster 不为空
      16. mainThreadPoster.enqueue(subscription, event);
      17. }
      18. break;
      19. // 无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
      20. // mainThreadPoster 不为空
      21. case MAIN_ORDERED:
      22. if (mainThreadPoster != null) {
      23. mainThreadPoster.enqueue(subscription, event);
      24. } else {
      25. invokeSubscriber(subscription, event);
      26. }
      27. break;
      28. case BACKGROUND:
      29. // 如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件
      30. if (isMainThread) {
      31. backgroundPoster.enqueue(subscription, event);
      32. } else {
      33. // 如果在子线程发送事件,则直接在发送事件的线程通过反射处理事件
      34. invokeSubscriber(subscription, event);
      35. }
      36. break;
      37. // 无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。
      38. case ASYNC:
      39. asyncPoster.enqueue(subscription, event);
      40. break;
      41. default:
      42. throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
      43. }
      44. }

      可以看到,postToSubscription()方法就是根据订阅事件方法的线程模式、以及发送事件的线程来判断如何处理事件,至于处理方式主要有两种:
      一种是在相应线程直接通过invokeSubscriber()方法,用反射来执行订阅事件的方法,这样发送出去的事件就被订阅者接收并做相应处理了:

      1. void invokeSubscriber(Subscription subscription, Object event) {
      2. try {
      3. subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
      4. } catch (InvocationTargetException e) {
      5. handleSubscriberException(subscription, event, e.getCause());
      6. } catch (IllegalAccessException e) {
      7. throw new IllegalStateException("Unexpected exception", e);
      8. }
      9. }

      另外一种是先将事件入队列(其实底层是一个List),然后做进一步处理,我们以mainThreadPoster.enqueue(subscription, event)为例简单的分析下,其中mainThreadPosterHandlerPoster类的一个实例,来看该类的主要实现:

      1. public class HandlerPoster extends Handler implements Poster {
      2. private final PendingPostQueue queue;
      3. private boolean handlerActive;
      4. ......
      5. public void enqueue(Subscription subscription, Object event) {
      6. // 用subscription和event封装一个PendingPost对象
      7. PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
      8. synchronized (this) {
      9. // 入队列
      10. queue.enqueue(pendingPost);
      11. if (!handlerActive) {
      12. handlerActive = true;
      13. // 发送开始处理事件的消息,handleMessage()方法将被执行,完成从子线程到主线程的切换
      14. if (!sendMessage(obtainMessage())) {
      15. throw new EventBusException("Could not send handler message");
      16. }
      17. }
      18. }
      19. }
      20. @Override
      21. public void handleMessage(Message msg) {
      22. boolean rescheduled = false;
      23. try {
      24. long started = SystemClock.uptimeMillis();
      25. // 死循环遍历队列
      26. while (true) {
      27. // 出队列
      28. PendingPost pendingPost = queue.poll();
      29. ......
      30. // 进一步处理pendingPost
      31. eventBus.invokeSubscriber(pendingPost);
      32. ......
      33. }
      34. } finally {
      35. handlerActive = rescheduled;
      36. }
      37. }
      38. }

      所以HandlerPosterenqueue()方法主要就是将subscriptionevent对象封装成一个PendingPost对象,然后保存到队列里,之后通过Handler切换到主线程,在handleMessage()方法将中将PendingPost对象循环出队列,交给invokeSubscriber()方法进一步处理:

      1. void invokeSubscriber(PendingPost pendingPost) {
      2. Object event = pendingPost.event;
      3. Subscription subscription = pendingPost.subscription;
      4. // 释放pendingPost引用的资源
      5. PendingPost.releasePendingPost(pendingPost);
      6. if (subscription.active) {
      7. // 用反射来执行订阅事件的方法
      8. invokeSubscriber(subscription, event);
      9. }
      10. }

      这个方法很简单,主要就是从pendingPost中取出之前保存的eventsubscription,然后用反射来执行订阅事件的方法,又回到了第一种处理方式。所以mainThreadPoster.enqueue(subscription, event)的核心就是先将将事件入队列,然后通过Handler从子线程切换到主线程中去处理事件。

      backgroundPoster.enqueue()asyncPoster.enqueue也类似,内部都是先将事件入队列,然后再出队列,但是会通过线程池去进一步处理事件。

      六、粘性事件

      一般情况,我们使用 EventBus 都是准备好订阅事件的方法,然后注册事件,最后在发送事件,即要先有事件的接收者。但粘性事件却恰恰相反,我们可以先发送事件,后续再准备订阅事件的方法、注册事件。

      由于这种差异,我们分析粘性事件原理时,先从事件发送开始,发送一个粘性事件通过如下方式:

      EventBus.getDefault().postSticky("Hello World!");
      

      来看postSticky()方法是如何实现的:

      1. public void postSticky(Object event) {
      2. synchronized (stickyEvents) {
      3. stickyEvents.put(event.getClass(), event);
      4. }
      5. post(event);
      6. }

      postSticky()方法主要做了两件事,先将事件类型和对应事件保存到stickyEvents中,方便后续使用;然后执行post(event)继续发送事件,这个post()方法就是之前发送的post()方法。所以,如果在发送粘性事件前,已经有了对应类型事件的订阅者,及时它是非粘性的,依然可以接收到发送出的粘性事件。

      发送完粘性事件后,再准备订阅粘性事件的方法,并完成注册。核心的注册事件流程还是我们之前的register()方法中的subscribe()方法,前边分析subscribe()方法时,有一段没有分析的代码,就是用来处理粘性事件的:

      1. private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
      2. ......
      3. ......
      4. ......
      5. // 如果当前订阅事件的方法的Subscribe注解的sticky属性为true,即该方法可接受粘性事件
      6. if (subscriberMethod.sticky) {
      7. // 默认为true,表示是否向上查找事件的父类
      8. if (eventInheritance) {
      9. // stickyEvents就是发送粘性事件时,保存了事件类型和对应事件
      10. Set, Object>> entries = stickyEvents.entrySet();
      11. for (Map.Entry, Object> entry : entries) {
      12. Class candidateEventType = entry.getKey();
      13. // 如果candidateEventType是eventType的子类或
      14. if (eventType.isAssignableFrom(candidateEventType)) {
      15. // 获得对应的事件
      16. Object stickyEvent = entry.getValue();
      17. // 处理粘性事件
      18. checkPostStickyEventToSubscription(newSubscription, stickyEvent);
      19. }
      20. }
      21. } else {
      22. Object stickyEvent = stickyEvents.get(eventType);
      23. checkPostStickyEventToSubscription(newSubscription, stickyEvent);
      24. }
      25. }
      26. }

      可以看到,处理粘性事件就是在 EventBus 注册时,遍历stickyEvents,如果当前要注册的事件订阅方法是粘性的,并且该方法接收的事件类型和stickyEvents中某个事件类型相同或者是其父类,则取出stickyEvents中对应事件类型的具体事件,做进一步处理。继续看checkPostStickyEventToSubscription()处理方法:

      1. private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
      2. if (stickyEvent != null) {
      3. postToSubscription(newSubscription, stickyEvent, isMainThread());
      4. }
      5. }

      最终还是通过postToSubscription()方法完成粘性事件的处理,这就是粘性事件的整个处理流程。

      七、Subscriber Index

      回顾之前分析的 EventBus 注册事件流程,主要是在项目运行时通过反射来查找订事件的方法信息,这也是默认的实现,如果项目中有大量的订阅事件的方法,必然会对项目运行时的性能产生影响。其实除了在项目运行时通过反射查找订阅事件的方法信息,EventBus 还提供了在项目编译时通过注解处理器查找订阅事件方法信息的方式,生成一个辅助的索引类来保存这些信息,这个索引类就是Subscriber Index,其实和 ButterKnife 的原理类似。

      要在项目编译时查找订阅事件的方法信息,首先要在 app 的 build.gradle 中加入如下配置:

      1. android {
      2. defaultConfig {
      3. javaCompileOptions {
      4. annotationProcessorOptions {
      5. // 根据项目实际情况,指定辅助索引类的名称和包名
      6. arguments = [ eventBusIndex : 'com.shh.sometest.MyEventBusIndex' ]
      7. }
      8. }
      9. }
      10. }
      11. dependencies {
      12. compile 'org.greenrobot:eventbus:3.1.1'
      13. // 引入注解处理器
      14. annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
      15. }

      然后在项目的 Application 中添加如下配置,以生成一个默认的 EventBus 单例:

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

      之后的用法就和我们平时使用 EventBus 一样了。当项目编译时会在生成对应的MyEventBusIndex类:


      对应的源码如下:

      1. public class MyEventBusIndex implements SubscriberInfoIndex {
      2. private static final Map, SubscriberInfo> SUBSCRIBER_INDEX;
      3. static {
      4. SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();
      5. putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
      6. new SubscriberMethodInfo("changeText", String.class),
      7. }));
      8. }
      9. private static void putIndex(SubscriberInfo info) {
      10. SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
      11. }
      12. @Override
      13. public SubscriberInfo getSubscriberInfo(Class subscriberClass) {
      14. SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
      15. if (info != null) {
      16. return info;
      17. } else {
      18. return null;
      19. }
      20. }
      21. }

      其中SUBSCRIBER_INDEX是一个HashMap,保存了当前注册类的 Class 类型和其中事件订阅方法的信息。接下来分析下使用 Subscriber Index 时 EventBus 的注册流程,我们先分析:

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

      首先创建一个EventBusBuilder,然后通过addIndex()方法添加索引类的实例:

      1. public EventBusBuilder addIndex(SubscriberInfoIndex index) {
      2. if (subscriberInfoIndexes == null) {
      3. subscriberInfoIndexes = new ArrayList<>();
      4. }
      5. subscriberInfoIndexes.add(index);
      6. return this;
      7. }

      即把生成的索引类的实例保存在subscriberInfoIndexes集合中,然后用installDefaultEventBus()创建默认的 EventBus实例:

      1. public EventBus installDefaultEventBus() {
      2. synchronized (EventBus.class) {
      3. if (EventBus.defaultInstance != null) {
      4. throw new EventBusException("Default instance already exists." +
      5. " It may be only set once before it's used the first time to ensure consistent behavior.");
      6. }
      7. EventBus.defaultInstance = build();
      8. return EventBus.defaultInstance;
      9. }
      10. }
      1. public EventBus build() {
      2. // this 代表当前EventBusBuilder对象
      3. return new EventBus(this);
      4. }

      即用当前EventBusBuilder对象创建一个 EventBus 实例,这样我们通过EventBusBuilder配置的 Subscriber Index 也就传递到了EventBus实例中,然后赋值给EventBus的 defaultInstance成员变量。之前我们在分析 EventBus 的getDefault()方法时已经见到了defaultInstance

      1. public static EventBus getDefault() {
      2. if (defaultInstance == null) {
      3. synchronized (EventBus.class) {
      4. if (defaultInstance == null) {
      5. defaultInstance = new EventBus();
      6. }
      7. }
      8. }
      9. return defaultInstance;
      10. }

      所以在 Application 中生成了 EventBus 的默认单例,这样就保证了在项目其它地方执行EventBus.getDefault()就能得到唯一的 EventBus 实例!之前在分析注册流程时有一个
      方法findUsingInfo()

      1. private List findUsingInfo(Class subscriberClass) {
      2. FindState findState = prepareFindState();
      3. findState.initForSubscriber(subscriberClass);
      4. while (findState.clazz != null) {
      5. // 查找SubscriberInfo
      6. findState.subscriberInfo = getSubscriberInfo(findState);
      7. // 条件成立
      8. if (findState.subscriberInfo != null) {
      9. // 获得当前注册类中所有订阅了事件的方法
      10. SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
      11. for (SubscriberMethod subscriberMethod : array) {
      12. // findState.checkAdd()之前已经分析过了,即是否在FindState的anyMethodByEventType已经添加过以当前eventType为key的键值对,没添加过返回true
      13. if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
      14. // 将subscriberMethod对象添加到subscriberMethods集合
      15. findState.subscriberMethods.add(subscriberMethod);
      16. }
      17. }
      18. } else {
      19. findUsingReflectionInSingleClass(findState);
      20. }
      21. findState.moveToSuperclass();
      22. }
      23. return getMethodsAndRelease(findState);
      24. }

      由于我们现在使用了 Subscriber Index 所以不会通过findUsingReflectionInSingleClass()来反射解析订阅事件的方法。我们重点来看getSubscriberInfo()都做了些什么:

      1. private SubscriberInfo getSubscriberInfo(FindState findState) {
      2. // 该条件不成立
      3. if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
      4. SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
      5. if (findState.clazz == superclassInfo.getSubscriberClass()) {
      6. return superclassInfo;
      7. }
      8. }
      9. // 该条件成立
      10. if (subscriberInfoIndexes != null) {
      11. // 遍历索引类实例集合
      12. for (SubscriberInfoIndex index : subscriberInfoIndexes) {
      13. // 根据注册类的 Class 类查找SubscriberInfo
      14. SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
      15. if (info != null) {
      16. return info;
      17. }
      18. }
      19. }
      20. return null;
      21. }

      subscriberInfoIndexes就是在前边addIndex()方法中创建的,保存了项目中的索引类实例,即MyEventBusIndex的实例,继续看索引类的getSubscriberInfo()方法,来到了MyEventBusIndex类中:

      1. @Override
      2. public SubscriberInfo getSubscriberInfo(Class subscriberClass) {
      3. SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
      4. if (info != null) {
      5. return info;
      6. } else {
      7. return null;
      8. }
      9. }

      即根据注册类的 Class 类型从 SUBSCRIBER_INDEX 查找对应的SubscriberInfo,如果我们在注册类中定义了订阅事件的方法,则 info不为空,进而上边findUsingInfo()方法中findState.subscriberInfo != null成立,到这里主要的内容就分析完了,其它的和之前的注册流程一样。

      所以 Subscriber Index 的核心就是项目编译时使用注解处理器生成保存事件订阅方法信息的索引类,然后项目运行时将索引类实例设置到 EventBus 中,这样当注册 EventBus 时,从索引类取出当前注册类对应的事件订阅方法信息,以完成最终的注册,避免了运行时反射处理的过程,所以在性能上会有质的提高。项目中可以根据实际的需求决定是否使用 Subscriber Index。

      八、小结

      结合上边的分析,我们可以总结出registerpostunregister的核心流程:

                                                                             register

                                                                                 post

                                                                 unregister

      到这里 EventBus 几个重要的流程就分析完了,整体的设计思路还是值得我们学习的。和 Android 自带的广播相比,使用简单、同时又兼顾了性能。但如果在项目中滥用的话,各种逻辑交叉在一起,也可能会给后期的维护带来困难。

    30. 相关阅读:
      Python+Django+vue的旅游信息网站系统项目源码介绍
      LVM 原理及动态调整空间使用
      GBase 8c向表中插入数据(二)
      非线性链表之树结构和堆的代码实现
      AutoSAR配置与实践(深入篇)5.2 OS原理(上)
      antd RangePicker 格式化 季度 YYYY- QQ 受控组件 / 非受控组件
      【HTML5高级第一篇】Web存储 - cookie、localStorage、sessionStorage
      聚观早报 | 网传恒大员工“停工留职”;腾讯WiFi管家停止服务
      百望云携手华为发布金融信创与数电乐企联合方案 创新金融合规变革
      01.Gameplay Architecture ECS简介
    31. 原文地址:https://blog.csdn.net/m0_49508485/article/details/127780285