前言
EventBus是一种用于Android/Java的发布/订阅事件总线。可用于各个组件、模块、多线程间通信(不涉及多进程)。开发中经常会选择使用它来进行模块间通信、解耦、线程切换,而且实现编写代码简单,下面也是主要通过这几点进行介绍使用和源码解读,嗯,上个目录先:
- 使用详解
- 准备工作
- 使用介绍
- 运行时使用方式(EventBus3.0之前)
- 编译时使用方式(EventBus3.0之后新增)
- 源码解读
使用详解
先上一张官方的关于EventBus的工作模式
EventBus库中最重要的三个点,分别是subscriber(订阅者),事件(消息),publisher(发布者)。主要理解这三者的关系即可。
- subscriber ——> EventBus 的register方法,传入的object对象
- 事件(Event)——> EventBus 的post方法,传入的类型。
- publisher(发布者)——> EventBus的post方法。
以上概念(register、post)不懂没关系,先有个认知,看完后面的慢慢细说就懂了
准备工作
首先,在模块的 build.gradle 构建脚本中添加EventBus依赖:
//使用编译时需要添加(运行时不需要)
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
//编译时给AbstractProcessor继承者传递的参数(也就是EventBusAnnotationProcessor),自动生成对应的MyEventBusIndex类
arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
}
}
}
}
dependencies {
implementation 'org.greenrobot:eventbus:3.2.0'
//使用编译时需要添加(运行时不需要)
annotationProcessor "org.greenrobot:eventbus-annotation-processor:3.2.0"
}
接着,添加EventBus混淆规则(忘加了,一般测试环境都ok,就怕生产环境不生效,凉凉)
#EventBus
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# And if you use AsyncExecutor:
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
使用介绍
EventBus运行时(反射)的使用非常简单,三部曲:(与编译时对比,效率会慢些,但占用内存空间小些,为什么后面说明)
-
定义事件
public static class MessageEvent { /* 如果需要添加对应的字段 */ }
-
订阅事件
//1.定义处理事件的线程(默认与发布者同一线程),如下表格说明几种处理模式 //2.定义是否是粘性事件(默认不是),粘性与非粘性事件:指的是发布事件者与订阅事件者的时间前后关系。粘性事件:订阅者能够在发布者发布事件时间的前或者后进行订阅事件,都能够收到事件;非粘性事件:只能够在发布者发布事件前进行订阅事件才能够收到事件 //3.优先级 数字越大优先级越高 @Subscribe(threadMode = ThreadMode.MAIN,sticky = true,priority = 10) public void onMessageEvent(MessageEvent event) {/* 接受事件的逻辑处理 */};
注意:
对如上接受处理事件方法有几点要求:- 该方法有且只有一个参数
- 该方法必须是public修饰符修饰,不能用static关键字修饰,不能是抽象的(abstract)
线程模式 说明 POSTING 默认模式,事件的发布和事件的接收处理在同一个线程 MAIN 不管是事件发布在哪个线程上,事件的处理都会在UI线程中执行 MAIN_ORDERED 跟MAIN的区别是,发布者事件将放置队列中排队等待传递,确保post调用者不堵塞(具体后续源码分析时说明) BACKGROUND 如果在主线程中发布消息,那么就new一个子线程来接收消息。如果是在子线程中发布消息,那么就会直接用该子线程来接收消息 ASYNC 无论你在那个线程中发布消息都会在不同的线程中接受消息 注册与反注册
@Override public void onCreate() {//也可在onStart中进行注册 super.onCreate(); EventBus.getDefault().register(this); } //也可在onStop()中进行反注册,主要看你是想在页面不可见时不需要接受收到事件还是说该页面没被销毁都需要接受事件 @Override public void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); }
-
发布事件
EventBus.getDefault().post(new MessageEvent());
EventBus的编译时使用,只是在运行时的三部曲之前添加一些前奏而已:(与运行时对比,效率高,但占用内存空间会大小,因为运行的时候会把编译时所有的类进行加载,具体详看MyEventBusIndex
类)
-
在
Application
类中添加索引类(该索引类在上面准备工作时在build.gradle中添加的参数配置MyEventBusIndex
)且设置.ignoreGeneratedIndex(false)
这个很重要不然还是会走到反射模式即还是编译运行时,如下:EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).ignoreGeneratedIndex(false).build(); 或者 EventBus.builder().addIndex(new MyEventBusIndex()).ignoreGeneratedIndex(false).installDefaultEventBus();
因为是编译时自动生成的
MyEventBusIndex
,所以在添加完上述代码需要重新编译下,不然找不到类会报错。 -
如果需要在多个模块下使用编译时EventBus,则需要在对应模块下也添加对应的build.gradle配置参数,如:
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ eventBusIndex : 'com.example.myLib.MyEventBusLibIndex' ] } } } }
然后在
Application
修为如下:(也可以在用到某模块的时候进行添加索引addIndex
,避免一次加载所有的类)EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).addIndex(new MyEventBusLibIndex()).ignoreGeneratedIndex(false).build(); 或者 EventBus.builder().addIndex(new MyEventBusIndex()).addIndex(new MyEventBusLibIndex()).ignoreGeneratedIndex(false).installDefaultEventBus();
这里所说的编译时,指的是方法的构建索引,其实调用函数最终的地方还是通过反射来实现的,具体下面从源码解读来说明
源码解读
看完如上使用,差不多大致有一个全面的认识,下面开始是分析下源码,让你知其然,不觉得踩空的感觉。在分析源码前,先上一个源码的类视图,让大家更加直观和全貌的感知。下面说下源码的几个核心类和设计思维:
1.EventBus
:整个框架的入口也是调用者使用的入口,内部使用EventBusBuilder
这个类作为参数配置的建造者设计模式。下面列举这个类中几个比较重要的数据结构
/**
* 存储所有事件类型对应的List数组对象
* CopyOnWriteArrayList<Subscription>表示整个项目中订阅同一事件的集合
*/
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
/**
* 存储所有注册对象对应订阅的事件数组对象
* List<Class<?>>表示注册对象中包含有哪些订阅事件
*/
private final Map<Object, List<Class<?>>> typesBySubscriber;
//存储粘性事件
private final Map<Class<?>, Object> stickyEvents;
//存储当前事件的状态,可以调用中断事件
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>()
2.PendingPostQueue
:是框架定义事件存储的数据结构模型,内部是一个PendingPost
队列,而PendingPost
对象内部是包含Subscription
对象,Subscription
对象内部是包含SubscriberMethod
对象,SubscriberMethod
是最终定义处理事件的反射方法名、线程模型、事件类型、优先级、是否粘性事件的数据模型
3.Poster
:线程模型处理模式类,包含主线程模型类HandlePoster
、后台线程模型BackgroundPoster
、子线程模型AsyncPoster
首先,说下EventBus源码如何阅读,我们阅读的入口点是从EventBus.getDefault().register(this)
的注册方法作为突破口,这一步其实是对使用了注解@Subscribe
的数据准备。下一步的入口点是EventBus.getDefault().post(EventObj)
,其实就是数据的查找以及调用。上一个调用触发流程图,具体细节就不一一展开说明了,都比较简单,主要还是要理清调用的脉络。至于编译时注解怎么生成的可以参见我另一篇文章,生成方式的原理都是一样的(Android APT注解扫盲)
参考链接