EventBus vs Otto vs Guava--自定义消息总线
同步发表于http://avenwu.net/ioc/2015/01/29/custom_eventbus
Fork on github https://github.com/avenwu/support
Android有广播和Receiver可以处理消息的传递和响应,要进行消息-发布-订阅,除此之外作为开发者现在也有其他类似的方案可以选择,比如EventBus和Otto,都是比较热门的三方库。那么这些三方库到底是怎么实现模块之间的解耦,使得消息可以再不同的系统组件之间传递呢?
源码剖析
由于是开源的,完全可以通过分析源代码来了解这些个消息-发布-订阅方案在Android内是怎么实现的,下面分别针对EventBus, Otto,Guava简单分析。
EventBus v2.4.0
从github上获取最新的EventBus代码
git clone git@github.com:greenrobot/EventBus.git
直接找到EventBus这个类,从使用角度开始分析:
EventBus的使用可以参考项目wiki,简单的来说就是EventBus#register(),EventBus#post(),实现onEventXXX
- EventBus利用反射技术
- register时遍历订阅者,通过反射获取所有public void onEventXXX(XX)方法,构造对应的订阅实例,方便后期post时invoke方法【SubscriberMethodFinder】
- post后通过EventBus#postToSubscription分发至对应线程的事件控制器
Otto v1.3.6
Otto是大名鼎鼎的Square开发的
git clone git@github.com:square/otto.git
- Otto利用反射和注解
- register时遍历订阅者内的所有方法,根据Subscribe和Produce注解获得所有目标方法,@Subscribe public void xxx(YYY);订阅方法必须是public且只有一个参数
- post后在EventHandler#handleEvent内invoke事件,这里没有EventBus中的线程区分,默认是MainThread,也可以任何线程ANY,但是必须是一致的ThreadEnforcer
从代码上来看EventBus和Otto非常像,不知道EventBus作者在设计编码时是否参考了Otto得设计,Otto项目则明确表示其是基于Google的Guava而来,Guava是Google开发的一个工具类库包含了非常多的实用工具类,其中就有一个EventBus模块,但是这个EventBus是并没有针对Android平台做线程方面的考量。
所以三者的是有关联的:
- Guava EventBus首先实现了一个基于发布订阅的消息类库,默认以注解来查找订阅者
- Otto借鉴Guava EventBus,针对Android平台做了修改,默认以注解来查找订阅、生产者
- EventBus和前两个都很像,v2.4后基于反射的命名约定查找订阅者,根据其自己的说法,效率上优于Otto,当然我们测试过,这也不是本文的重点。
由于源代码也不少,所以只列举了核心代码对应的位置,感兴趣的童鞋肯定会自己去研读。
自定义一个EventBus
上面的库当然不是为了研究而研究,现在理解了他们的核心思路后,我们其实已经可以着手自己写一个简单版的消息-发布-订阅。
现在先定义要实现的程度:
- 基于UI线程的消息-发布-订阅
- 使用上合EventBus尽量保持一致,比如register,post,onEvent
思路设计
一个简单的Bus大致上需要有几个东西,Bus消息中心,负责绑定/解绑,发布/订阅;Finder查找定义好的消息处理方法;PostHandler分发消息并处理.

