事件总线设计——EventBus3.0精炼详解

一、前期基础知识储备

为了简化并且更加高质量的地在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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值