本文的EventBus,是指greenrobot的 EventBus, 主要以EventBus3.0 讲解;
什么是EventBus?
EventBus事件总线, 用于简化Android程序内,各个组件,线程之间的事件传递; 订阅发布模式,将事件的接收者和发布者解耦,一旦publisher发出消息,subscribe自己按需改变; 我个人喜欢把它拿来和BroadCast比较;
在什么场景下使用
- 复杂逻辑下的对象传递
- 函数的调用者与被调用者需要低耦合,或者框架设计之初,无法预料到的调用
eg. 上面的使用场景,在我们代码中时长出现的场景就是,监听器的传递,回调函数和各种Listener;
比如,在一个activity中,又2个fragment,而每个fragment中又各嵌套一个子fragment, 其中一个子fragment要监听另一个子fragment中的按钮变化; 一般做法是将listener作为函数参数传递, 或者设置为静态变量;
第二个, 就和BoardCast相似
怎么使用
- 在gradle中添加依赖
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
}
- 注册和取消注册
在要接收消息的类中register
unregister
, 和广播的注册类似, 一般在activity的 onCreate 和 onDestory 方法中进行
EventBus.getDefault().register( this );
EventBus.getDefault().unregister( this );
- 申明处理消息的函数;
在接收消息的函数上,加上@Subscribe
, EventBus是按函数参数的类型确认消息的接收者的, 此函数只能有且仅有一个参数;
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1, sticky = false)
public void onEvent( TestEvent testEvent ){
Log.e( "zy", ">>>> receiverEvent");
}
只需要在函数上加上 @Subscribe
注解即可, 此注解还可以带上额外的参数
threadMode
, 用于指定此函数运行的线程, 是一个Enum, 有4个常量,MAIN
BACKGROUND
ASYNC
POSTING
, 默认为POSTING
ThreadMode.MAIN
在主线程中运行
ThreadMode.POSTING
跟消息发送者在同一线程运行
ThreadMode.BACKGROUND
后台线程, 如果发送消息的线程就是后台线程,就直接执行; 如果不是, 则会把消息放在队列中,依次执行
ThreadMode.ASYNC
后台线程, 消息会在单独的线程中执行,用了线程池,多个消息会同时执行priority
优先级, 值越小优先级越低,当有多个方法处理同一个消息时,处理的顺序,默认为0sticky
是否接收黏性消息, 和黏性广播相同, 默认为false
- 发送消息
所谓的消息,就只是一个java对象, 发送消息就是把这个对象,传递给处理消息的函数; EventBus消息和EventBus的对象实例有关, 用一个EventBus对象发送的消息,必须是用同一个EventBus对象注册的才能收到消息.
// 发送黏性消息
EventBus.getDefault().postSticky( new TestEvent() );
// 发送普通的消息
EventBus.getDefault().post( new TestEvent() );
发送的消息有2种,
sticky
黏性消息, 当消息发送出去之后,如果没有消息接收者处理这个消息,此消息会暂时存储在eventBus实例中, 当后面注册接受者时,如果合适的处理者, 将会把消息给处理者去处理;我个人喜欢用这个来做数据的预加载;
- 提升性能, 增加编译时注解处理
由于android机器本身性能有限,一般不建议使用运行时注解,EventBus的注解声明为Runtime, 但它同时支持编译时注解和运行时注解, 当没配置编译时注解处理器时, 会自动通过反射查找运行时的注解;- 添加注解处理器依赖
buildscript {
...
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
// 在最外层添加gradle的插件依赖
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
...
}
// 项目中 增加注解处理器插件
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
// 添加注解处理器
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
arguments {
// 注解处理器 最终生成的java文件位置
eventBusIndex "com.zy.test.MyEventBusIndex"
}
}
2. 初始化EventBus时, 使用注解处理器生成的类文件
```java
mEventBus = EventBus.builder().addIndex( new MyEventBusIndex() ).build();
```
EventBus的消息和EventBus实例有关系, 自己配置的EventBus实例,一般需要用单例保存, 确保发送和接收消息的地方,使用的是同一个实例
关于其他的一些细节
- 消息处理者的继承
EventBus的消息处理者,是可以继承的, 父类中的消息处理器, 在子类中仍可使用; 这是一个比较好的功能, 比如通用的消息接收处理,我们在BaseActivity中声明一次, 子类都可以使用了; 此功能可以关闭, 在构建Eventbus实例时, 调用 `EventBus.builder().eventInheritance( false )` ; 官方的说法是关闭后可以提供20%的性能;
- 黏性消息
非常实用的功能, 我一般用来做预加载数据; 每种消息类型,最多存储一个黏性消息, 和黏性广播类似; 消息处理者. 声明为sticky = true
, 依然可以接收普通消息 - 进程间的通讯
Eventbus的发送消息和消息处理是和Eventbus实例有关的, 是无法跨进程传递消息的; 如果涉及到进程间通讯, 还是要使用android系统的接口
对比
- Boardcast
优点: 可以指定运行线程, 消息处理可继承, 代码简单, 消息处理可继承, 低延迟, 对消息数据无要求(不需要实现Parcelable或者Serializable接口)
缺点: 无法跨进程 - LocalBroadcastManager
这个除了广播的低延迟外, Boardcast的缺点都有, 并且它还不能不能跨进程, 没有黏性广播 - RxBus
源码初探
EventBus的源码不多, 这里只讲一下大概, 具体细节大家自己去读源码
源码版本( 66ead83 )
- EventBus.java
此类对外提供所有的接口,register
,unregister
,post
;
提供一个默认的单例对象, 通过getDefault()
获取;
核心的变量如下
/** eventType和消息接收者存储的map, key是event的class, value是接收者的信息, Subscription中包含的接收消息的对象, 处理消息的方法 */
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
/** 消息处理者和其所包含的能处理的event的Map, key为消息处理者的实例, value为其所能处理的event的类型 */
private final Map<Object, List<Class<?>>> typesBySubscriber;
/** sticky event 的存储的Map, key为event的class, value是具体事件的对象, 每种类型的sticky event 最多存储一个 */
private final Map<Class<?>, Object> stickyEvents;
SubscriberMethodFinder.java
用于寻找消息处理者的方法, 里面有一个静态变量Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE
用于保存找到的消息处理这, 加快下一次查找过程HandlerPoster.java
HandlerPoster
本质是一个Handler, 使用主线程的Looper, 可以看一下初始化语句mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10)
; 发送到主线程的消息, 实质都是用此发送一个Message, 然后在Handler#handleMessage中, 调用 Method#invoke(Object, Event);AsyncPoster.java BackgroundPoster.java
名称已经很明显, 处理后台消息的2个类; 本质都是Runnable, 都从消息队列中获取消息, 然后在线程池中执行PendingPostQueue.java
消息存储的地方, 一个简单的链表结构
-
注册流程
register
后, 会通过SubscriberMethodFinder#findSubscriberMethods方法, 查找注册的类, 如果添加注解处理器, 会通过反射去查找; 查找后,将各个对应关系保存在Eventbus实例的成员变量; 并且检测是否有黏性消息, 有黏性消息,则立马执行post流程, 有
ThreadLocal
获取所在线程信息, 然后在Eventbus#subscriptionsByEventType
获取所有的消息处理者, 然后判断处理的线程, 分发到各个Poster去处理