十、EventBus 源码随想

EventBus 源码随想

首先网上已经有不少优秀的EventBus的源码分析文章,这篇只是为了记录自己的理解,毕竟自己亲自写出来才能理解的更深,所以如有不对的地方,还望谅解。

参考
https://www.jianshu.com/p/f057c460c77e
http://p.codekk.com/blogs/detail/54cfab086c4761e5001b2538
https://kymjs.com/code/2015/12/16/01/

0. 几个问题

EventBus 的使用过程无非就是 注册、post、响应事件函数。
那么我们得弄清楚这几个问题

1、怎样进行注册的 ?
2、post 的事件是 什么,即 post 里的参数到底代表着什么?
3、post 时,订阅者是怎样收到响应的,是怎么通知到所有与事件相关的订阅者的?

1. 注册

// 把当前类注册为订阅者(Subscriber)
EventBus.getDefault().register(this);

// 解除当前类的注册
EventBus.getDefault().unregister(this);

代码很简单,就一行,那么我们来看看到底是怎样注册的。在注册的时候究竟做了什么。在整个 EventBus 的使用过程中,除了注册大部分就是 post 和 事件响应函数了,所以我们猜测在注册的时候,应该会把事件和订阅者绑定起来。那么我们带着这个疑惑去看这段代码。

1.1 获取 EventBus 对象

首先 Event.getDefault() 看到这个应该能想到是个 单例模式。

    static volatile EventBus defaultInstance;

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

果不其然,一个双重校验锁的单例模式。

    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

    public EventBus() {
        this(DEFAULT_BUILDER);
    }

    EventBus(EventBusBuilder builder) {
        // 省略
    }

可以看到上面构造函数传入了一个 builder,很明显 建造者模式。 至此,我们可以获取到了一个单例的 EventBus 的对象了,另外关于 builder 的更详细的内容可自己看源码。

1.2 register

获取到对象后,就调用 register 方法了,里面传入了 this 参数。

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

我们可以看到 register 方法的参数名是 subscriber,说明我们传进去的 this 就是订阅者对象。此时我们清楚了 当前类就是订阅者。
这个方法里面做了什么呢,我们来看一看。首先获取到传进去的 this (即当前类)的 Class 对象,然后调用 subscriberMethodFinder.findSubscriberMethods(subscriberClass); 我们根据名字可以猜想,这应该是去找到当前订阅者的所有 事件响应函数(即带有 @Subscribe 注解的方法)。 找到所有事件响应函数后,就调用 subscribe, 那这里我们就可以猜想是把 当前类对象 与这些 事件响应函数 关联起来。下面验证我们的猜想。

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

这里面我们直接找到 findUsingReflection 方法, 根据方法名也知道这是什么意思了。 继续跟进

    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findUsingReflectionInSingleClass(findState);
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

这里的 FindState 用于做 事件响应函数 的校验和保存。继续跟进 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");
            }
        }
    }

这里如果知道反射的应该都看得懂,没什么复杂的地方。 从这里的实现我们可以知道,这个 eventType 其实是第一个参数类型, 并且保证只能有一个参数。 这样我们的事件其实是以 事件响应函数的 参数类型 为基准的,可以看到如果参数的数量不为 1 ,会抛出异常。

最后再通过 findUsingReflection 方法的 getMethodsAndRelease 返回一个 List<SubscriberMethod>
至此, register 方法的 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); 这一行代码就走完了,我们拿到了该订阅类的所有 事件响应函数 , 即 SubscriberMethod 的集合。

1.2.2 subscribe

接着继续 register 往下走,可以看到会循环 List<SubscriberMethod> 这个集合,取出每个 SubscriberMethod(事件响应函数),然后调用 subscribe,我们看看这个方法到底做了什么。
这里先给出结果,这个方法就是我们注册的关键了,它完成了我们每个 事件响应函数的注册过程。

