一、前期基础知识储备
为了简化并且更加高质量的地在Activity、Fragment、Service和Thread等之间的通信,同时解决组件之间高耦合的同时仍能继续高效地通信,事件总线设计出现了。可用于替代Intent、Handler、BroadCast进行消息传递。
EventBus是一款针对Android优化的发布-订阅事件总线。它简化了应用程序内各组件间、组件与后台线程间的通信。其优点是开销小、代码更优雅以及将发送者与接收者解耦。如果Activity和Activity进行交互还比较容易实现,但如果是Fragment与Activity、Fragment与Fragment之间交互则有些麻烦,直接使用广播进行处理会有效率的问题。如果传递的数据是实体类,需要进行序列化,那么传递成本就会增加。本文分析基于EventBus3.0版本。
1)EventBus - 理解3个要素
- Event:事件。可以是任意类型的对象;
- Subscriber:事件订阅者。在EventBus3.0之后,事件订阅者处理事件的方法可以按需求命名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING),表示在对应线程中处理事件;
- Publisher:事件发布者。可以在任意线程任意位置发送事件,直接调用EventBus的post(Object)方法。可以自己实例化EventBus对象,但一般使用EventBus.getDefault()来实例化EventBus对象。根据post函数参数的类型,会自动调用订阅相应类型事件的函数。
三者关系如图示:onEvent() 代表事件订阅者定义的处理事件的方法。
2)EventBus - 掌握4种ThreadMode(线程模型)
- POSTING:执行 invokeSubscriber() 方法,就是直接反射调用。事件在哪个线程发布出来的,事件处理函数就会在哪个线程中运行,也就是发布事件和处理事件在同一个线程中。在该线程模型中避免执行耗时操作,会阻塞事件的传递,甚至会引起ANR错误;
- MAIN:首先去判断当前是否在 UI 线程,如果是的话则直接反射调用,否则调用mainThreadPoster#enqueue(),即把当前的方法加入到队列之中,然后通过 handler 去发送一个消息,在 handler 的 handleMessage 中去执行方法。具体逻辑在 HandlerPoster.java 中;
- BACKGROUND:判断当前是否在 UI 线程,如果不是的话直接反射调用,是的话通过backgroundPoster.enqueue() 将方法加入到后台的一个队列,最后通过线程池去执行,在该线程模型中避免执行UI操作;
- ASYNC:与 BACKGROUND 的逻辑类似,将任务加入到后台的一个队列,最终由Eventbus 中的一个线程池去调用,这里的线程池与 BACKGROUND 逻辑中的线程池用的是同一个。
注:BACKGROUND 和 ASYNC 有什么区别呢?
BACKGROUND 中的任务是一个接着一个的去调用,而 ASYNC 则会即时异步运行,具体的可以对比 AsyncPoster.java 和 BackgroundPoster.java 两者代码实现的区别。
3)EventBus - 学习5个使用步骤
①自定义一个事件类(实体类);
public class MessageEvent {
...
}
②在需要订阅事件的地方注册事件;
// this 代表上下文环境
EventBus.getDefault().register(this)
③在订阅事件的地方处理事件;
// 处理事件的方法按照需求命名 接收参数为 Event事件类
@ Subscribe (threadMode = ThreadMode.MAIN)
public void XXXXXX(Messagement messageEvent) {
...
}
④事件发布者发布事件;
// 获取EventBus实例 post()方法接收Event事件类 发送事件
EventBus.getDefault().post(messageEvent);
⑤在订阅事件的地方取消事件订阅;
EventBus.getDefault().unregister(this);
二、上代码,具体实现
1)添加依赖库;
配置app下的gradle,如下所示:
implementation 'org.greenrobot:eventbus:3.0.0'
2)定义事件类;
public class TestEvent {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3)MainActivity 注册和取消订阅事件;
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text);
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
EventBus.getDefault().unregister(this);
super.onDestroy();
}
}
4)MainActivity 处理订阅事件;
@Subscribe(threadMode = ThreadMode.MAIN)
public void onTestEvent(TestEvent event) {
textView.setText(event.getMsg());
}
5)事件发布者发布事件;
public class SecondActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
TestEvent event = new TestEvent();
event.setMsg("已接收到事件!");
EventBus.getDefault().post(event);
finish();
}
});
}
}
点击按钮,效果如下:
6)别忘了,使用EventBus 需要添加混淆规则;
# EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
注:补充其他常用的需要额外添加依赖规则的第三方库:Gson、Butterknife、SlidingMenu、ImageLoader、Okhttputils、Glide、picasso、okhttp、okio、EventBus、dagger、rxJava、retrofit2、greendao、fresco、crashlytics、fastjson、litepal。
三、EventBus黏性事件
上面的实例为使用EventBus实现普通事件,除此之外,EventBus还支持发送黏性事件,就是在发送事件之后再订阅该事件也能够接收到该事件,这个机制和黏性广播类似。我们修改下之前的代码:
1)发送黏性事件 - 使用postSticky();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
TestEvent event = new TestEvent();
event.setMsg("已接收到事件!");
EventBus.getDefault().postSticky(event);
finish();
}
});
2)订阅者处理黏性事件;
@Subscribe(threadMode = ThreadMode.POSTING, sticky = true)
public void onTestEvent(TestEvent event) {
textView.setText(event.getMsg());
}
这样改造之后,使用EventBus处理黏性事件,即使事件订阅发生在事件发送之后,事件订阅者仍能接收到发送的消息。
注:黏性事件会保存在内存中,每次进入都会去内存中查找获取最新的黏性事件,除非手动解除黏性事件。方法如下:
// 移除指定的粘性事件
removeStickyEvent(Object event);
// 移除指定类型的粘性事件
removeStickyEvent(Class<T> eventType);
// 移除所有的粘性事件
removeAllStickyEvents();
修改事件处理的代码如下:
@Subscribe(threadMode = ThreadMode.POSTING, sticky = true)
public void onTestEvent(TestEvent event) {
textView.setText(event.getMsg());
// 移除所有粘性事件
EventBus.getDefault().removeStickyEvent(event);
}
在执行玩事件处理的操作之后,将黏性事件解除。
四、EventBus 定义事件处理的优先级
EventBus支持在定义订阅者处理事件的方法时,指定事件传递的优先级。默认情况下,订阅者方法的事件传递优先级为0。数值越大,优先级越高。在相同的线程模式下,更高优先级的订阅者方法将优先接收到事件。注意:优先级只有在相同的线程模式下才有效。
指定事件传递优先级的示例代码如下所示:
@Subscribe(priority = 1)
public void onMessageEvent(MessageEvent event) {
...
}
可以在高优先级的订阅者方法接收到事件之后取消事件的传递。此时,低优先级的订阅者方法将不会接收到该事件。注意: 订阅者方法只有在线程模式为ThreadMode.POSTING时,才可以取消一个事件的传递。取消事件传递的示例代码如下所示:
@Subscribe(threadMode = ThreadMode.POSTING, priority = 1)
public void onMessageEvent(MessageEvent event) {
...
// 取消事件传递
EventBus.getDefault().cancelEventDelivery(event);
}
举例说明:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initContentView();
// 注册订阅者
EventBus.getDefault().register(this);
}
private void initContentView() {
findViewById(R.id.btn_main_start_activity).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_main_start_activity) {
SecondActivity.start(this);
}
}
@Subscribe(threadMode = ThreadMode.POSTING, priority = 1)
public void onMessageEvent1(MessageEvent event) {
Log.i(TAG, "onMessageEvent1(), message is " + event.getMessage());
}
@Subscribe(threadMode = ThreadMode.POSTING, priority = 2)
public void onMessageEvent2(MessageEvent event) {
Log.i(TAG, "onMessageEvent2(), message is " + event.getMessage());
// 取消事件
EventBus.getDefault().cancelEventDelivery(event);
}
@Subscribe(threadMode = ThreadMode.POSTING, priority = 3)
public void onMessageEvent3(MessageEvent event) {
Log.i(TAG, "onMessageEvent3(), message is " + event.getMessage());
}
@Subscribe(threadMode = ThreadMode.POSTING, priority = 4)
public void onMessageEvent4(MessageEvent event) {
Log.i(TAG, "onMessageEvent4(), message is " + event.getMessage());
}
@Subscribe(threadMode = ThreadMode.POSTING, priority = 5)
public void onMessageEvent5(MessageEvent event) {
Log.i(TAG, "onMessageEvent5(), message is " + event.getMessage());
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销订阅者
EventBus.getDefault().unregister(this);
}
}
MainActivity订阅了MessageEvent事件,定义了5个不同优先级的订阅者方法。当接收到MessageEvent事件时,订阅者方法将打印日志消息。优先级为2的订阅者方法在接收到事件之后取消了事件的传递。
事件发布者发送事件之后,打印log如下:
MainActivity: onMessageEvent5(), message is Hello EventBus!
MainActivity: onMessageEvent4(), message is Hello EventBus!
MainActivity: onMessageEvent3(), message is Hello EventBus!
MainActivity: onMessageEvent2(), message is Hello EventBus!
接收事件的顺序,按照优先级定义的,依次是5、4、3、2,事件在2中取消之后,没有发送到1。