再学Android:EventBus源码探究

712 阅读10分钟

前言

EventBus对于Android开发着来讲可以说是再熟悉不过了。不管是最开始的mvc模式还是现在的组件化开发模式,EventBus一直没有没落下去,今天我们就来详细分析一下EventBus的内部原理。

介绍

官方介绍中,EventBus是一个Android或者Java可以使用的基于发布、订阅的框架。这里放上官方的流程图:

通过流程图可以发现,EventBus的整体使用的是非常的简单,发布者post一个事件,所有消费者监听到之后消费。这就是一点典型的观察者模式的设计模式。

观察者模式:定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

上手使用

跟之前一样,我们还是先从使用开始,其实这步好像可以省略。大家对EventBus的使用应该非常熟悉,但是流程还是要走一下的。

onStart()方法中注册,onStop()中销毁。再来定义一个event,在onResume中发送,并且直接注册一个观察者来接受这个event。

    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onReceiveEvent(event: TestEvent) {
        Log.d("EventBus", "receive event")
    }

运行上面这段代码可以在控制台看到输出:

可以看到EventBus整个的使用流程是非常简单的,当然也可以在发送事件的时候携带参数,直接在event类中定义就ok。下面我们将详细分析一下EventBus的内部流程。

源码探究

还是老规矩,我们将按照使用流程来一点点剥开它的外壳,发现它的内部纹路。

获取EventBus

EventBus在构造方法执行的时候会通过builder模式来设置一些常用参数,先来看一下这些设置:

    EventBus(EventBusBuilder builder) {
        //log打印器
        logger = builder.getLogger();
        //Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType
        //以event为key,订阅列表为value,是一个线程安全的容器
        subscriptionsByEventType = new HashMap<>();
        //Map<Object, List<Class<?>>> typesBySubscriber;
        //订阅者为key,event为value的容器 
        typesBySubscriber = new HashMap<>();
        //粘性事件的容器
        stickyEvents = new ConcurrentHashMap<>();
        //是否支持主线程
        mainThreadSupport = builder.getMainThreadSupport();
        //如果支持主线程,构建出来一个主线程发射器
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        //初始化BackgroundPoster
        backgroundPoster = new BackgroundPoster(this);
        //初始化AsyncPoster
        asyncPoster = new AsyncPoster(this);
        //是否有编译期已经生成的索引类,关于索引将会在列表中放出解析文章链接
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        //初始化注册时间寻找器,具体将在后续分析
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        //是否打印出订阅异常
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        //当接收模式为backGroud和async时的线程调度器,默认为缓存线程池
        executorService = builder.executorService;
    }

当然如果我们在使用的时候也可以自定义配置。配置方式如下:

EventBus eventBus = EventBus.builder()
    .logNoSubscriberMessages(false)
    .sendNoSubscriberEvent(false)
    .build();

分析过构造方法之后再来看一下getDefault():

    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

可以看到这里是使用了单例模式的双重检查模式,关于单例模式的多种不同写法可以参考。只有第一次使用的时候才会初始化,最大限度的保证了资源的利用效率。

注册

要想通过EventBus来观察被观察者,首先是需要注册,来告诉程序你需要观察某个对象的改变。我们来看一下register是做了什么事情。

    将给定的对象注册用来接收event,当不在需要观察event的变化时,必须调用unregister。
    同时,注册的对象内部必须有Subscribe注解的方法。
    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

可以看到会通过subscriberMethodFinder当前对象注册的所有事件监听的方法,然后通过subscribe()方法来将注册的所有监听都添加到监听列表中。来分析下具体方法;

findSubscriberMethods

根据方法名就可以很方便的理解,根据传入的观察者对象去寻找其内部定义的全部观察方法。首先根据METHOD_CACHE.get(subscriberClass);去获取到之前可能已经缓存的全部订阅方法。如果之前已经获取过那么直接返回,前面有提到过EventBus是全局单例,所以就很好理解了。如果缓存没有就需要重新去获取。

接着会根据build期间设置的是否忽略索引来获取,前文也有提到EventBus在编译期会提前生成索引,那么索引是怎么生成的呢?

在3.0版本中,EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe注解,并解析和处理其中所包含的信息,然后生成java类来保存订阅者中所有的事件响应函数,这样就比在运行时使用反射来获得订阅者中所有事件响应函数的速度要快。
此处为引用

如果观察者内部并没有定义@Subscribe注解的方法会抛出异常。

通过findSubscriberMethods方法我们已经找到了观察者内部定义的全部标有@Subscribe的方法。接着就是在线程同步锁中调用subcribe方法。

subscribe

前文已经拿到了当前订阅的事件类型,这里会根据eventType放入上文提到的subscriptionsByEventType线程安全的map中,当然会有一些安全检查。接着会根据注册时定义的优先级对订阅同一个事件的观察者进行排序。

紧接着将当前观察者中所有的订阅方法添加进以subscriber为key的map中。

