事件总线框架EventBus的使用与原理解析

 事件总线模式基于发布-订阅机制实现,它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,从而达到一种解耦的目的。事件总线模式主要由三个部分构成,即事件事件总线事件倾听器,其中,事件为事件源产生,在事件总线上传播,传播的方式是采用广播的方式;事件总线是实现事件传播的"基础设施",可理解为事件总线模式的“调度中心”,所有在系统中阐述的事件都将在事件总线上进行广播,左右挂在在事件总线上的事件侦听器都将接收到在事件总线上发布的事件;事件侦听器是用于侦听各种事件的对象,当事件侦听器接收到事件总线上发布的事件后,先判断事件的类型,如果是自身感兴趣的事件,事件侦听器将进行相应的处理,修改自身的状态。事件总线模式的逻辑图如下:
在这里插入图片描述

1. EventBus框架

1.1 EventBus简介

EventBus是GreenDao开源的一个用于Android的事件发布-订阅总线框架,它旨在简化Android应用中Activity、Fragment、Threads、Services等各个组件之间进行通信的复杂度,避免使用广播、接口等通信方式所带来的的诸多不便,同时从最大成程度上降低组件之间的耦合度。在EventBus框架中,主要包含Event(事件)Subsciber(事件订阅者)Publisher(事件发布者)三个角色,其中Event表示的是任意类型的事件,EventBus会根据事件类型进行全局广播通知;Subscriber表示事件订阅者,能够接收EventBus广播出来的事件,并对其感兴趣的事件进行处理。事件处理的方法名可以任意指定,但需要在方法添加@subcribe注解和指定线程模式;Publisher为事件发布者,可以再任意线程里发布事件。EventBus框架工作原理:
在这里插入图片描述

1.2 EventBus基本使用

1.2.1 添加Gradle依赖

implementation 'org.greenrobot:eventbus:3.1.1'

1.2.2 定义事件

 EventBus的事件可以为任意类型,比如String、int、类等等,这里我们定义一个类EncodeEvent为例,该类用于描述编码器相关编码信息,它包含帧率、码率两个成员属性。该类表示一个事件,编码器每编码一帧视频,就生成这么一个事件。EncodeEvent类定义如下:

public static class EncodeEvent {
	private String fps;
    private String bitrate;
    
    public StreamEvent() {}
    
    public StreamEvent(String fps,  String bitrate) {
        this.fps = fps;
        this.bitrate = bitrate;
    }
    
    public void setFps(String fps) {
        this.fps = fps;
    }
    
    public String getFps() {
        return fps;
    }
    
    public void setBitrate(String bitrate) {
        this.bitrate = bitrate;
    }
    
    public String getBitrate() {
        return bitrate;
    }
}

1.2.3 准备订阅者

  • 首先,注册/注销订阅者。

     通过调用EventBus的register/unregister方法实现,假如我们需要在Activity、Fragment、Service等组件中注册订阅者,那么订阅者的生命周期尽量与这些组件生命周期保持同步。如果订阅者注册在子线程等其他地方,就需要看情况注销订阅者,以免出现内存泄漏。

 @Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }
  • 其次,定义一个订阅者方法(事件处理方法)并为其添加Subscribe注解。

     订阅者方法将用于接收自己感兴趣的事件,所谓感兴趣的事件,就是方法中传入的参数类型。需要注意的是,在添加Subscribe注解时,需要传入名为threadMode参数,该参数表示的线程模式,默认值为POSTING,线程模式主要用于描述事件处理方法所在线程的特点。

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onEncodeEvent(EncodeEvent event) {
	// do something
}