Bus实现
这里的Bus做成单例,这样无论在什么地方注册,发布都是有这个消息中心来处理。
用一个Map来保存我们的订阅关系,当消息到达时从map中取出该消息类型的所有订阅方法,通过反射依次invoke。
public class Bus {
static volatile Bus sInstance;
Finder mFinder;
Map<Class<?>, CopyOnWriteArrayList<Subscriber>> mSubscriberMap;
PostHandler mPostHandler;
private Bus() {
mFinder = new NameBasedFinder();
mSubscriberMap = new HashMap<>();
mPostHandler = new PostHandler(Looper.getMainLooper(), this);
}
public static Bus getDefault() {
if (sInstance == null) {
synchronized (Bus.class) {
if (sInstance == null) {
sInstance = new Bus();
}
}
}
return sInstance;
}
public void register(Object subscriber) {
List<Method> methods = mFinder.findSubscriber(subscriber.getClass());
if (methods == null || methods.size() < 1) {
return;
}
CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.get(subscriber.getClass());
if (subscribers == null) {
subscribers = new CopyOnWriteArrayList<>();
mSubscriberMap.put(methods.get(0).getParameterTypes()[0], subscribers);
}
for (Method method : methods) {
Subscriber newSubscriber = new Subscriber(subscriber, method);
subscribers.add(newSubscriber);
}
}
public void unregister(Object subscriber) {
CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.remove(subscriber.getClass());
if (subscribers != null) {
for (Subscriber s : subscribers) {
s.mMethod = null;
s.mSubscriber = null;
}
}
}
public void post(Object event) {
//TODO post with handler
mPostHandler.enqueue(event);
}
}
Finder
查找订阅方法即可以用注解,也可以用命名约定,这里先实现命名约定的方式。
为了处理方便这里和EventBus不完全一致,只做了方法名和参数的限制,但是最好实现的严谨些。
public class NameBasedFinder implements Finder {
@Override
public List<Method> findSubscriber(Class<?> subscriber) {
List<Method> methods = new ArrayList<>();
for (Method method : subscriber.getDeclaredMethods()) {
if (method.getName().startsWith("onEvent") && method.getParameterTypes().length == 1) {
methods.add(method);
Log.d("findSubscriber", "add method:" + method.getName());
}
}
return methods;
}
}
PostHandler
分发消息肯定要用到Handler,EventBus中自己维护了一个队列来来处理消息的入栈、出栈,我这里就世界用了Message来传递
public class PostHandler extends Handler {
final Bus mBus;
public PostHandler(Looper looper, Bus bus) {
super(looper);
mBus = bus;
}
@Override
public void handleMessage(Message msg) {
CopyOnWriteArrayList<Subscriber> subscribers = mBus.mSubscriberMap.get(msg.obj.getClass());
for (Subscriber subscriber : subscribers) {
subscriber.mMethod.setAccessible(true);
try {
subscriber.mMethod.invoke(subscriber.mSubscriber, msg.obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
void enqueue(Object event) {
Message message = obtainMessage();
message.obj = event;
sendMessage(message);
}
}
小结
基本上的代码都在这里,实现一个Bus还是挺简单的,当然如果吧各种情况都考虑进去就会变得复杂一些,比如支持多线程线程,也不可能想本文这样区区数百行代码就搞定。
感兴趣的可以到这里获取上面自定义bus的源代码:https://github.com/avenwu/support/tree/master/support/src/main/java/net/avenwu/support
EventBus vs Otto vs Guava--自定义消息总线的更多相关文章
- Android学习系列(43)--使用事件总线框架EventBus和Otto
事件总线框架 针对事件提供统一订阅,发布以达到组件间通信的解决方案. 原理 观察者模式. EventBus和Otto 先看EventBus的官方定义: Android optimized event ...
- Guava: 事件总线EventBus
EventBus 直译过来就是事件总线,它使用发布订阅模式支持组件之间的通信,不需要显式地注册回调,比观察者模式更灵活,可用于替换Java中传统的事件监听模式,EventBus的作用就是解耦,它不是通 ...
- Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线
Go beyond yourself rather than beyond others. 上篇文章 深入理解 EventBus 3.0 之使用篇 我们了解了 EventBus 的特性以及如何使用,这 ...
- EventBus和Otto第三方构架
代码 添加依赖:implementation 'org.greenrobot:eventbus:3.0.0'1注册并声明订阅者,然后发布事件最后解除注册 @Override protected voi ...
- EventBus vs Otto vs LiteEventBus
http://blog.chengyunfeng.com/?p=449 http://litesuits.com/
- RxJava重温基础
RxJava是什么 a library for composing asynchronous and event-based programs using observable sequences f ...
- Guava - EventBus(事件总线)
Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用本质对我们的模块和领域边界很好的解耦设计 ...
- EventBus 事件总线 案例
简介 地址:https://github.com/greenrobot/EventBus EventBus是一个[发布 / 订阅]的事件总线.简单点说,就是两人[约定]好怎么通信,一人发布消息,另外一 ...
- 【Android】事件总线(解耦组件) EventBus 详解
当Android项目越来越庞大的时候,应用的各个部件之间的通信变得越来越复杂,例如:当某一条件发生时,应用中有几个部件对这个消息感兴趣,那么我们通常采用的就是观察者模式,使用观察者模式有一个弊病就是部 ...
随机推荐
- BZOJ.2095.[POI2010]Bridges(最大流ISAP 二分 欧拉回路)
题目链接 最小化最大的一条边,二分答案.然后就变成了给一张无向图定向使其为欧拉回路 二分答案后对于一个位置的两条边可能都保留,即双向边,需要给它定向:可能只保留小的一条,即单向边,不需考虑 如何给它定 ...
- BZOJ.4653.[NOI2016]区间(线段树)
BZOJ4653 UOJ222 考虑二分.那么我们可以按区间长度从小到大枚举每个区间,对每个区间可以得到一个可用区间长度范围. 我们要求是否存在一个点被这些区间覆盖至少\(m\)次.这可以用线段树区间 ...
- 轻松学C#----第二篇笔记
第二篇: 分析下第一篇的代码,见下图: 2.同其他语言一样,C#语言在编写时也遵守一定的语法规范. A.标识符(identify):指为方法.变量.其他任何用户自定义项指定的名称.标识符必须遵循一定的 ...
- VS2012 VS2015打开项目加载失败
VS2012 VS2015打开项目加载失败 改成这个$(MSBuildToolsVersion)试试
- C++泛型编程(1)--自己实现C++迭代器/遍历器 iterator
1.原理 迭代器又称为遍历器,用于访问容器中的数据,迭代器旨在算法和容器之间搭建访问的桥梁,从而使算法和数据分离,不用关心数据具体的存储细节.具体的原理描述请参考以下两个博客: [1].C++迭代器 ...
- C# Dictionary<TKey,TValue>如何添加键重复的内容
这里以Dictionary<string,string>为例 当我们实例化Dictionary<string,string>集合时,其中有一个重载构造方法如下: // // 摘 ...
- Ubuntu下升级Git以及获取ssh keys的代码
今天开始用的git,记下获取ssh keys 的代码 ? 1 2 3 ssh-keygen -t rsa -C "your_email@example.com" # Enter f ...
- Knockout.Js官网学习(Mapping高级用法二)
使用ignore忽略不需要map的属性 如果在map的时候,你想忽略一些属性,你可以使用ignore累声明需要忽略的属性名称集合: " }; var mapping = { 'ignore' ...
- GitHub超全机器学习工程师成长路线图,开源两日收获3700+Star!【转】
作者 | 琥珀 出品 | AI科技大本营(ID:rgznai100) 近日,一个在 GitHub 上开源即收获了 3700+ Star 的项目,引起了营长的注意.据介绍,该项目以 TensorFlow ...
- 修改覆盖springboot默认日志策略logback
目录 背景 自定义 背景 springboot初始化了日志的默认实现,只要我们在配置文件添加对应的配置即可. 比如 logging: file: logs/application-debug.log ...