最后是对粘性事件的处理,与StickyBroadcast是同一个概念。EventBus在定义订阅方法时,除了可以指定接收的线程之外还设有另外两个参数可以设定:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    //接收的线程
    ThreadMode threadMode() default ThreadMode.POSTING;
    //是否是粘性事件
    boolean sticky() default false;
    //事件优先级
    int priority() default 0;
}

继续看是怎么处理粘性事件的注册,直接判断当前注册进来的粘性事件是否存储在本地的stickyEvents。如果匹配上那么就会最终调用postToSubscription()方法。

发送事件

EventBus的事件发送其实是非常简单的。我们来看一下:

  • 普通事件发送
  EventBus.getDefault().post(TestEvent())
  • 粘性事件发送
  EventBus.getDefault().postSticky(TestEvent())
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中取出发送线程的状态机。currentPostingThreadState其实是一个ThreadLocal,关于ThreadLocal推荐一篇文章:ThreadLocal就是这么简单
  • 拿到状态机中的事件队列
  • 将事件添加到事件队列中
  • 判断当前发送线程没有堆积任务正在处理才进入到发送逻辑
  • while循环中不断的从队列中取出第一个任务,调用postSingleEvent进行发送
postSingleEvent方法

上面说到,post方法实际上是将event添加到队列中,然后通过不断的从队列中取出第一个来进行处理。

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

对这个方法进行一个简单的总结:

  • 获取到发送的事件类型
  • 调用lookupAllEventTypes方法从缓存中或者通过立即获取方式取到所有注册了此种事件类型的观察者们,包括被继承的类也会被找出来
  • 调用postSingleEventForEventType来将事件发送

postSingleEventForEventType实际上从本地缓存中取到对象中后还是调用我们上文分析粘性事件时最终调用的postToSubscription方法。

postToSubscription 方法
    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 {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    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("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

可以说这个方法是观察者接收事件时绝对会调用的方法。可以看到根据threadMode是区分了不同的逻辑,那threadMode是干嘛的呢?

//定义用来表示EventBus的方法在哪个线程被调用的枚举类
public enum ThreadMode {
    //与post方是在同一个线程
    POSTING,
    //直接在主线程中调用这个方法
    MAIN,
    //同样也是在主线程中调用 ,但是与Main有区别的地方在于它是通过队列来进行的,不是直接调用
    MAIN_ORDERED,
    //Android上将会在异步线程调用subscribler的方法,如果本身post方不是主线程,那么会直接在当前线程执行,跟`post`类似。如果是在主线程post,那么会直接在一个新的异步线程执行。
    BACKGROUND,
    //这个模式下,总是在一个单独的线程执行,不会在post线程以及主线程。可以用来执行一些耗时任务。内部是采用线程池来做线程的复用
    ASYNC
}

分析到这里也就差不多可以理解postToSubscription是按照方法注解中定义的ThreadMode来进行观察者接收事件的线程调度管理。不轮其是直接调用方法还是放入队列中进行管理最终都是通过反射在当前线程对方法进行调用。

postSticky方法
  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);
    }

可以看到postSticky内部逻辑也是非常简单,就是将event注册为粘性事件存放入本地缓存。后续逻辑与正常事件的发送一样。

取消注册

分析完了注册和发送,我们必须要来讲一下反注册。我们平常在activity中使用广播的时候有注册,那么在activity销毁的时候一定要记得进行反注册,不然就会引起内存泄漏。同样的道理,假如我们某个观察者类在销毁的时候没有从eventBus中进行取消注册。eventBus全局单例就一直会持有当前观察者的引用,从而造成了内存泄漏。

    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 {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

可以看到,取消注册的逻辑其实是非常简单的,无非是将要取消注册的观察者对象从本地缓存中和相关的map中都统一移除。

总结

写到这里,基本上EventBus整个使用流程的代码分析都已完成,我们来大概总结一下:

  • EventBus核心是使用观察者模式
  • 通过builder模式设置参数,一般情况下使用默认
  • EventBus是使用双重检查模式的全局单例
  • 注册时通过索引或者反射来获取订阅者中的所有订阅方法,粘性事件注册时会根据本地粘性事件map是否存在当前注册进来需要监听的订阅类型来决定要不要发送一次。
  • 发送时根据事件类型等从本地在注册时就已经换成的对象进行发送,发送有接收线程、优先级以及是否粘性事件的区分。最终都是通过反射调用方法来进行通知的
  • 取消注册,将订阅者对象从EventBus类的相关缓存中移除

以上就是EventBus整体的大概流程分析,大概还是会有很多语意不明或者逻辑漏洞的地方,希望大家指正。

参考资料

EventBus 3.0进阶:源码及其设计模式 完全解析
EventBus配置、粘性事件、优先级和取消事件分发
EventBus3.0新特性之Subscriber Index
ThreadLocal就是这么简单