EventBus源码解析

Greenrobot官网地址
Github地址

1.简介

(1)EventBus是一款发布订阅式的事件总线框架,适用于Android和Java开发。通过EventBus能更方便的进行Android组件间通信,使订阅者和事件发布者解耦,让代码看上去更简洁。部分特点:基于运行时注解、支持订阅优先级、事件发布线程切换、粘性事件等。

(2)Eventbus架构
Eventbus的发布订阅本质上就是观察者模式,也可以简单理解为接口回调(被观察者持有观察者的接口引用,触发时被观察者直接调用观察者的接口引用,如上图中间的Eventbus即充当了引用持有者的角色,将发布者和接收者进行了解耦)


eventbus_architect.png
未使用Eventbus的组件间通信 使用Eventbus后的组件间通信
eventbus_before.png
eventbus_after.png

可见使用Eventbus后组件间已完全解耦,图片出自其PPTEventBus3 slides

(3)订阅速度比较
Eventbus3从2015年开始发布,采用编译时注解寻找接收消息的方法,需要手动开启编译时注解,否则默认使用反射的方式寻找。Eventbus3之前采用的是固定的方法名字接收。

下图是在Nexus 9(M)机型测试的每秒订阅次数,包括Otto、Eventbus的不同版本,以及Eventbus3使用编译时注解和反射分别的速度,可见使用编译时注解生成的方法index比其它方式有明显速度优势,表格中越高表示注册速度越快,不过Eventbus3需要手动开启编译时注解,同时配置注解库才能使用,不过对于大多数场景订阅数并不多,使用默认反射策略基本不受影响。

eventbus_performance.png

出自EventBus 3 beta announced at droidcon

2.使用方式

  • (1)导入Eventbus依赖,可导入注解处理器以使用编译时注解生成订阅者方法索引
  • (2)定义消息类型
  • (3)准备订阅者,声明注解和接收方法
  • (4)发送消息
(1) implementation("org.greenrobot:eventbus:3.3.1")

(2) public static class MessageEvent { // xxx filed }

(3) @Subscribe(threadMode = ThreadMode.ASYNC)  
public void onMessageEvent(MessageEvent event) {
    // receieve event
}
EventBus.getDefault().register(this);
EventBus.getDefault().unregister(this);

(4) EventBus.getDefault().post(new MessageEvent());
apply plugin: 'kotlin-kapt'

kapt {
    arguments {
        arg('eventBusIndex', 'com.chris.twain.MyEventBusAppIndex')  // 需包含包名
    }
}

dependencies {
    implementation "org.greenrobot:eventbus:3.3.1"
    kapt "org.greenrobot:eventbus-annotation-processor:3.3.1"
}

然后rebuild工程后即可在build目录下生成对应的方法索引代码,使用注解处理器生成的方法索引需要导入注解处理器并自行通过builder模式build Eventbus对象
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusAppIndex()).build();

3.注册

存储订阅关系的数据结构:

/**
 * 按照事件类型分类的订阅方法 HashMap
 * key:Class<?> 事件类的 Class 对象, value:CopyOnWriteArrayList<Subscription> 订阅者方法包装类集合
 */
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
/**
 * 订阅者所接收的事件类型
 * key:Object 订阅者, value:List<Class<?>> 所接收的事件类型的 Class 对象 List
 */
private final Map<Object, List<Class<?>>> typesBySubscriber;

入参为订阅者对象,用于寻找订阅者对象、接收消息的方法和事件类型
故有订阅者对象、方法和接收的事件类型组合成的Map
一个订阅者对象可接收多个消息类型,故有消息类型与订阅者的Map