注解Subscribe支持三个参数,即threadMode、sticky和priority,其中threadMode用于指定事件订阅方法的线程模式,即在那个线程执行事件订阅方法处理事件,默认为POSTING;sticky用于指定是否支持黏性事件,所谓黏性事件就是事件发布后,再注册订阅者方法仍然能够接收到事件,默认为false;priority用于指定事件订阅方法优先级,如果多个事件订阅方法可以接收相同事件的,则优先级高的先接收到事件,默认为0。注解Subscribe源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // 指定事件订阅方法的线程模式,即在那个线程执行事件订阅方法处理事件,默认为POSTING
    // EventBus五种线程模式为:
    // 1. ThreadMode.POSTING:表示事件处理方法所在的线程与发布事件的线程在同一个线程;
	// 2. ThreadMode.MAIN:表示事件处理方法所在的线程为主线程(UI线程),因此不能进行耗时操作;
	// 3. ThreadMode.BACKGOUND:表示事件处理方法的线程不是主线程(或称后台线程),因此不能进行
    //    UI操作。如果发布事件的线程是主线程,那么事件处理方法将会开启一个后台线程;如果发布事件的
    //    线程是在后台,那么事件处理函数就使用该线程;
	// 4. ThreadMode.ASYNC:表示事件处理方法始终会创建一个新的子进程运行,无论事件发布的线程是
    //    哪一个。因此在该模式下,事件处理方法中不能进行UI操作。
    // 5. ThreadMode.MAIN_ORDERED
    ThreadMode threadMode() default ThreadMode.POSTING;
    // 是否支持粘性事件,默认为false
    boolean sticky() default false;
    // 指定事件订阅方法的优先级,默认为0
    int priority() default 0;
}

1.2.4 发布事件

 EventBus支持在任意线程、任意位置中发布事件,通过调用EventBus的post方法即可。

EventBus.getDefault().post(new EncodeEvent("25fps", "5Mbps"));

2. EventBus原理解析

 从1.2小节中可知,当我们要使用EventBus时,首先需要调用EventBus.getDefault()方法来获取EventBus实例(或称事件总线)。从EventBus.getDefault()方法源码可知,它采用了双重检查(DCL)形式的单例模式来创建EventBus实例。在调用EventBus的构造方法中,又调用了另外一个重载构造方法,并且传入了一个值为DEFAULT_BUILDER的参数,通过查看源码可知,这个DEFAULT_BUILDER就是EventBusBuilder的实例,该实例主要用于对EventBus进行配置,这里采用了建造者模式。EventBus.getDefault()方法等源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
// 单例模式
// 实例化EventBus
public static EventBus getDefault() {
    EventBus instance = defaultInstance;
    if (instance == null) {
        synchronized (EventBus.class) {
            instance = EventBus.defaultInstance;
            if (instance == null) {
                instance = EventBus.defaultInstance = new EventBus();
            }
        }
    }
    return instance;
}
// EventBusBuilder用于配置EventBus
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
// 构造方法
// 多条EventBus,订阅者可以注册到不同的EventBus
public EventBus() {
    this(DEFAULT_BUILDER);
}
// 构造方法
EventBus(EventBusBuilder builder) {
    logger = builder.getLogger();
    // Map<Class<?>, CopyOnWriteArrayList<Subscription>>
    // 事件类型(key)-订阅者列表(value)集合
    subscriptionsByEventType = new HashMap<>();
    // Map<Object, List<Class<?>>>
    // 订阅者(key)-事件列表(value)集合
    typesBySubscriber = new HashMap<>();
    // Map<Class<?>, Object>
    // 黏性事件集合
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadSupport = builder.getMainThreadSupport();
    // 用于在主线程发布事件
    mainThreadPoster = mainThreadSupport != null ? 
        mainThreadSupport.createPoster(this) : null;
    // 用于在后台线程发布事件
    backgroundPoster = new BackgroundPoster(this);
    // 用于在后台线程发布事件
    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;
     // 调用事件处理方法出现异常
    // 是否需要发送SubscriberExceptionEvent事件
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    // 当没有事件处理方法时
    // 是否需要发送NoSubscriberEvent事件
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    // 调用事件处理方法出现异常
    // 是否需要抛出SubscriberException异常
    throwSubscriberException = builder.throwSubscriberException;
    // 有继承关系的订阅者是否都需要发送
    eventInheritance = builder.eventInheritance;
    // 线程池
    executorService = builder.executorService;
}

 需要注意的是,与常见的单例模式不同的是,EventBus的构造方法作用域为public,这就意味着我们的应用可以创建不同的事件总线,然后订阅者可以注册到不同的事件总线上。由于不同的事件总线是隔离开的,因此订阅者只会收到它注册的事件总线发送的数据(事件)。接下来,我们分析该框架中订阅者注册、注销和事件发布过程。

