EventBus源码解析

前言

EventBus是安卓(Java中也可以用)开发中非常流行的一个第三方库,是一种发布/订阅事件的总线.拥有以下特性:

  • 简化了组件间的通信,可应用与Activity,Fragment和后台线程
  • 将事件的发送方和接收方解耦
  • 体积小

现在的EventBus早已经从2.0进入3.0版本,而且最新版3.1.1也已经稳定了挺长的时间,从17年底再没有更新过GitHub.因为之前的项目中集成的都是3.0.0版本的,因此以该版本源码做分析.常见的使用方法就不介绍了,官方文档里都有.所有操作,包括发送事件、在订阅事件的组件中注册
和解绑,都是通过EventBus.getDefault()创建一个单例对象,完成后续方法的调用.

register(注册事件)

先看注册事件的部分,因为EventBus通常的使用顺序就是接收方注册->发送方定义发送事件->接收方定义接受入口->发送方发送事件->收到事件。当然从数据流向看先从发送事件开始也是可以理解的:

源码:

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是SubscriberMethodFinder类型对象,在EventBus的默认构造时初始化。通过findSubscriberMethods方法可以找到该类中添加了Subscribe注解的方法。只有添加了该注解的方法(方法名可以任意)才可以收到事件。如果该类没有添加过注解,抛异常。

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("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

METHOD_CACHE是Map, List>类型,它存储的value是该类中所有添加了Subscribe注解的方法(被包装为SubscriberMethod类)的集合。该类第一次注册事件的时候集合为null,默认ignoreGeneratedIndex为false,会走到方法findUsingInfo.而在该方法内部,默认的情况下经过一系列逻辑判断又会走到findUsingReflectionInSingleClass

findUsingReflectionInSingleClass

精简核心代码如下:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        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()));
                    }
                }
            }
    }
}

前面的操作已经将该类封装到了静态内部类FindState中,作为clazz字段。这里重新加工要使用的FindState的步骤是:

  • 通过getDeclaredMethods获取该类本身定义的所有类型的方法.因为订阅类通常是Activity这样的组件,层层继承,这样效率更高。
  • 遍历方法数组,获取方法的修饰符并进行判断。这也是为什么将接受事件的方法用private、static修饰会抛异常的原因
  • 符合要求,获取该方法的参数个数,必须为1。
  • 参数个数为1,则判断方法内是否定义了Subscribe注解。若有,则获取参数的类,也就是发送事件方那边所定义的事件类型。
  • 通过FindState的checkAdd方法判断,这里有两层判断。校验通过,会包装一个SubscriberMethod对象添加到FindState的subscriberMethods集合中。
  • 第一层,只是快速简单的判断事件类型。FindState在初始化的时候创建了对象anyMethodByEventType,类型为Map。订阅事件的类如果只定义了一个符合要求的接收事件的方法,那么以方法接收的事件类为key存储该方法。
  • 第二层校验,通常一个订阅者中不会有多个方法接收同一事件,但如果定义了多个呢?这时anyMethodByEventType的value正是前一个定义的接收事件的方法,会调用checkAddWithMethodSignature方法校验。源码如下:
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());

    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

methodKeyBuilder是一个StringBuilder对象。可以看出,每次调用该方法都将重置它,并且以方法名+'>'+事件类型作为键值。subscriberClassByMethodKey是一个Map对象,存储的value为接受事件的方法所在类。只有该Map存在重复键时该方法才会返回false,但是我们定义的键值,因为同一个类中不会出现同名同参数类型同参数个数的方法,所以不会重复,返回true。

额外话:该方法什么情况会返回false?假设有一个类ActivityA,其中定义了订阅和接受事件的方法,另外有一个类ActivityB继承了A作为页面展示,并重写了其中的订阅方法。这时候在findUsingInfo方法中的while循环体内,会继续向B的父类A调用。这时的subscriberClassByMethodKey相同的键对应的value就不为空了,并且由于A、B之间 继承关系,方法会走入else分支返回false。最终抛出IllegalStateException。因此要注意派生关系在订阅事件的应用

继续向下,出现了接受同一事件的多个方法,这时anyMethodByEventType的value用任意的对象代替以“消费”该方法,源码中直接取的FindState的当前对象。接着又一次调用了checkAddWithMethodSignature方法。前面说过了因为键不会重复的原因,因此最终返回true。该接受事件方法校验通过。也就是说,在同一个类中定义多个接受同一事件的方法,那么这些方法都会执行。

准备好事件集合

上面的方法调用完毕后就获取到了订阅事件方法的集合,并且将其缓存到METHOD_CACHE中,这样同一个类多次注册事件时就不必做重复的大量逻辑判断。这时候就可以遍历该集合,正式开始事件的订阅了。

subscribe(订阅事件)

精简代码:

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    }

    int size = subscriptions.size();
    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);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

	//后面分析stick事件
}

将订阅事件的类和订阅方法包装成一个Subscription对象。CopyOnWriteArrayList相当于一个线程安全的ArrayList,其实就是内部实现的方法上加了锁。subscriptionsByEventType是类型为Map, value为CopyOnWriteArrayList>的对象,会以事件类型为键存储所有订阅该事件的方法集合,根据@SubScribe的priority,优先级高的会添加到头部,保证了多个订阅方法的执行顺序。typesBySubscriber是类型为Map>>的对象,用于存储该订阅类中订阅的所有事件类型

post(发送事件)