// EventBus.java
public void register(Object subscriber) {
    // 通过反射获取Android Lopper类来确定当前是否是Android平台下调用,若在Android下则判断当前库是否依赖了Android相关兼容代码,
    // 否则抛出异常,因为Eventbus支持Android平台合纯Java平台
    if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
        // 满足条件进入此分支后,表示是 Android 平台,但是没有依赖 EventBus 的 Android 兼容库
        throw new RuntimeException("It looks like you are using EventBus on Android, " +
                "make sure to add the \"eventbus\" Android library to your dependencies.");
    }

    // 反射获取订阅者的 Class 对象
    Class<?> subscriberClass = subscriber.getClass();
    // 通过 subscriberMethodFinder 订阅方法查找器去查找订阅者的订阅方法,得到一个订阅方法List<SubscriberMethod>
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        // 对订阅方法 List 进行遍历
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // 遍历到的每一个方法对其产生订阅关系,就是正式存放在订阅者的大集合中
            subscribe(subscriber, subscriberMethod);
        }
    }
}

查找订阅者收消息方法

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // 首先尝试从缓存中获取订阅方法 List
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    // 判断从缓存中获取订阅方法 List 是否为null
    if (subscriberMethods != null) {
        // 如果不为 null,就返回缓存中的订阅方法 List
        return subscriberMethods;
    }

    // 是否强制使用反射查找,通过此标志位判断,默认false,可手动通过builder构造Eventbus对象时设置为ture
    if (ignoreGeneratedIndex) {
        // 忽略索引的情况下,通过反射进行查找订阅者方法
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        // 通过索引的方式进行查找
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    // 如果没有订阅者方法,就抛出 EventBusException 异常
    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;
    }
}

默认EventBus.getDefault()获取的Eventbus对象将采用反射的方式运行时获取订阅消息接收的方法
通过配置注解处理器则将调用findUsingInfo()方法读取编译时注解处理器生成的订阅消息接收方法的索引,使方法查找更高效

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    // 获取一个 FindState 对象
    FindState findState = prepareFindState();
    // 为当前订阅者初始化 FindState
    findState.initForSubscriber(subscriberClass);
    // 循环 从子类到父类查找订阅方法
    while (findState.clazz != null) {
        // 获取索引类并赋值给 findState.subscriberInfo
        findState.subscriberInfo = getSubscriberInfo(findState);
        // 当索引类的信息类不为 null 时,进一步操作
        if (findState.subscriberInfo != null) {
            // 获取索引类的所有订阅者方法
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            // 对订阅者方法数组遍历
            for (SubscriberMethod subscriberMethod : array) {
                // 检查并将方法添加
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    // 校验通过,将该订阅方法添加至 subscriberMethods,
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 如果没有索引类,就使用反射的方式继续查找
            findUsingReflectionInSingleClass(findState);
        }
        // 将 findState 的 Class 对象移至父类型
        findState.moveToSuperclass();
    }
    // 返回查找到的订阅者方法并释放资源
    return getMethodsAndRelease(findState);
}