首先这个方法会涉及两个变量,我一开始特别懵,完全不知道这两个变量啥意思,不过图一画出来就立马清晰了。
这两个变量是 subscriptionsByEventType 和 typesBySubscriber 。基本上理解这两个变量是什么意思,整个注册流程就通了。

首先我们定义两个类和方法,如下所示,这个得认真理清楚。

public class A {
    @Subscribe
    public void testA1(Event1 event1) {
        //do something
    }

    @Subscribe
    public void testA2(Event2 event2) {
        //do something
    }
}

public class B {
    @Subscribe
    public void testB1(Event1 event1) {
        //do something
    }

    @Subscribe
    public void testB2(Event2 event2) {
        //do something
    }
}

可以看到这两个类分别有着两个事件响应函数,事件类型有两种,Event1 和 Event2
subscriptionsByEventType 这个变量是 Map<Class<?>, CopyOnWriteArrayList<Subscription>> 类型,它的图示如下:

subscriptionsByEventType.png

typesBySubscriber 这个变量是 Map<Object, List<Class<?>>> 类都,它的图示如下:

typesBySubscriber.png

至于其中的 SubscriberMethod 对象,在 findUsingReflectionInSingleClass 方法中可以看到是怎样构造出来的。这个对象也是非常重要的,里面封装了 方法信息 method、线程模式 threadMode、参数类型 eventType、优先级 priority、sticky 布尔值 等信息。

相信这两个图可以帮助你很好的理解接下来的 subscribe 流程。关于 subscribe 的源码,以及流程我就不分析了,自己看源码对着上面两个图绝对能理解。 不能理解的话,还有 参考 内的文章,这里一定得自己去弄懂。当然并不复杂,所以耐心点。

当 subscribe 走完后,我们的 EventBus 拥有了什么?
它拥有了 事件类型(例 Event1) 所对应的 Subscription 信息列表,什么意思,就是这个 事件 在 哪些类的哪些方法存在,有了这些信息,EventBus 就能很方便的将一个 事件 传递给所有 订阅了这个事件的 方法了。

那么 typesBySubscriber 有什么用呢,在 unregister 时,它可以发挥它的作用。那么趁热打铁,看看 unregister 是如何工作的。噢,对了,放上注册的流程图,是取自 codekk 的流程图,相信再看这个图,你会非常清晰。

register-flow-chart.png

2 unregister 解除注册

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

第一行就用到了刚刚的 typesBySubscriber, 这里将当前订阅者的 事件类型列表 取出来,也就是拿到了当前类的所有 事件。
然后判断 事件列表 是否为空,不为空则 循环事件列表,依次调用 unsubscribeByEventType(subscriber, eventType); 最后调用 typesBySubscriber.remove(subscriber); 这个是把 当前订阅者 从 typesBySubscriber 中删掉,这样就完成了解除绑定的操作。当然最重要的是 unsubscribeByEventType 这个方法。

2.1 unsubscribeByEventType
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

这个根据上面的 subscriptionsByEventType 图示,应该不难理解。
首先从 subscriptionsByEventType 中拿到当前 事件(eventType)的 Subscription 列表。 然后循环这个列表,从中找到传进来的 订阅者(subscriber 即 当前解除注册的那个 类,找到后,从 Subscription 列表 中删除即可。

3. post 发布事件

前面注册和解除注册两个流程都走通了,那么只剩下 post 了。关于 post 的流程,参考内的文章都讲的很好,碍于篇幅,就不重复阐述了,这里说说我刚看时,不懂的几个地方。

1、 postSingleEvent 方法会先去得到该事件类型的所有父类及接口类型,然后循环 调用 postSingleEventForEventType 函数发布每个事件到每个订阅者? 这里说了这么多是什么意思。
2、 ThreadMode 对应的四种状态的 Poster。

针对第一个问题很好解释,我们基于上面的 Event1 事件解释,假设有一个 订阅者订阅了 Object 类型的事件,而 Event1 的父类是 Object,那么我们 post Event1事件 时, 订阅了 Event1 事件的订阅者订阅了 Object 事件的订阅者 是不是都得接收到这个事件。所以我们需要处理父类及其接口类型的 post。

第二个问题,看了 post 的源码应该知道最后会进入到 postToSubscription 这个方法,判断 subscription.subscriberMethod.threadMode 然后调用 invokeSubscriber 完成 post流程。
那么这个 threadMode 有四种状态,

3.1 POSTING

首先看下这个 POSTING 状态,这是 默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程),也就是说你是哪个线程就会在哪个线程执行。 代码中可以看到直接调用了 invokeSubscriber ,看看 invokeSubscriber 方法的源码。

