每天记录学习的新知识:EventBus 使用

查阅自 刘望舒《Android进阶之光》
WngShhng Android EventBus 的使用: https://www.jianshu.com/p/e7d5c7bda783
骑猪撞交警 EventBus的粘性事件: https://www.cnblogs.com/fuyaozhishang/p/7968059.html


Function:

每天记录学习的新知识:EventBus 使用

EventBus是一种用于Android的事件发布-订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题(序列化),可以避免由于使用广播通信而带来的诸多不便。

官方注解:EventBus能够简化各组件间的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思),能避免复杂和容易出错的依赖性和生命周期问题。


Element:

E.1. 三要素:

Event 事件。它可以是任意类型。

Subscriber 事件订阅者。在EventBus3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe(),并且指定线程模型,默认是POSTING。

Publisher 事件的发布者。我们可以在任意线程里发布事件,一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。

E.2. 五种线程模型:

POSTING,默认模式,事件处理函数的线程跟发布事件的线程在同一个线程
订阅者将直接在和发布事件同一个线程中被调用。事件传递是同步完成的,所有订阅者一旦发布完成就被调用,该模式下传递事件开销最小,因为它完全避免了线程切换。因此这是简单任务下推荐的模式,比如已知可以在很短的时间内完成,而不需要主线程。使用此模式的事件处理程序必须快速返回以避免阻塞发布线程(可能是主线程)。

MAIN,UI主线程
在Android上,订阅者将在Android的主线程(UI线程)中被调用。如果发布事件的线程是主线程,则直接调用订阅者方法,阻塞发布线程。否则,事件排队等待传送(非阻塞)。使用这种模式的订阅者必须快速返回以避免阻塞主线程(时间过长会导致ANR)。如果不在Android上,则表现与POSTING相同。

MAIN_ORDERED,UI线程 排队等待传送
在Android上,订阅者将在Android的主线程(UI线程)中被调用。与MAIN不同,该事件将始终排队等待传送。这确保了推送事件是非阻塞的。例如,如果使用main线程模式在事件处理程序中发布另一个事件,则第二个事件处理程序将在第一个事件处理程序之前完成(因为它被同步调用 - 将其与方法调用进行比较)。使用MAIN_ORDERED,第一个事件处理程序将完成,然后第二个将在稍后的时间点被调用(只要主线程有能力)。

BACKGROUND,线程在后台线程,因此不能进行UI操作
订阅者将在后台线程中被调用。如果发布线程不是主线程,则会在发布线程中直接调用事件处理程序方法。如果发布线程是主线程,则EventBus将使用一个后台线程来按顺序发送所有事件。使用这种模式的事件处理程序应该尽快返回以避免阻塞后台线程。

ASYNC,新建一个子线程运行,同样不能进行UI操作
订阅者将在一个单独的线程中被调用。这总是独立于发布线程和主线程。发布事件永远不会等待使用此模式的订户方法。如果用户方法的执行可能需要一些时间,则它们应该使用这种模式。用于网络访问。避免同时触发大量长时间运行的异步订阅者方法来限制并发线程的数量。 EventBus使用线程池来有效地重用已完成的异步订户通知中的线程。


Usage method:基本用法

U.1. 添加依赖

 implementation 'org.greenrobot:eventbus:3.1.1'

U.2. 定义事件

事件可以是任意普通的Java对象(POJO),没有任何特殊的要求。

public class MessageEvent {

    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

U.3. 订阅事件
订阅者需要定义事件处理方法(也称为订阅者方法)。当发布对应类型的事件时,该方法将被调用。EventBus 3使用 @Subscribe 注解来定义订阅者方法。方法名可以是任意合法的方法名,参数类型为订阅事件的类型。EnvestBUS 2中没有命名约定。

// 当发布消息事件时,将调用此方法(在ToST的UI线程中)
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
 
// 当发布某个事件时,将调用此方法。
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}

订阅者还需要在总线上注册,并在不需要时在总线上注销。只有订阅者注册了,它们才会收到事件。

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}
 
@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

U.4. 发布事件

在需要的地方发布事件,所有订阅了该类型事件并已注册的订阅者将收到该事件。

EventBus.getDefault().post(new MessageEvent(" Hello !!!"));

U.5. 混淆规则

#EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
 
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
 
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}

混淆异常:注意包名 org.greenrobot.eventbus 还是其他依赖


Usage method:黏性事件

拓展 何为黏性事件呢??

简单讲,就是在发送事件之后再订阅该事件也能收到该事件。Android中就有这样的实例,也就是Sticky Broadcast,即粘性广播。正常情况下如果发送者发送了某个广播,而接收者在这个广播发送后才注册自己的Receiver,这时接收者便无法接收到 刚才的广播,为此Android引入了StickyBroadcast,在广播发送结束后会保存刚刚发送的广播(Intent),这样当接收者注册完 Receiver后就可以接收到刚才已经发布的广播。这就使得我们可以预先处理一些事件,让有消费者时再把这些事件投递给消费者.

EventBus也提供了这样的功能,有所不同是EventBus会存储所有的Sticky事件,如果某个事件在不需要再存储则需要手动进行移除。用户通过Sticky的形式发布事件,而消费者也需要通过Sticky的形式进行注册,当然这种注册除了可以接收 Sticky事件之外和常规的注册功能是一样的,其他类型的事件也会被正常处理。

黏性事件和普通事件的使用有两点区别:
Um.1. 粘性事件的发布

EventBus.getDefault().postSticky(new MessageEvent(" Hello !!!"));

Um.2. 粘性事件的接收

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

手动获取和移除粘性事件(Getting and Removing sticky Events manually)
Um.3. 移除粘性事件

MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
if(stickyEvent != null) {
    EventBus.getDefault().removeStickyEvent(stickyEvent);
  //todo
}

Um.4. 移除粘性事件
removeStickyEvent 会返回之前持有的粘性事件。

MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
if(stickyEvent != null) {
  //todo
}