2.1 订阅者注册过程

 假如我们需要监听事件总线上感兴趣的事件,就需要在事件总线上进行注册,这样当事件总线发送数据后才会被通知到,这个注册过程被称为“订阅者注册”,通过调用EventBus的register()方法实现。该方法源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
public void register(Object subscriber) {
    // 1. 获取订阅者Class
    Class<?> subscriberClass = subscriber.getClass();
    // 2. 获取订阅者的所有订阅方法
    //    SubscriberMethod用于描述一个订阅方法
    List<SubscriberMethod> subscriberMethods = 
        				subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    // 3. 注册所有订阅方法
    // 线程同步调用
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

 在register()方法中,它首先会去获取订阅者类的Class对象(注释1),然后调用SubscriberMethodFinder的findSubscriberMethods方法获取订阅者所有的事件处理方法(或称订阅方法,注释2),最后遍历注册所有订阅方法,并调用EventBus的subscribe方法完成接下来的注册逻辑。(注释3)

  • 查找订阅者的所有订阅方法

 对于订阅者所有订阅方法的查找,是通过调用SubscriberMethodFinder的findSubscriberMethods方法实现的,该方法最终会返回一个订阅方法集合,而列表中元素的类型为SubscriberMethod,该类用于描述一个订阅方法,用于保存订阅方法的Method对象线程模式(ThreadMode)感兴趣的事件类型(eventType)优先级(priority)以及黏性事件标志(sticky)等属性。findSubscriberMethods方法源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\SubscriberMethodFinder.java
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // 1. 从缓存中查找订阅者的订阅方法
    //   subscriberClass表示订阅者Class
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
	// 2. 是否忽略注解器生成的MyEventBusIndex
    //   默认ignoreGeneratedIndex为false,即忽略
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    // 3. 如果没有找到订阅者的订阅方法,抛出异常
    //    如果找到了,将其保存到缓存中,并返回订阅方法列表
    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;
    }
}

 在findSubscriberMethods方法中,主要做以下三件事情:

(1) 从缓存METHOD_CACHE中查找订阅者的订阅方法,如果找到则直接取出返回。其中,METHOD_CACHE是一个Map集合,它缓存了所有订阅者及其订阅方法列表,且订阅者Class为key,订阅方法列表为value。

Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE 
                                       = new ConcurrentHashMap<>();