void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

就是通过反射执行了 method , 这样我们也就清楚了,事件函数得到了响应。
注意: 若 Post 线程为主线程,别忘了不能进行耗时操作

3.2 MAIN

            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;

这个状态我们可以看到,首先会判断是否是 主线程, 如果是直接调用 invokeSubscriber ,如果不是会去调用 mainThreadPoster.enqueue 。 那么这个 mainThreadPoster 是什么?

3.2.1 mainThreadPoster

这个 mainThreadPoster 会在构造函数中进行初始化,自己可以去源码里看看, 可以知道最后就是 new 一个 HandlerPoster。
看看 HandlerPoster 的构造函数

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

这个 looper 是主线程的 looper,也就是说最后发送的消息,会在主线程去处理
然后会 new 一个 PendingPostQueue, 这个 PendingPostQueue 是一个 PendingPost 类型的队列。
而 PendingPost 则是 订阅者和事件信息实体类,并含有同一队列中指向下一个对象的指针。通过缓存存储不用的对象,减少下次创建的性能消耗。
会看的很懵,其实很好理解, PendingPostQueue就相当于 MessageQueue, PendingPost 则相当于 Message。当然只是类比,在调用 enqueue 的时候,会发送一个空的 Message,如下代码:

public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

可以看到我们把 pendingPost 放入到 PendingPostQueue, 然后发送一个空的 Message, 我们理解 Handler 机制,所以去看 handleMessage 吧。

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            while (true) {
                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;
        }
    }

代码很长,但是你可以很清楚的看到这句代码 eventBus.invokeSubscriber(pendingPost); 还记得 invokeSubscriber 方法吗,没错就是 通过反射执行 method。 当然这里重载了该 方法, 但是最终还是会走到 那个有两个参数的 invokeSubscriber 方法。

到这里,我们的 MAIN 状态的 post 流程也走完了。

3.3 BACKGROUND 和 ASYNC

这两个状态希望大家自己去看源码了,并没有什么复杂的,无非就是 backgroundPoster 和 asyncPoster 对线程处理的不同,这两个 Poster 内部同样有 PendingPostQueue 。

private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

这是 AsyncPoster 和 BackgroundPoster 内部的线程池 在 EventBusBuilder 中的定义。

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

推荐阅读更多精彩内容

  • 我每周会写一篇源代码分析的文章,以后也可能会有其他主题.如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达s...
    SkyKai阅读 24,902评论 23 184
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,468评论 0 50
  • 我的理想国 我不清楚是不是每个人心中都有一个理想国,但是我心中的理想国渐渐的清晰起来,哲学家心中的理想国是一个真正...
    宛若清风R阅读 808评论 0 0
  • 乐慢瑜伽私教馆 每天晚上,在睡前花15分钟抬抬脚,坚持二、三个月,就可以改善体质、精力倍增,对于生理不适、便秘、胃...
    小小井同学阅读 671评论 0 1
  • 《新年心语》 香陌野径凝天地, 火树银花化春风。 天高地厚微茫飞, 流光溢彩大潮涌。 红旗招展向久远, 高歌猛进新...
    曦微w行走在路上阅读 364评论 2 10