寻找完消息接收方法后,完成订阅

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // 获取订阅者方法接收的事件类型 Class 对象
    Class<?> eventType = subscriberMethod.eventType;
    // 创建 Subscription
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    // 从 subscriptionsByEventType 中 尝试获取当前订阅方法接收的事件类型的值
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        // 如果为 null,表示该方法是第一个,创建空的CopyOnWriteArrayList put 进 subscriptionsByEventType
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        // 如果不为 null,判断现有数据中是否存在该方法,如果存在抛出 EventBusException 异常
        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++) {
        // 这一步主要是将订阅者方法添加进 subscriptionsByEventType 数据中,并且会按照优先级进行插入
        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);

    // 对黏性事件进行处理
    if (subscriberMethod.sticky) {
        // 是否事件继承
        if (eventInheritance) {
            // 必须考虑所有 eventType 子类的现有粘性事件。
            // Note: 迭代所有事件可能会因大量粘性事件而效率低下,因此应更改数据结构以允许更有效的查找
            // (e.g. 存储超类的子类的附加映射: Class -> List<Class>).
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                // 判断 eventType 是否是 candidateEventType 的父类或父接口
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    // 如果是父子关系  进行事件检查和发布
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            // 从黏性事件 Map 中获取当前事件类型的最新事件
            Object stickyEvent = stickyEvents.get(eventType);
            // 校验事件并发布事件
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

4.消息/事件发布

public void post(Object event) {
    // 从当前线程中取得线程专属变量 PostingThreadState 实例,ThreadLocal实现
    PostingThreadState postingState = currentPostingThreadState.get();
    // 拿到事件队列
    List<Object> eventQueue = postingState.eventQueue;
    // 将事件入队
    eventQueue.add(event);

    // 判断当前线程是否在发布事件中
    if (!postingState.isPosting) {
        // 设置当前线程是否是主线程
        postingState.isMainThread = isMainThread();
        // 将当前线程标记为正在发布
        postingState.isPosting = true;
        // 如果 canceled 为 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;
        }
    }
}


private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    // 获取事件的Class对象
    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);
        }
        // 判断事件无匹配订阅函数时,是否发布 NoSubscriberEvent
        if (sendNoSubscriberEvent
                && eventClass != NoSubscriberEvent.class
                && eventClass != SubscriberExceptionEvent.class) {
            // 发布 NoSubscriberEvent
            post(new NoSubscriberEvent(this, event));
        }
    }
}

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    // 加锁 监视器为当前对象
    synchronized (this) {
        // 获取该 Class 对象的订阅方法 List
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        // 如果存在订阅方法,就进行遍历操作
        for (Subscription subscription : subscriptions) {
            // 将事件和订阅方法赋值给 postingState
            postingState.event = event;
            postingState.subscription = subscription;
            // 是否中止
            boolean aborted;
            try {
                // 将事件发布到订阅者
                postToSubscription(subscription, event, postingState.isMainThread);
                // 是否已经取消发布
                aborted = postingState.canceled;
            } finally {
                // 重置 postingState 状态
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            // 如果已经中止,就跳出循环
            // TODO: 2022/5/27 为什么不在开始处判断? 还要发布一次后再取消?
            if (aborted) {
                break;
            }
        }
        // 方法体结束,返回找到订阅关系
        return true;
    }
    // 到此步骤表示没有订阅方法,返回 false
    return false;
}

5.消息接收

EventBus五种消息接收的线程模式

// ThreadMode.java
public enum ThreadMode {
    /**
     * Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery
     * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
     * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers
     * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
     
     消息接收与发送处于同一线程
     */
    POSTING,

    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is
     * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event
     * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread.
     * If not on Android, behaves the same as {@link #POSTING}.
     主线程执行消息接收
     */
    MAIN,

    /**
     * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},
     * the event will always be queued for delivery. This ensures that the post call is non-blocking.
     同上,但每次都放到主线程Lopper消息队列尾部,确保异步
     */
    MAIN_ORDERED,

    /**
     * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods
     * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
     * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to
     * return quickly to avoid blocking the background thread. If not on Android, always uses a background thread.
     确保不在主线程收消息,若发消息在主线程则切到线程池,若发消息在子线程则不切线程
     */
    BACKGROUND,

    /**
     * Subscriber will be called in a separate thread. This is always independent from the posting thread and the
     * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should
     * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
     * of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus
     * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications.
     */
     收消息方法放入线程池中执行
    ASYNC
}
// EventBus.java
    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);
        }
    }

Android平台下有主线程和子线程的5种收消息执行的线程模式,若不在Android下则仅两种,一种收发消息在相同线程,一种是收消息通过线程池切换线程
Eventbus的ASYNC线程模式使用的默认线程池为Executors.newCachedThreadPool(),也可以自定义线程池通过Builder构造Eventbus对象时传入

6.解注册

根据此订阅者接收消息的事件类型和此订阅者逐个从订阅集合中移除

public synchronized void unregister(Object subscriber) {
    // 获取订阅者接收的事件类型
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        // 按事件类型遍历退订相关订阅方法
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        // 从订阅者所接收的事件类型Map中移除该订阅者
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

Caused by: org.greenrobot.eventbus.EventBusException: Subscriber class com.xxxk.MyTestClass and its super classes have no public methods with the @Subscribe annotation
at org.greenrobot.eventbus.SubscriberMethodFinder.findSubscriberMethods(SubscriberMethodFinder.java:67)

官网开启APT
EventBus 3.0.0高效使用及源码解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容