浅析EventBus 3.0实现思想

最近接触了EventBus,也看了一些源码分析的文章。在此就不再细述其代码的实现细节,主要针对其的设计思想做一些记录,也是自己思考的过程。同时本文尽量以较少的代码来将其主要设计思想说的透彻明白,不会针对细节做过多深入。

基本的事件发布订阅的实现

一般情况下,事件发布订阅机制都是跟观察者模式紧密相连。事件的发布中心都会维持着一组当前的观察者(也可叫做订阅者),这里称之为事件总线,(观察者的注册/取消则对应着在这组数据中进行添加和删除)。另外被观察者(也可叫发布者)则通过发出事件,事件总线拿到该事件,则在观察者列表中根据事件来查找相应的事件观察者,紧接着执行观察者的行为即可。对应一个简单的事件总线图如下:

事件总线

EventBus 3.0的实现

1. EventBus的基本使用

  • 发布事件:
EventBus.post(Object event);
  • 订阅事件:
@Subscribe
public void subscribe(Object event){
}
  • 订阅事件的注册以及取消注册:
EventBus.register(Object);
EventBus.unregister(Object);

由上可见,EventBus的使用,还是相当简单的。其中添加了@subscribe注解的方法,则代表着真正的事件订阅者,另外,添加了注解的方法,必须通过registerunregister来进行订阅和取消订阅,它俩相应的参数Object则指代的是@subscribe方法所在的类。
另可看出,事件订阅参数Object event,即是我们所在事件总线中传递使用事件的参数。当我们要确定这个事件被接受到,则需要保持event的class是相同的。

2. EventBus的事件总线

首先需要明白的是在EventBus中,真正对应的订阅对象是SubscriberMethod,其包含了相应的Method类,以及事件参数类型Class<?> eventType,其他就是线程,优先级,是否Sticky信息。

/** Used internally by EventBus and generated subscriber indexes. */
public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
 }

紧接着,谈及事件总线,一般都对应着一个集合,而EventBus中使用的是:

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

这里的Map集合采用的是一个HashMap集合,map的key对应就是之前SubscriberMethod中的eventType, value则对应着一个线程安全的List,List中存放的是包含订阅对象Object及相应订阅方法SubscriberMethodSubscription类:

final class Subscription {
    final Object subscriber;
    final SubscriberMethod subscriberMethod;
}

现在,就很明了了,List数组用来维持所有当前的订阅者,即对应着我们经常所说的事件总线中的集合。

3. EventBus的事件订阅/消费

  • EventBus中的订阅与取消订阅 -> register/unregister

1) 订阅类信息的封装: EventBus的register方法,则会将参数 Object类中@subscribe注解的所有方法,逐一封装为SubscriberMethod,再与参数subscriber统一封装为Subscription,这样因注解方法可以多个,而通过订阅类的register的方法,最后得到的将是一组订阅者。

2) 订阅者的添加: EventBus类会将这组订阅者,根据不同的eventType参数,将放置在上文提到的subscriptionsByEventType结构中。这样,一个事件中心的机制就完成了,注册事件时,就在List中添加方法订阅者;取消注册事件,同时也是针对这个List中移除的订阅类Object对象中相应的subscription

  • EventBus事件的消费/发布事件
    这里通过EventBus的post(Object event)方法,进行事件的发出。紧接着EventBus的总线LIst中找出订阅了这个event的方法Subscription,然后根据method指定的不同线程信息,将这个方法的调用,放置在相应线程中调用:
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

可以看出,订阅者已经被封装地非常完美,这样,我们在使用不同的线程调度策略就很简单了,随意指定一个ThreadMode即可在指定线程中调用。唯一不完美的地方,就是这里的调用时通过Method类调用的,肯定没有直接通过类调用来的直接,不过相对其带我们的好处来说,这点性能影响可以忽略不计了。

4. 优化之EventBusIndex

到这里,会发现有个很大的疑问:SubscriberMethod信息是怎么生成的?答案很肯定的嘛,就是加了注解@subscribe的方法嘛。我们可以通过在运行时通过采用反射的方法,获取相应添加了注解的方法,再封装成为SubscriberMethod。而对我们开发者来说,使用反射带来的性能消耗,不由得我们不慎重一二的。
在3.0的版本,EventBus加入了apt处理的逻辑,有个Subscriber Index的介绍,主要是通过Apt在编译期根据注解直接生成相应的信息,来避免在运行时通过反射来获取。使用方法如下:

apt {
    arguments {
        eventBusIndex "com.example.myapp.MyEventBusIndex"
    }
}

配置Index的调用如下:

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

之后,我们在执行了代码的编译之后,会生成一个包名为com.example.myapp的类MyEventBusIndex。这个类会实现一个如下的接口:

public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}

从接口中可以看出,这个Index类只提供了一个方法getSubsriberInfo,这个方法需要我们传入订阅者所在的class类,然后获得一个SubscriberInfo的类,其类结构如下:

public interface SubscriberInfo {
    Class<?> getSubscriberClass();

    SubscriberMethod[] getSubscriberMethods();

    SubscriberInfo getSuperSubscriberInfo();

    boolean shouldCheckSuperclass();
}

从接口中暴露的方法,即可获取所需的所有SubscriberMethod信息。而这只是一个获取单个类的方法,而apt生成的EventBusIndex中,会将所有的这些class及SubscriberInfo保存在静态变量hashMap结构,这样就达到了避免运行期反射获取生成订阅方法的性能问题。而变成另外一个流程:订阅类在注册的时候,直接通过HashMap中的订阅类,获取到SubscriberInfo,进而获取到所有的SubscerberMethod,并封装为Subscription,被添加到事件总线中。这样,在引入了apt之后,EventBus的性能问题就不需要我们担心了。(PS:在EventBus作者的博客也提及到了这一点,使用apt了的EventBus在性能表现这方便,犹如打鸡血一般。)

总结

EventBus将订阅者巧妙地转换为通过注解@subscriber定义的方法,方法的参数定义为事件所使用的数据类型,另外,可以指定订阅者不同的线程及优先级,给我们开发者带来最大的好处就是使用非常简单方便。这就不得不提及一些不好的地方了,可以看到的是其在设计实现的过程中,引入了许多中间对象,若是我们对内存使用及性能非常敏感的话,则必须自己实现一个更加轻量级的事件总线了。

后续

因这是一篇简单的介绍EventBus3.0设计思想实现的文章,但在查看其源码的过程中,也发现了其他很有意思的技术实现细节及线程处理等内容,将在下一篇中做介绍,欢迎关注。

PS: 转载请注明原文链接:http://alighters.com/blog/2016/05/22/eventbus3-dot-0-analyze/

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

推荐阅读更多精彩内容

  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,467评论 0 50
  • 项目到了一定阶段会出现一种甜蜜的负担:业务的不断发展与人员的流动性越来越大,代码维护与测试回归流程越来越繁琐。这个...
    fdacc6a1e764阅读 3,161评论 0 6
  • 最近在项目中使用了EventBus(3.0),觉得非常好用,于是就看了一些关于EventBus源码分析的文章,现在...
    shenhuniurou阅读 1,493评论 0 4
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,441评论 25 707
  • 本文参加#川理情•我的大学故事#活动,本人承诺,文章内容皆为原创,且未再其他平台发布过。 那一个占地几千亩,...
    三画生故事阅读 496评论 0 2