(2)如果缓存中没有找到,则根据ignoreGeneratedIndex属性的值来选择采用哪种方法来查找订阅方法的集合。ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex,至于如何生成MyEventBusIndex类以及它的使用后续再讨论,总之ignoreGeneratedIndex默认为false,当然也可以通过EventBusBuilder配置MyEventBusIndex的值。因此,接下来将调用SubscriberMethodFinder的findUsingInfo完成订阅方法集合的查找。该方法源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\SubscriberMethodFinder.java
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    // 注释1:创建FindState类,将订阅者Class保存到该类
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    // 初始状态下findState.clazz=subscriberClass
    while (findState.clazz != null) {
        // 注释2:获取订阅者信息
        // 条件不成立
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, 
                                       subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 注释3:通过反射查找订阅事件的方法
            findUsingReflectionInSingleClass(findState);
        }
        // 修改findState.clazz为subscriberClass的父类Class
        // 即需要遍历父类
        findState.moveToSuperclass();
    }
    // 注释4:返回订阅者方法列表,释放掉FindState
    return getMethodsAndRelease(findState);
}

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    // 创建订阅方法列表
    // 并使用findStatefindState.subscriberMethods的值进行赋值
    List<SubscriberMethod> subscriberMethods = new ArrayList<>
        (findStatefindState.subscriberMethods);
    // 释放FindState
    findState.recycle();
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    return subscriberMethods;
}

 在findUsingInfo方法中,首先会创建一个FindState类,并将订阅者的Class对象保存到该类。该类是SubscriberMethodFinder的内部类,用来辅助查找订阅事件的方法(注释1);然后调用getSubscriberInfo来获取订阅者信息,由于没有通过EventBusBuilder配置MyEventBusIndex,因此这里获取的subscriberInfo为null(注释2),因此就会去调用findUsingReflectionInSingleClass方法通过反射的方式查找所有的订阅方法,并将其保存在FindState对象的subscriberMethods集合中(注释3);最后,再调用getMethodsAndRelease方法创建一个subscriberMethods,并将findState.subscriberMethods保存的订阅方法添加到这个列表中,同时释放FindState对象(注释4)。接下来,我们查看下findUsingReflectionInSingleClass方法是如何实现订阅方法查找。

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\SubscriberMethodFinder.java
private void findUsingReflectionInSingleClass(FindState findState) {
     // 注释1:反射获取类的所有方法Method对象
    Method[] methods;
    try {
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    // 注释2:遍历所有订阅方法,筛选符合条件的方法
    // 添加到findState.subscriberMethods集合中
    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) {
                // 获取订阅方法上的subscibe注解subscribeAnnotation
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    // 获取事件类型,即给订阅方法传入的参数(第一个)
                    Class<?> eventType = parameterTypes[0];
                    // 判断FindState的anyMethodByEventType map是否
                    // 已经添加过以当前eventType为key的键值对,没添加过则返回true
                    if (findState.checkAdd(method, eventType)) {
                        // 获取订阅方法subscibe注解的threadMode参数值
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // 将订阅方法添加到findState.subscriberMethods集合中
                        findState.subscriberMethods.add(new SubscriberMethod(method, 
                               eventType, threadMode,  subscribeAnnotation.priority(), 
								subscribeAnnotation.sticky()));
                    }
                }
            }
        }
        ...
    }
}

 从上述源码中可知,findUsingReflectionInSingleClass方法首先是通过反射的方式获取订阅者Class的所有方法的Method对象(注释1),然后遍历这个Method对象列表刷选出符合条件的方法,即为订阅方法,再将其保存到FindState的subscriberMethods属性值中,这个属性类型为List<SubscriberMethod>注释2)。判断是否符合条件的方法:首先通过反射获取当前方法的参数列表,筛选出参数数量为1个的方法;然后获取方法是否被subscibe注解,如果是就说明该方法为订阅方法,并取出订阅方法的事件类型eventType(第一个参数);最后判断FindState的anyMethodByEventType集合中是否添加过以当前事件类型为key的键值对,如果没有添加就将该订阅方法相关信息保存到findState.subscriberMethods集合中(注释2)。subscibe注解和SubscriberMethod类定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.METHOD})
public @interface Subscribe {
    // 线程模式值
    ThreadMode threadMode() default ThreadMode.POSTING;
    // 黏性事件标志值
    boolean sticky() default false;
    // 优先级值
    int priority() default 0;
}