接收方注册了事件,订阅了事件,就来看发送方发布事件的代码.最常用的post方法,其中的参数event就是我们在要发送事件的地方自己定义的一个对象,通常是一个静态内部类:

public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

    if (!postingState.isPosting) {
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        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;
        }
    }
}

首先在EventBus第一次被初始化的时候创建了一个ThreadLocal类的对象currentPostingThreadState,其泛型形参为EventBus中的静态内部类PostingThreadState.在post的时候会获取该内部类,从而获取到其内部的eventQueue(事件队列,用ArrayList存储),并且将要发送的事件(抽象为发送方自己定义的对象)加入到该队列中.isPosting可以避免并发情况下一个Event的多次被调用.判断事件不处于发送中的状态后,开始依次从队列中的头部取出事件发送,直到队列为空.可以看出,发送事件的同时会将事件移除出队列.发送单个事件的方法为postSingleEvent。

postSingleEvent

忽略一些逻辑判断,核心代码是:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    postSingleEventForEventType(event, postingState, eventClass);
}

看到这个方postSingleEventForEventType,该方法内部的逻辑是:如果对应事件类型的订阅方法存在,那么会为每个订阅方法调用postToSubscription。它会比较发送事件和订阅事件各自所在的线程来决定是否立即执行。符合条件的话,有订阅类,有事件类型,最终会反射调用Method的invoke方法,也就是我们接受到了事件开始处理了。这样整个发送->事件流也就结束了。

ThreadMode

EventBus定义的一个枚举。我们通过订阅方法上添加的Subscribe注解就可以设置这个值。它代表了订阅方法在哪类线程被EventBus调用。设计之初就是让发送和接收事件的线程独立开来。共定义了四种线程:

POSTING

默认。订阅事件和发送事件处在同一线程。最常用的是这种,它可以避免切换线程的损耗。但由于发送事件可能在主线程中,因此接收事件的方法不能做耗时操作,否则会阻塞线程。

MAIN

接收事件总会在主线程(UI线程)中,也要避免耗时操作。如果发送事件在子线程,则接收事件会放到MessageQueue中等待执行

BACKGROUND

接收事件总会在子线程中。如果发送事件也在子线程中,那么接收事件应立即在发送事件所在的线程中执行。如果发送在主线程中,接收事件运行在EventBus维护的单一子线程中,因为还可能存在其他接收事件的方法,所以也要避免阻塞。

ASYNC

接收事件在一个独立的线程中执行,独立于发送方所在线程和主线程。适合做耗时操作,如网络请求。EventBus内部使用了线程池来复用这些异步的接收事件方法所在线程,但还是要避免同一时间内大量触发此类型方法。

Poster

前面提到,如果接收和发送所处的线程不一样,会将事件先放到队列中等待执行。有这么三种情况:

发送方子,接收方主

加入到HandlerPoster,这个类继承了Handler,其实就是在enque方法中调用了sendMessage,又在handleMessage中调用了EventBus的invokeSubscriber方法,完成了接受事件方法的调用。

发送方主,接收方子

加入到BackgroundPoster,这个类实现类Runnable,在enque方法中找到了EventBus全局定义的一个默认的线程池:Executors.newCachedThreadPool(),获取了一个无数量上限,无核心线程,可以复用空闲线程的线程池。enque方法加锁,而且全局定义了一个volatile类型变量,保证了任一时间只有一个任务被执行,也就是前面说的单一子线程。在run方法中调用了invokeSubscriber。

接收方独立

加入到AsyncPoster,它也实现Runnable。但不同于BackgroundPoster,它没有加锁和保证有序性,因此等待被执行的任务只要放入线程池就行了。

postSticky

假设有这种场景,我在登录页面输入用户名和密码后跳转到其他的一些界面,相应的页面都要有所变化。那这时候发送事件的时候因为新打开的页面还没有初始化,还没有完成订阅事件,subscriptionsByEventType里不存在value,也就无法接收到事件。这时候就会用到postSticky.

/**
 * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
 * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
 */
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);
}

和普通的发送事件相比,就是同步将要发送的事件类型和事件对象存储到了stickyEvents中。然后到了订阅事件时,会走到subsribe方法的该分支,默认精简如下:

if (subscriberMethod.sticky) {
    
    // Existing sticky events of all subclasses of eventType have to be considered.
    // Note: Iterating over all events may be inefficient with lots of sticky events,
    // thus data structure should be changed to allow a more efficient lookup
    // (e.g. an additional map storing sub classes of super classes: Class -> List).
    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);
        }
    }
}

取出之前的stickyEvents,遍历其键值对。校验发送的和接受的是同一事件后,判断如果存储的事件对象不为空,则调用postToSubscription方法。这样也就保证了即使发送事件在先,注册事件在后,也可以接收到事件。注意的是接受事件的方法的Subscribe注解中的sticky要置为true.

unregister

/** Unregisters the given subscriber from all event classes. */
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 {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

取消订阅的方法中,首先移除了所有订阅了该事件的方法集合中该类中所定义的方法。然后移除了该类中所有的订阅事件类型。

总结

EventBus作为部分替代BroadcastReceiver和接口回调的一款知名框架,本身是基于观察者模式实现的,实现原理总结了有下面几个核心要点:

  • 单例模式
  • 建造者模式
  • 包装类
  • 键值对存储
  • 使用锁同步方法
  • 基于注解使用
  • 通过反射找匹配,大量的反射方法:getClass、getSuperclass、getDeclaredMethods、getParameterTypes、getAnnotation、isAssignableFrom、invoke

你可能感兴趣的:(Android进阶)