0%

EventBus原理剖析

一、前言

EventBus是一个开源的事件总线框架,Android中被广泛使用,目前最新的版本为3.2.0,github地址:https://github.com/greenrobot/EventBus

二、简单使用

  1. 在build.gradle中引入依赖:

    implementation 'org.greenrobot:eventbus:3.2.0'
  2. 定义事件实体类:

    public class CallEvent {
    }
  3. 订阅消息:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
    
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onEvent(CallEvent event) {
        // TODO 
    }
  4. 发送消息:

    CallEvent event = new CallEvent();
    EventBus.getDefault().post(event);
  5. 来到这里就可以把EventBus用起来了,不过这里有个问题,注册时会反射遍历注册类的所有被Subscribe注解的方法,这个过程比较耗时会影响性能,因此EventBus又提供了一个eventbus-annotation-processor库来解决该问题,因此我们把它引入进来:

    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
  6. 这个eventbus-annotation-processor还需要一个key为eventBusIndex的键值对,用来指明自动生成的类的类名:

    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [ eventBusIndex : 'org.greenrobot.eventbusperf.MyEventBusIndex' ]
                }
            }
        }
  7. 然后编译代码,就可以在build/source/apt/debug/下看到自动生成了org.greenrobot.eventbusperf.MyEventBusIndex类,这时我们需要把这个类添加到EventBus中:

    EventBus.builder()
        .addIndex(new MyEventBusIndex())
        .installDefaultEventBus();
  8. 配置到此结束,可以愉快地使用了。

三、原理

EventBus的流程并不复杂,其本质就是采用观察者模式,使用者可以进行消息的订阅和发布,所以内部肯定就是维护了一个订阅者的集合,当发布者发布消息时,会找到对应的订阅者,然后调用其事件处理方法。

  1. 订阅消息

    调用register方法把订阅者对象传进去,EventBus会找到该对象所对应的类的所有被Subscribe注解的方法,也就是事件处理方法,然后把订阅者对象和这些方法的信息都保存起来,具体到代码中是保存到一个map里:

    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

    这里的key就是事件处理方法的第一个参数的Class类型。

    这里的value也就是订阅者对象列表,每一个元素,就是一个Subscription对象,该对象保存了订阅者对象和对应的事件方法信息,注意对于同一个订阅者对象,如果有N个事件处理方法,这里就会对应N个Subscription对象。在把Subscription对象添加到列表中,会根据事件处理方法的优先级字段进行添加,优先级高的放前面,低的放后面。

  2. 找到订阅者对象的事件处理方法

    在第1步中,订阅消息会找到订阅对象的事件处理方法,这里是怎么找的呢?第一种方法就是直接反射查找,找到所有方法后,再逐个判断是否被Subscribe注解,如果是,就说明这是一个事件处理方法,反射的同时,也收集注解的信息,比如回调线程,优先级等等,关键代码如下:

    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()));
        }
    }

    很明显这种方法比较耗时,特别是订阅一般都是在UI线程中进行的,执行过慢就可能会出现一系列问题,因此EventBus就把这个过程提前到编译时进行,在编译时就把这些信息收集好,运行时直接查找就行,为了实现该目的,需要引入org.greenrobot:eventbus-annotation-processor库,然后编译后就会自动生成MyEventBusIndex类,类名可以在脚本中指定,生成的类代码:

    /** This class is generated by EventBus, do not edit. */
    public class MyEventBusIndex implements SubscriberInfoIndex {
        private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
    
        static {
            SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
    
            putIndex(new SimpleSubscriberInfo(com.mco.testdemo.SecondActivity.class, true, new SubscriberMethodInfo[] {
                new SubscriberMethodInfo("onEvent", Object.class, ThreadMode.ASYNC),
            }));
    
        }
    
        private static void putIndex(SubscriberInfo info) {
            SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
        }
    
        @Override
        public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
            SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
            if (info != null) {
                return info;
            } else {
                return null;
            }
        }
    }

    可以看到,订阅者的Class和事件处理方法被封装成SimpleSubscriberInfo,然后放到map里,至于如何自动解析注解生成这个类,可以看EventBusAnnotationProcessor的源码,流程比较简单这里就不分析了。

    继续分析EventBus是怎样使用这个类的,看EventBus的getSubscriberInfo方法:

    private SubscriberInfo getSubscriberInfo(FindState findState) {
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

    这里的subscriberInfoIndexes就是保存我们调用addIndex方法传进来的MyEventBusIndex对象,因此这里会直接调用MyEventBusIndex的getSubscriberInfo,也就获取到了对应的SubscriberInfo对象。

  3. 发布消息

    发布消息就比较简单了,调用post方法,传入事件对象,看下EventBus的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);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

    注意currentPostingThreadState是一个ThreadLocal对象,所以不同线程同时发消息时不会互相干扰的,这里会把消息放到currentPostingThreadState的队列中,然后循环遍历队列发消息,发消息的流程:

    (1)根据消息的Class对象(包括父类和接口)拿到对应的订阅者列表。

    (2)调用postToSubscription真正地发消息,这里会做线程切换。

    (3)反射调用事件处理方法。

  4. 回调线程

    EventBus有个枚举ThreadMode,定义了各种回调线程的枚举值:

    (1)POSTING:表示在发送消息所在线程中回调。

    (2)MAIN:表示在主线程中回调。

    (3)MAIN_ORDERED:表示post一个任务到主线程消息队尾中回调。

    (4)BACKGROUND:表示在子线程中回调,如果post线程本身就是子线程,那就直接回调,默认用了DEFAULT_EXECUTOR_SERVICE线程池,它实际上就是无队列,无限大的缓存线程池,线程缓存时间60s。

    (5)ASYNC:表示在子线程中回调,但post本身是子线程也不会直接回调,默认也是用了DEFAULT_EXECUTOR_SERVICE线程池。

  5. 粘性事件

    可以调用postSticky发送粘性事件,事件对象会被保存到stickyEvents中,后面订阅者在订阅该消息时,如果事件处理方法需要处理粘性事件,就回调。

四、其它问题

  1. 通过上面的分析,EventBus并不支持多进程,消息的订阅和发布都只能在同个进程中进行。

  2. 混淆问题,在查找订阅者的事件处理方法时,如果用反射查找,就会报错:

    java.lang.NoSuchFieldError: No static field POSTING of type Lorg/a/a/r; in class Lorg/a/a/r; or its superclasses

    这里keep住ThreadMode就可以了。(这个其实没怎么想明白,反编译看是有POSTING的,继续研究……)

    如果用index查找,因为自动生成类中订阅者的方法名是混淆前的,而运行时该方法名已经被混淆成另外一个了,因此反射调用肯定是找不到的,而且没有任何地方调用的话这个方法会被proguard移除掉,所以这里要keep住事件处理方法。

  3. 事件死循环

    如果两个地方都有发消息和处理消息的逻辑,要避免消息来回发送消息导致死循环的问题。