// SubscriberMethod类用于描述一个订阅方法
public class SubscriberMethod {
    final Method method;         // Method对象,表示方法
    final ThreadMode threadMode; // 线程模式
    final Class<?> eventType;    // 事件类型
    final int priority;			 // 优先级
    final boolean sticky;        // 是否为黏性事件
    /** Used for efficient comparison */
    String methodString;
 	...   
}
  • 注册订阅方法

 当完成对订阅者的所有订阅方法查找后,接下来就是将这些订阅方法进行注册,通过调用EventBus的subsrcibe方法实现,前面我们在分析EventBus的register方法时就有说到。接下来,我们就知道看EventBus的subsrcibe方法是如何完成对一个订阅方法注册的。EventBus$subsrcibe方法源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // 1. 获取当前订阅方法的事件类型
    Class<?> eventType = subscriberMethod.eventType;
    // 2. 创建一个Subscription对象
    // 它描述了当前订阅者与当前订阅方法的关系
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    // 3. 根据事件类型,取出存储在subscriptionsByEventType集合中的Subscription列表
    // 如果列表不存在,则创建该列表并将其添加到subscriptionsByEventType集合中
    // 如果列表存在,判断订阅者是否已经被注册过并抛出EventBusException异常
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        // key:事件类型  value:订阅事件的订阅者列表
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " 
                                        already registered to event "
                                        + eventType);
        }
    }
    // 将newSubscription添加到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;
        }
    }
    // 4. 根据订阅者,取出存储在typesBySubscriber集合中的事件类型列表
    // 如果没找到,就创建一个事件类型列表subscribedEvents
    // 并将事件类型添加到这个列表中
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        // key:订阅者  value:事件类型列表
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);
    // 5. 订阅方法的黏性事件标志是否为true
    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            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);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

 从subsrcibe方法源码可知,注册一个订阅方法实际上就是将当前订阅者以及当前订阅方法的相关信息存储到subscriptionsByEventTypetypesBySubscriber两个map集合中,其中,subscriptionsByEventType集合存储的是事件类型与订阅该事件类型的订阅者(包含订阅方法,用Subscription类描述)列表之间的映射关系,而typesBySubscriber集合存储是订阅者(key)与其"感兴趣(订阅)"的事件类型列表(value)之间的映射关系。

// key:事件类型  value:订阅事件的订阅者列表
// 即一个事件类型可以被多个订阅者订阅   (CopyOnWriteArrayList:线程安全列表)
Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
// key:订阅者  value:事件类型列表
// 即一个订阅者可以订阅多个事件类型
Map<Object, List<Class<?>>> typesBySubscriber;

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\Subscription.java
// 用于描述一个订阅者及其订阅方法
final class Subscription {
    final Object subscriber;  // 订阅者
    final SubscriberMethod subscriberMethod; // 订阅者方法
    volatile boolean active;

    Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
        this.subscriber = subscriber;
        this.subscriberMethod = subscriberMethod;
        active = true;
    }
    ...
}

2.2 订阅者注销过程

 在1.2小节EventBus的使用中,我们了解到订阅者的注册是通过调用EventBus的unregister方法实现的,该方法通过三步完成订阅者的注销:首先从typesBySubscriber集合中获取当前订阅者所订阅的事件类型列表subscribedTypes(注释1);然后遍历事件类型列表,调用EventBus的unsubscribeByEventType方法删除订阅当前事件类型的当前订阅者信息(注释2);最后从typesBySubscriber集合删除当前订阅者的记录,包含它所订阅的感兴趣的事件类型。EventBus$unregister方法源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
public synchronized void unregister(Object subscriber) {
    // 1. 获取当前订阅者所订阅的所有事件类型
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        // 2. 遍历所有订阅的事件类型
        // 从当前事件类型的订阅者列表中移除当前订阅者信息
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        // 3. 将当前订阅者从typesBySubscriber集合中移除
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister 
                   was not registered before: " + subscriber.getClass());
    }
}

 我们看下注释2中,unsubscribeByEventType方法:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    // 注释1:获取订阅当前事件类型的所有订阅者
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        // 注释2:遍历订阅者列表,判断是否包含当前订阅者
        // 如果包含则从subscriptions中移除
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            // 匹配
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

 从上述源码可知,该方法的作用就是从订阅了当前事件类型eventType的所有订阅者中删除将要被注销的订阅者信息,即首先从subscriptionsByEventType集合中获取当前事件类型eventType的订阅者列表subscriptions(注释1);然后遍历订阅者列表,匹配出当前订阅者,再从当前事件类型的订阅者列表subscriptions中移除(注释2)。

