EventBus源码阅读笔记

一、简介

EventBus是一个事件“发布/订阅”总线,可用于Android或者Java项目中。它具有以下优点:

  1. 简化组件间的通信
    • 解耦事件发布方和订阅方;
    • 很好的实现在Activities、Fragments和后台线程间通信;
    • 避免复杂的易错的依赖和生命周期问题;
  2. 使代码更简洁;
  3. SDK体积很小(约50k);
  4. 已经在上亿的安装应用中使用;
  5. 具有高级特性,如跨线程事件分发、按优先级分发等。

EventBus

下文我们把Publisher翻译成事件发布器,把Subscriber翻译成事件订阅者,Event翻译成事件,EventBus翻译成总线

二、EventBus使用三部曲

1. 定义事件类型

public class MessageEvent {
    public String msg;
}

2. 事件订阅

public class MainActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();
        // 将事件订阅者注册到总线中
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 将事件订阅者从总线中注销掉
        EventBus.getDefault().unregister(this);
    }

    // 定义事件接收器处理事件的方法
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Log.d(TAG, "onMessageEvent="+event.msg);
    }
}

3. 事件发布

public void sendEventMsg(View v){
    // 事件发布器发布事件
    MessageEvent event = new MessageEvent();
    event.msg = "hello";
    EventBus.getDefault().post(event);
}

三、源码分析

正如官方文档介绍,sdk真的很小。核心类EventBus.java就500行代码完成了所有功能。短小精悍、简洁易用、稳定高效,实为我辈开发之楷模。EvenBus核心任务就两个:1、事件订阅者的管理;2、事件的分发。先总的看下整体架构,类图大致如下:

类图

1. EventBus的创建

EventBus SDK使用不需要任何初始化操作。在需要订阅或者发布事件时,通过EventBus.getDefault()方法获取一个全局的EventBus的单例来执行相关操作。下面是SDK源码中单例创建的代码,没什么好说的标准的单例创建。

// double check lock
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus(); 
            }
        }
    }
    return defaultInstance;
}

2. EventBus对事件订阅者的管理逻辑

上面EventBus使用部分介绍了,事件接收器的注册/注销是通过EventBus实例的register/unregister方法来完成的。我们重点分析下register方法的实现,unregister方法类似就不重复分析了。

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(Object subscriber)方法接收一个Object对象作为入参。

  1. 通过findSubscriberMethods(Class<?> subscriberClass)方法分析出该对象的类型对象中所有事件处理方法。是通过反射的方式找到我们用@Subscribe注解的方法,如下代码所示。
  2. 将Object对象和上面找到的每个事件处理方法都分别组成一个事件订阅者Subscription,也就是说如果一个订阅对象类里面实现了多个事件处理方法,那就会生成多个事件订阅者。然后将这些事件订阅者对象放到集合Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType中保存起来,待事件分发时使用。subscriptionsByEventType是一个map,map的key是事件类型,value是相同事件的订阅者的列表。插入到列表的时候是按照订阅者优先级顺序插入的,后续事件分发的时候就会按照事件优先级来顺序分发。

类比上面使用说明中的例子,我们传入的Object对象是一个Activity对象,事件处理方法就是onMessageEvent方法。在EventBus中是将这个Activity对象和onMessageEvent方法和成了一个事件订阅者Subscription,然后存在数剧集合中的。上面类图中事件订阅者Subscription的结构清楚的展示了这一点。

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods = findState.clazz.getDeclaredMethods();
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        // 1、必须是public类型,且不能是被static、abstract修饰的方法
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 2、方法有且只有一个参数
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                //3、方法必须是被Subscribe注解的方法
                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");
        }
    }
}

unregister的过程我们就不分析了,类似register的逆过程,就是从数据集合中移除对应的事件订阅者的过程。

3. EventBus对事件的分发逻辑

事件发布post(Object event)方法接收一个事件对象作为入参。还记得我们事件订阅者是存在以事件类型为key的map中,那事情就简单了,通过入参的事件类型获取所有订阅此事件的事件订阅者列表。

  1. 执行事件订阅者Subscription中事件订阅对象的事件处理方法;
void invokeSubscriber(Subscription subscription, Object event) {
  subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
}
  1. 跨线程分发
    EventBus中有两个poster。mainThreadPoster:如类图所示它继承了Handler,关联的是主线程的Looper,所以可以通过mainThreadPoster将事件发送到主线程执行;backgroundPoster:如果所示本身是个Runnable,关联了一个线程池,所以它是将事件发送到线程池去执行的。
    还有一种类型是不适用上面的poster,直接就在事件发布器调用post(Object event)方法的线程执行事件处理方法,这就实现了在当前线程分发事件。

四、学习

1、Double-Check单例创建法
  • Double-Check是为了执行提高效率,只在首次获取实例的时候有线程同步的开销,后续获取实例时不涉及同步;
  • 不同步的引用对象不是线程安全的,除double和float外的基本变量类型是线程安全的。所以在double check模式下引用对象、double和float变量都需要使用volatile 关键字来保证其线程安全。
2、注解

RetentionPolicy.RUNTIME类型的注解可以在运行时通过反射的方式获取到注解的内容。如下运行时注解Subscribe可应用到方法上。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
    boolean sticky() default false;
    int priority() default 0;
}

运行时通过反射获取到注解的相关信息。

Method[] methods = findState.clazz.getDeclaredMethods();

for (Method method : methods) {
    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
    if (subscribeAnnotation != null) {
        ThreadMode threadMode = subscribeAnnotation.threadMode();
    }
}
3、多线程编程

根据前文的介绍可以了解整个框架中的临界资源应该是这个Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType数据集合对象了。在注册/注销时间订阅者和事件分发是都需要操作这个数据集合,可能出现多个线程同时注册/注销时间订阅者,或者注册/注销时间订阅者和事件分发不在同一个线程,这些场景都有多线程的问题。
我们来看看作者是怎么处理的。

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}
public synchronized void unregister(Object subscriber) {
    ... ...
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    ... ...
    return false;
}

subscriptionsByEventType的操作使用synchronized关键字来同步。另外对于事件订阅者列表使用CopyOnWriteArrayList来同步修改列表,同时让列表的遍历更加高效。

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

推荐阅读更多精彩内容

  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,972评论 4 42
  • 先吐槽一下博客园的MarkDown编辑器,推出的时候还很高兴博客园支持MarkDown了,试用了下发现支持不完善就...
    Ten_Minutes阅读 557评论 0 2
  • EventBus基本使用 EventBus基于观察者模式的Android事件分发总线。 从这个图可以看出,Even...
    顾氏名清明阅读 616评论 0 1
  • 我想跑到很远很远的地方去,没有人认识我,我也不认识任何人,然后失忆,不记得任何人任何事,包括我的养父母。一直失忆,...
    漠北的孤独阅读 171评论 0 0
  • 2016-06-14 华杉 《尚书》上说:“能自得师者王,谓人莫己若者亡。”能处处以人为师者王天下,觉得谁都不如自...
    郁萍阅读 674评论 0 0