EventBus源码赏析一 —— 基本使用
Posted 上马定江山
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EventBus源码赏析一 —— 基本使用相关的知识,希望对你有一定的参考价值。
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
就可以了。
以上是关于EventBus源码赏析一 —— 基本使用的主要内容,如果未能解决你的问题,请参考以下文章