2.3 事件发布过程

 在1.2小节EventBus的使用中,我们了解到将一个事件发布到事件总线(Event Bus)是通过调用EventBus的post方法实现的,该方法的源码如下所示:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
// 发布事件到事件总线Event Bus
public void post(Object event) {
    // 1. 获取当前线程的PostingThreadState对象
    // 该对象保存了当前线程的事件发送状态
    PostingThreadState postingState = currentPostingThreadState.get();
    // 2. 获取事件列表,将要发送的事件event保存到列表中
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);
    // 3. 事件没有正在被发布
    //   (1) 更改PostingThreadState对象相关属性的状态
    //   (2) 然后调用postSingleEvent执行事件发送流程
    // 	 (3) 发布完成后,重置PostingThreadState对象相关属性的状态
    if (!postingState.isPosting) {
        // (1)
        postingState.isMainThread = isMainThread();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            // (2)
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            // (3)
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

 在EventBus$post方法中,该方法主要完成两个步骤:

(1)获取当前线程的PostingThreadState对象。从ThreadLocal属性中获取一个PostingThreadState对象,该对象记录了当前线程的事件发布状态,包括事件列表、发布状态、主线程标志等等,然后将当前事件event插入到PostingThreadState的事件列表中。另外,ThreadLocal是一个线程内部的存储类,它可以在指定线程内存储数据,数据存储以后只有指定线程内部可以得到存储数据,即线程隔离。也就是说,每个线程中的PostingThreadState对象都是独立的,并且只能在线程内部获取。相关源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
// ThreadLocal保存当前线程的数据PostingThreadState
// 线程隔离
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new 
    ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {       	
        return new PostingThreadState();
    }
};

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus$PostingThreadState
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<>(); // 事件列表
    boolean isPosting; // 是否发布
    boolean isMainThread; // 是否为主线程
    Subscription subscription; // 订阅者信息
    Object event; // 事件
    boolean canceled;
}

(2)判断当前事件是否处于正在发布中,如果没有则修改PostingThreadState对象的属性值,比如当前线程是否为主线程、将事件发布状态设置为true;然后遍历事件列表中的事件,将所有的事件发布出去,这里调用的是EventBus的postSingleEvent方法,该方法源码如下:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    // 1. 获取事件的Class对象
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    // 2. 有继承关系的订阅者是否需要发送事件
    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 {
        // 3. 不考虑订阅者的继承关系
        // 发布事件,发布成功subscriptionFound=true
        // 参数:本次事件、当前线程事件发布状态、事件的Class对象
        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));
        }
    }
}

 postSingleEvent方法的实现比较简单,就是根据eventInheritance标志决定是否需要向有继承关系的订阅者发送本次事件,这里我们暂时不考虑这种情况,因此接下来会执行postSingleEventForEventType方法执行接下来的事件发布流程。EventBus$postSingleEventForEventType方法源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    // 1. 根据事件类型获取它的订阅者列表
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    // 2. 遍历订阅者列表,设置PostingThreadState相关属性值
    // 调用postToSubscription
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event; // 本次事件
            postingState.subscription = subscription; // 当前订阅者
            boolean aborted = false;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

 在postSingleEventForEventType方法中,首先会去获取订阅了本次事件的订阅者列表(注释1);然后遍历订阅者列表,并将事件发送到订阅了该事件的所有订阅者,其中,发布一个事件通过调用postToSubscription方法实现。EventBus$postToSubscription方法源码如下:

// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
private void postToSubscription(Subscription subscription, Object event, 
                                boolean isMainThread) {
    // 根据当前订阅方法的线程模式
    // 作对应的处理
    switch (subscription.subscriberMethod.threadMode) {
        // ThreadMode.POSITING模式
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        // ThreadMode.MAIN模式
        case MAIN:
            // 判断发布的事件的线程是否为主线程
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        // ThreadMode.MAIN_ORDERED模式
        case MAIN_ORDERED:
            if (mainThreadPoster != null) {
                mainThreadPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        // ThreadMode.BACKGROUND模式
        case BACKGROUND:
            // 判断发布的事件的线程是否为主线程
            // 创建子进程处理事件
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        // ThreadMode.ASYNC模式
        case ASYNC:
            // 创建子进程处理事件
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + 
                                            subscription.subscriberMethod.threadMode);
    }
}

 在postToSubscription方法中,最终会根据订阅方法(或称事件处理方法)的线程模式来选择事件发布的方式,具体逻辑如下所示:

  • ThreadMode.POSTING模式

 ThreadMode.POSTING模式表示发布事件的线程与事件处理方法(订阅方法)是同一个线程,有可能是子线程(后台线程),也有可能是主线程,因此,在线程模式为ThreadMode.POSTING的事件处理方法中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。在该模式下,会调用EventBus的invokeSubscriber方法,该方法通过反射的方式调用事件处理方法,并将事件event作为参数传入,最终完成事件发布流程。

void invokeSubscriber(Subscription subscription, Object event) {
    try {
        // 通过反射调用订阅方法(事件处理方法)
        // 并将事件event作为参数传递给订阅方法
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}
  • ThreadMode.MAIN模式

 ThreadMode.MAIN模式表示事件处理会在主线程(UI线程)中,因此事件处理的事件不能过长,否则会出现ANR异常。在该模式下,事件的发布会根据当前发布事件的线程是否为UI线程作不同的处理,假如发送事件的线程为UI线程则会调用EventBus的invokeSubscriber方法通过反射形式完成事件发布;假如为非UI线程(后台线程),则会调用HandlerPoster的enqueue方法将执行流程由后台线程切换到UI线程,其中,HandlerPoster继承于Hander,在UI线程中被创建。最后,再调用EventBus的invokeSubscriber方法完成事件的发布。HandlerPoster相关源码如下:

public class HandlerPoster extends Handler implements Poster {

    private final PendingPostQueue queue;
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    private boolean handlerActive;

    protected HandlerPoster(EventBus eventBus, Looper looper, 
                            int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        // 将订阅者信息封装到PendingPost
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            // 将PendingPost插入到队列中
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                // 使用Handler向主线程发送消息
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            while (true) {
                // 从队列中取出PendingPost
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                // 发布事件
                // (主线程的事件处理方法接收到事件)
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
}
  • ThreadMode.BACKGROUND模式

 ThreadMode.BACKGROUND模式表示事件处理方法是在后台线程中处理的,在该模式下,如果事件是从后台线程发布出来的,那么该事件处理方法直接在发布事件的线程中执行,即直接调用EventBus的invokeSubscriber方法;如果事件是从UI线程发布出来的,那么该事件处理方法就会在新的线程中运行,通过调用BackgroundPoster的enqueue方法实现,该方法会将事件插入到一个队列中,然后启动线程池中的线程进行处理。BackgroundPoster类源码如下:

final class BackgroundPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        // 将订阅者信息封装到PendingPost
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            // 将PendingPost插入到队列
            queue.enqueue(pendingPost);
            // 启动线程池
            if (!executorRunning) {
                executorRunning = true;
                eventBus.getExecutorService().execute(this);
            }
        }
    }
	
    // 线程启动后,会执行run方法
    @Override
    public void run() {
        try {
            try {
                while (true) {
                    PendingPost pendingPost = queue.poll(1000);
                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();
                            if (pendingPost == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    
                    // 事件处理
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, 
                                         Thread.currentThread().getName() 
                                         + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }

}

  • ThreadMode.ASYNC模式

 ThreadMode.ASYNC模式表示事件处理方法都会在新创建的子线程执行,无论事件是哪种线程(UI线程或后台线程)发布的,因此在该模式下的事件处理方法中禁止进行UI更新操作。其中,子线程的创建是通过调用AsyncPoster的enqueue方法实现的,该方法会创建一个线程池,然后使用线程池中的线程完成事件的处理。AsyncPoster类源码如下:

class AsyncPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    AsyncPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        // 封装事件信息,插入到队列
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        // 启动线程池
        eventBus.getExecutorService().execute(this);
    }

    // 执行线程任务
    @Override
    public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        // 处理事件
        eventBus.invokeSubscriber(pendingPost);
    }

}

3. EventBus性能优化

 在第2小节源码的分析中,我们知道EventBus的订阅者注册流程默认是在项目运行时通过反射来事件订阅方法信息,但是如果项目中有大量的订阅事件的方法,肯定会对项目运行时的性能产生影响。实际上,除了在项目运行时采用反射方式查找订阅方法,EventBus还提供了在项目编译时通过注解处理器的方式查找订阅方法,该方式会生成一个辅助的索引类来保存这些订阅方法信息,而这个索引类为SubscriberIndex。具体操作方法如下:

(1)在项目app的build.gradle中引入注解处理器,并指定辅助索引类的名称和包名。添加好后,重新编译项目,最终会在app/build/generated/app_generated_sources/debug/目录下生成一个名为MyEventBusIndex的类。build.gradle配置如下:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                // 根据项目实际情况,指定辅助索引类的名称和包名
                arguments = [ eventBusIndex : 'com.jiangdg.test.MyEventBusIndex' ]
            }
        }
    }
}

dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
    // 引入注解处理器
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

(2)在项目的Application中创建默认的EventBus实例。

/** 全局Application类
 * author : jiangdg
 * date   : 2019/12/19 11:24
 * version: 1.0
 */
public class MPApplication extends Application {

    @Override
    protected void onCreate() {

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


}

 完成(1)(2)步之后,其他的用法同第1小节。考虑到篇幅原因,这里就不对其中的原理进行分析了,如果感兴趣可自行查看EventBus源码。


 最后,我们对EventBus框架的实现原理做一个小结,即EventBus是一个基于发布-订阅模式(或称事件总线模式)实现且用于Android的事件发布-订阅总线框架,事件总线模式可以看成是观察者模式的扩展与进化,它并不是24种设计模式之一,其与观察者模式最大的区别是,观察者模式中的观察者(Observer,或称订阅者)需要主动注册到事件发布主体(Subject)才能收到发布的事件,也就是说这个发布事件的主体很清楚有哪些订阅者,而事件总线模式却不需要订阅者主动注册到主体,主体也不知道谁注册订阅了它,整个事件发布-订阅过程依赖于“事件总线”(可理解为调度中心),该“事件总线”用于接收主体发布的事件,然后将事件依次通知在“事件总线”上订阅了该事件的订阅者。在整个EventBus框架中,管理者三个Map对象,这三个Map对象存储了订阅者、订阅方法和事件之间的关系。其中,在EventBus订阅者注册过程中,首先通过反射的方式获取订阅方法的注解信息来查找订阅方法,然后将其存储到对应的Map对象中以完成注册;在EventBus的注销订阅者过程中,则是根据订阅者将其保存在对应的Map对象中的信息移除;在EventBus的事件发布过程中,通过判断当前发布事件线程类型(UI线程或后台线程),来决定事件处理方法应该运行在哪种类型线程中,而对订阅方法的调用同样是通过反射实现的。

发布了83 篇原创文章 · 获赞 293 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/AndrExpert/article/details/103645957