EventBus 使用 / 架构 / 源码分析
EventBus 是针对 Android 优化的发布 - 订阅事件总线, 简化了 Android 组件间的通信. EventBus 以其简单易懂, 优雅, 开销小等优点而备受欢迎.
GitHub 地址: https://github.com/greenrobot/EventBus
1. 使用 1.1 gradle 中引入
API 'org.greenrobot:eventbus:3.0.0'
1.2 定义事件
定义一个类作为事件, 可以在类中定义不同的参数, 发布者赋值, 订阅者取值.
- public class TestEvent {
- private String mName;
- public TestEvent(String name) {
- mName = name;
- }
- public String getEventName() {
- return mName;
- }
- }
1.3 注册事件
首先需要将当前对象 (Activity/Fragment 等) 与 EventBus 绑定(一般在 onCreate 函数中进行注册)
EventBus.getDefault().register(this);
接收事件的函数:
- @Subscribe (threadMode = ThreadMode.MAIN, sticky = true)
- public void onTestKeyEvent(TestEvent event) {
- Log.d(TAG, "onTestKeyEvent | eventName=" + event.getEventName());
- Toast.makeText(this, "test event, name=" + event.getEventName(), Toast.LENGTH_SHORT).show();
- }
这里通过注解的方式, 定义事件的类型, 和回调的线程等信息.
查看 EventBus jar 包中 Subscribe 定义:
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD})
- public @interface Subscribe {
- ThreadMode
- threadMode() default ThreadMode.POSTING;
- /**
- * If true, delivers the most recent sticky event (posted with
- * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
- */
- boolean
- sticky
- () default false;
- /** Subscriber priority to influence the order of event delivery.
- * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
- * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
- * delivery among subscribers with different {@link ThreadMode}s! */
- int
- priority () default 0; }
查看 EventBus jar 包中 ThreadMode 定义:
a) POSTING : 回调在发布者线程
b) MAIN : 回调在主线程
c) BACKGROUND : 回调在子线程(如果发布在子线程者回调直接运行在该线程)
d) ASYNC : 异步回调(不回回调在发布线程也不会回调在主线程)
1.4 发送事件
发布者不需要进行注册, 只需要将事件 post 出去.
a) 普通事件: EventBus.getDefault().post(new TestEvent("normalEvent"));
b) 粘性事件: EventBus.getDefault().postSticky(new TestEvent("stickEvent"));
普通事件和粘性事件区别:
如果发布的是普通事件, 当前如果没有 Subscriber, 则后续注册的 Subscriber 也不会收到该事件.
如果发布的是粘性事件, 当前如果没有 Subscriber, 内部会暂存该事件, 当注册 Subscriber 时, 该 Subscriber 会立刻收到该事件.
2. 结构
采用了典型的订阅发布设计模式.
3. 源码分析
// 这里只分析其原理和结构不会细细推敲每一行代码
订阅者信息封装(Subscription):
定义了两个成员变量,
- final Object subscriber; // 订阅一个事件的对象
- final SubscriberMethod subscriberMethod; // 订阅的具体信息(方法名 / ThreadMode/isStrick/priority)
EventBus 主要成员变量:
- private final Map, CopyOnWriteArrayList> subscriptionsByEventType;
- private final Map>> typesBySubscriber;
- private final Map, Object> stickyEvents;
subscriptionsByEventType: 以 event(即事件类)为 key, 以订阅列表 (Subscription) 为 value, 事件发送之后, 在这里寻找订阅者, 而 Subscription 又是一个 CopyOnWriteArrayList, 这是一个线程安全的容器. 你们看会好奇, Subscription 到底是什么, 其实看下去就会了解的, 现在先提前说下: Subscription 是一个封装类, 封装了订阅者, 订阅方法这两个类.
typesBySubscriber: 以订阅者类为 key, 以 event 事件类为 value, 在进行 register 或 unregister 操作的时候, 会操作这个 map.
stickyEvents: 保存的是粘性事件
3.1 注册 Subscriber
注册过程, 也就是调用 regester 函数的执行过程(主要是通过反射将注册者信息添加到上述讲的两个 map 中: typesBySubscriber,subscriptionsByEventType)
a) SubscriberMethodFinder 是专门用来查找目标对象中所有订阅函数(带缓存, 避免同一个类多次反射查找). 反射可以获取函数的注解内容及每个函数的返回值 / 修饰符, 具体查看 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()));
- }
- }
- } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
- String methodName = method.getDeclaringClass().getName() + "." + method.getName();
- throw new EventBusException("@Subscribe method" + methodName +
- "must have exactly 1 parameter but has" + parameterTypes.length);
- }
- } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
- String methodName = method.getDeclaringClass().getName() + "." + method.getName();
- throw new EventBusException(methodName +
- "is a illegal @Subscribe method: must be public, non-static, and non-abstract");
- }
- }
- }
查找订阅函数
b) 将订阅函数添加到两个缓存 map 中
c) 如果订阅函数接收的是粘性事件, 则将缓存中的粘性事件回调给该订阅函数.
上述 b) c) 两个步骤的具体代码如下:
- private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
- Class<?--> eventType = subscriberMethod.eventType;
- Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
- CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
- if (subscriptions == null) {
- subscriptions = new CopyOnWriteArrayList<>();
- subscriptionsByEventType.put(eventType, subscriptions);
- } else {
- if (subscriptions.contains(newSubscription)) {
- throw new EventBusException("Subscriber" + subscriber.getClass() + "already registered to event"
- + eventType);
- }
- }
- 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> subscribedEvents = typesBySubscriber.get(subscriber);
- if (subscribedEvents == null) {
- subscribedEvents = new ArrayList<>();
- typesBySubscriber.put(subscriber, subscribedEvents);
- }
- subscribedEvents.add(eventType);
- if (subscriberMethod.sticky) {
- if (eventInheritance) {
- // 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, Object>> entries = stickyEvents.entrySet();
- for (Map.Entry, 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);
- }
- }
- }
缓存订阅信息 3.2 分发消息到每个 Subscriber
分发过程是从 subscriptionsByEventType 中取 Subscriber 并在指定的线程中回调接收函数的过程.
如何实现在不同线程中执行回调函数?
a)从订阅信息中获取订阅函数回调线程.
b) 在指定线程中回调订阅函数.
分发消息过程
- 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 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);
- }
- }
不同的的消息分发器在 EventBus 构造的时候初始化, 下面看一下 AsyncPoster 的源码如下:
- class AsyncPoster implements Runnable {
- 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);
- }
- }
AsyncPoster 分发器继承自 Runable, 核心是通过自定义的阻塞队列维护消息, 然后在 EventBus 定义的线程池中执行 runable 接口中的代码.
EventBus 中还定义了 BackgroundPoster/HandlerPoster 这里不赘述.
3.3 物理类图
其它细节:
上述分析只是讲解了 EventBus 大概原理, 并没有细细分析. 如, 代码中很多考虑了并发, 事件优先级等
来源: https://www.2cto.com/kf/201905/807972.html