EventBus简介
EventBus是一种用于Android的发布/订阅事件总线。我们经常用来在不同界面,不同线程传递数据,它解耦了事件发送方和事件处理方。 虽然Android本身提供了LocalBroadcastReceiver类可以实现类似的功能,但是LocalBroadcastReceiver使用起来稍微繁琐,而且传递数据大小也受intent限制。
EventBus角色
EventBus主要有一下三个角色:
Event事件,可以是任意引用类型,不能是基础数据类型如int,char。他是EventBus传递,处理的对象。Subscriber事件订阅者,订阅者必须有@Subscribe注解的订阅方法,用来处理事件。Publisher事件发布者,可以在任意线程发布事件。事件订阅者收到后会进行处理。
基本使用
添加依赖
implementation 'org.greenrobot:eventbus:3.3.1'定义事件
public class MessageEvent {}这一步不是必须的,因为事件可以是任意引用类型,所以可以使用一些已经存在的类作为事件类,比如String
注册/反注册
// 注册 protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); EventBus.getDefault().register(this); } // 反注册 protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); }注册完了之后必须反注册,否则造成内存泄漏,在没有反注册之前,不能重复注册,不然会抛出异常。
事件处理
@Subscribe(threadMode = ThreadMode.MAIN,priority=1,sticky=true) public void handleMessage(MessageEvent event) { Log.i("EventBusTest", event.toString()); }在上一步注册的类里面添加一个方法,用于事件处理,方法名任意,但是只能有一个参数,而且修饰符必须是public的,不能使用static,abstract修饰,然后使用@Subscribe注解。@Subscribe提供了一些可选参数让我们在不同场景选用。
事件发送
EventBus.getDefault().post(new MessageEvent());在需要发送事件的地方调用Eventbus的post()方法发送事件。
@Subscribe参数详解
@Subscribe有3个参数,threadMode用于指定线程模型,sticky指定是否处理粘性事件,priority指定优先级。
threadMode
EventBus允许事件发送的线程和事件处理的线程不一样,我们可以通过threadMode指定,ThreadMode是一个枚举类型,有以下五种取值:
POSTING订阅者的订阅方法将在发布事件的同一线程中被调用,这种模式下不会涉及线程切换,所以开销比较小,但是可能发布事件的线程是主线程,所以需要避免在订阅方法中处理耗时操作MAIN订阅方法将在UI线程被调用,如果发布事件的线程也是UI线程,则可以直接执行调用订阅方法,否则会使用Handler切换到UI线程MAIN_ORDERED与MAIN类似,只不过不管发布线程在不在UI线程,都会由Handler处理,这确保了post()的调用是非阻塞的BACKGROUND订阅方法将在非UI线程被调用,如果发布事件的线程在UI线程,则会创建一个线程来执行订阅方法,否则直接执行,为避免后续事件的发送或调用,需要避免在订阅方法中处理耗时操作ASYNC订阅方法的调用线程将独立于发送事件的线程,所以这种情况下可以做耗时操作,但是需要避免在同一时间进行大量的异步订阅,控制并发线程的数量。
ThreadMode.POSTING
事件发送
new Thread(() -> EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName()))).start();在发布线程处理事件
@Subscribe(threadMode = ThreadMode.POSTING) public void postingButton(MessageEvent event) { Log.i("EventBusTest", "事件发送线程:" + event.name + ",事件处理线程:" + Thread.currentThread().getName()); }结果
EventBusTest: 事件发送线程:Thread-7,事件处理线程:Thread-7可以看出订阅者的订阅方法将在发布事件的同一线程中被调用。
ThreadMode.MAIN
事件发送
long start = System.currentTimeMillis(); EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName())); long end = System.currentTimeMillis(); Log.i("EventBusTest", "间隔 " + (end - start) + "ms 才后发送下一个事件"); new Thread(() -> EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName()))).start();处理事件
@Subscribe(threadMode = ThreadMode.MAIN) public void mainButton(MessageEvent event) { Log.i("EventBusTest", "事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName()); SystemClock.sleep(1000); }结果输出
EventBusTest: 事件发送线程: main,事件处理线程: main EventBusTest: 间隔 1001ms 才后发送下一个事件 EventBusTest: 事件发送线程: Thread-7,事件处理线程: main可以看出,即使发送线程不在主线程,最后处理的时候也会切换到主线程,而且因为处理事件的方法做了耗时操作,所以隔了1s多才发送第二个事件。
ThreadMode.MAIN_ORDERED
事件发送与上面一样,处理事件的方法调整下,将threadMode设置为ThreadMode.MAIN_ORDERED
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED) public void mainOrderButton(MessageEvent event) { Log.i("EventBusTest", "事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName()); SystemClock.sleep(1000); }结果输出
EventBusTest: 间隔 0ms 才后发送下一个事件 EventBusTest: 事件发送线程: main,事件处理线程: main EventBusTest: 事件发送线程: Thread-8,事件处理线程: main可以看出MAIN_ORDERED与MAIN相似的是都是切换到MAIN线程处理事件,不一样的是ThreadMode.MAIN这种模型下,前一个事件处理可能会阻塞下一个事件的发送,而ThreadMode.MAIN_ORDERED不会
这里说的是“可能阻塞”,至于会不会阻塞取决于事件的发送线程,如果一个事件在非主线程发送,结果其实与MAIN_ORDERED一样,不会阻塞,如果是在主线程发送,线程模型又是指定的ThreadMode.MAIN,则会先立即执行处理此事件的方法,等到方法执行完毕,才会发送下一个事件。
ThreadMode.BACKGROUND
事件发送
new Thread(() -> { long start = System.currentTimeMillis(); EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName(), start)); long end = System.currentTimeMillis(); Log.i("EventBusTest", "子线程发送耗时 " + (end - start) + "ms"); }).start(); //保证上下两种情况互不干扰 SystemClock.sleep(2000); long start = System.currentTimeMillis(); EventBus.getDefault().post(new MessageEvent(Thread.currentThread().getName(), System.currentTimeMillis())); long end = System.currentTimeMillis(); Log.i("EventBusTest", "主线程发送耗时 " + (end - start) + "ms");这里在非MAIN线程和MAIN线程都分别发送了事件,主要是为了体现发布线程对结果的影响。
事件处理
@Subscribe(threadMode = ThreadMode.BACKGROUND,priority = 1) public void bgButton(MessageEvent event) { long time = System.currentTimeMillis() - event.sendTime; Log.i("EventBusTest", "优先级1:事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName() + ",发送" + time + " m后才开始处理"); SystemClock.sleep(500); } @Subscribe(threadMode = ThreadMode.BACKGROUND,priority = 2) public void bgButton1(MessageEvent event) { long time = System.currentTimeMillis() - event.sendTime; Log.i("EventBusTest", "优先级2:事件发送线程: " + event.name + ",事件处理线程: " + Thread.currentThread().getName() + ",发送" + time + " m后才开始处理"); SystemClock.sleep(500); }结果输出
EventBusTest: 优先级2:事件发送线程: Thread-7,事件处理线程: Thread-7 发送1 m后才开始处理 EventBusTest: 优先级1:事件发送线程: Thread-7,事件处理线程: Thread-7 发送501 m后才开始处理 EventBusTest: 子线程发送耗时 1002ms EventBusTest: 主线程发送耗时 4ms EventBusTest: 优先级2:事件发送线程: main,事件处理线程: pool-3-thread-1 发送4 m后才开始处理 EventBusTest: 优先级1:事件发送线程: main,事件处理线程: pool-3-thread-1 发送506 m后才开始处理从日志可以看出
ThreadMode.BACKGROUND线程模型下如果发布线程不是MAIN线程,则事件处理是在当前线程进行,而且同一个线程内是串行执行的,上一个执行完了才会执行下一个。- 如果发布线程是
MAIN线程,则事件处理线程会切换到非MAIN线程,而且事件处理不会阻塞MAIN线程,同一个线程内也是串行执行。
ThreadMode.ASYNC
事件发送与上面一致,事件处理方法体也一样,只是修改下threadMode
@Subscribe(threadMode = ThreadMode.ASYNC)结果输出
EventBusTest: 子线程发送耗时 1ms 2EventBusTest: 事件发送线程: Thread-7,事件处理线程: pool-3-thread-1 发送0 m后才开始处理 EventBusTest: 事件发送线程: Thread-7,事件处理线程: pool-3-thread-2 发送0 m后才开始处理 EventBusTest: 主线程发送耗时 2ms EventBusTest: 事件发送线程: main,事件处理线程: pool-3-thread-1 发送1 m后才开始处理 EventBusTest: 事件发送线程: main,事件处理线程: pool-3-thread-2 发送0 m后才开始处理与ThreadMode.BACKGROUND相似的是,事件处理线程都不会是MAIN线程,不一样的是即使ThreadMode.ASYNC是在非MAIN线程发送的事件,处理事件的线程也不会是原线程,而且事件处理是异步的,不会阻塞原线程,多个事件处理方法也是并行执行。
sticky
粘性事件是指发送了事件之后在注册订阅者,订阅者依然能收到事件。
需要注意的是粘性事件是以参数类型来缓存的,同一类型的事件只会保留最近的一个。
priority
EventBus可以指定事件处理方法的优先级。默认情况下为0。数值越大优先级越高,优先级只有在相同的线程模式下才有意义。
首先我们发送一个事件
MessageEvent event = new MessageEvent(Thread.currentThread().getName()); event.info = "项目100天完成"; EventBus.getDefault().post(event);然后定义三个不同优先级的事件处理方法
@Subscribe(priority = 0,threadMode = ThreadMode.POSTING) public void priorityButton0(MessageEvent event) { Log.i("EventBusTest","码农收到消息: "+event.info); } @Subscribe(priority = 1,threadMode = ThreadMode.POSTING) public void priorityButton1(MessageEvent event) { Log.i("EventBusTest","经理收到消息: "+event.info); EventBus.getDefault().cancelEventDelivery(event); } @Subscribe(priority = 2,threadMode = ThreadMode.POSTING) public void priorityButton2(MessageEvent event) { Log.i("EventBusTest","主管收到消息: "+event.info); event.info = "项目10天完成"; }既然事件是按照优先级处理的,而且处理的时候能拿到事件本身,那么高优先级的就可以对事件进行修改和取消,所以结果输出如下:
EventBusTest: 主管收到消息: 项目100天完成 EventBusTest: 经理收到消息: 项目10天完成优先级为2的先收到原始事件,他处理完成后对事件进行了修改,优先级为1的收到的就是他修改之后的事件,在优先级为1的方法处理完成之后将事件取消,导致优先级为0的就收不到事件了。
需要注意的是事件的取消是有限制的
- 它的线程模型必须是
ThreadMode.POSTING。 - 取消的事件不能为
null并且必须和发送中的事件一样。 - 只有在事件发送中才能取消,否则事件都发送完了,取消了个寂寞。
订阅者索引
经过上面的使用我们基本掌握了EventBus常见用法,但是这并没有享受到EventBus3.x给我们带来的性能提升。
我们知道EventBus3.0之后就引入了编译时注解,避免了反射查找订阅方法带来的性能损耗。虽然上面我们引入的是3.3.1的包,但是查找订阅方法使用的依然是反射。虽然默认情况下EventBus3.x会优先使用编译时注解,但是在上面的使用过程中我们并没有提供订阅者索引,导致最后降级为反射查找。
要想享受到性能提升,首先需要配置索引类的信息
android { defaultConfig { kapt { arguments { arg("eventBusIndex","com.example.test.MyEventBusIndex") } } } } dependencies { kapt 'org.greenrobot:eventbus-annotation-processor:3.3.1' }在build之后,EventBus会生成MyEventBusIndex文件。里面包含了我们的订阅方法的信息。
然后通过EventBusBuilder配置索引类
EventBus eventBus= EventBus.builder().addIndex(new MyEventBusIndex()).build()虽然通过EventBusBuilder#buildr()可以获取EventBus实例,但是我们在平时使用更多的是EventBus.getDefault()获取,为了让getDefault()方法获取的实例设置了索引类,我们需要使用installDefaultEventBus()方法初始化默认实例defaultInstance。
installDefaultEventBus()只能在defaultInstance为null的时候调用,所以一般在Application#onCreate()中调用。
事件继承
默认情况下,发送一个事件,该事件类父类的订阅方法也能得到调用,如果想避免这种情况,只需要调用EventBusBuilder#eventInheritance()方法将eventInheritance属性设置为false就可以了。