写点什么

EventBus 发送的消息,如何做到线程切换?

用户头像
Android架构
关注
发布于: 1 小时前

链接:https://juejin.im/post/6844903944561377293


二. EventBus 的线程切换



2.1 EventBus 切换线程

EventBus 是一个基于观察者模式的事件订阅/发布框架。利用 EventBus 可以在不同模块之间,实现低耦合的消息通信。



EventBus 诞生以来这么多年,在很多生产项目中都可以看到它的身影。而从更新日志可以看到,除了体积小,它还很稳定,这两年就没更新过,最后一次更新也只是因为支持所有的 JVM,让其使用范围不仅仅局限在 Android 上。


可谓是非常的稳定,稳定到让人有一种感觉,要是你使用 EventBus 出现了什么问题,那一定是你使用的方式不对。


EventBus 的使用方式,对于 Android 老司机来说,必然是不陌生的,相关资料太多,这里就不再赘述了。


在 Android 下,线程的切换是一个很常用而且很必须的操作,EventBus 除了可以订阅和发送消息之外,它还可以指定接受消息处理消息的线程。


也就是说,无论你 post() 消息时处在什么线程中,EventBus 都可以将消息分发到你指定的线程上去,听上去就感觉非常的方便。


不过无论怎么切换,无外乎几种情况:


  • UI 线程切子线程。

  • 子线程切 UI 线程。

  • 子线程切其他子线程。


在我们使用 EventBus 注册消息的时候,可以通过 @Subscribe 注解来完成注册事件, @Subscribe 中可以通过参数 threadMode 来指定使用那个线程来接收消息。


@Subscribe(threadMode = ThreadMode.MAIN)


fun onEventTest(event:TestEvent){


// 处理事件


}


threadMode 是一个 enum,有多种模式可供选择:


  1. POSTING,默认值,那个线程发就是那个线程收。

  2. MAIN,切换至主线程接收事件。

  3. MAIN_ORDERED,v3.1.1 中新增的属性,也是切换至主线程接收事件,但是和 MAIN 有些许区别,后面详细讲。

  4. BACKGROUND,确保在子线程中接收事件。细节就是,如果是主线程发送的消息,会切换到子线程接收,而如果事件本身就是由子线程发出,会直接使用发送事件消息的线程处理消息。

  5. ASYNC,确保在子线程中接收事件,但是和 BACKGROUND 的区别在于,它不会区分发送线程是否是子线程,而是每次都在不同的线程中接收事件。


EventBus 的线程切换,主要涉及的方法就是 EventBus 的 postToSubscription() 方法。


private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {


switch (subscription.subscriberMethod.threadMode) {


case POSTING:


invokeSubscriber(subscription, event);


break;


case MAIN:


if (isMainThread) {


invokeSubscriber(subscription, event);


} else {


mainThreadPoster.enqueue(subscription, event);


}


break;


case MAIN_ORDERED:


if (mainThreadPoster != null) {


mainThreadPoster.enqueue(sub


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


scription, event);


} else {


// temporary: technically not correct as poster not decoupled from subscriber


invokeSubscriber(subscription, event);


}


break;


case BACKGROUND:


if (isMainThread) {


backgroundPoster.enqueue(subscription, event);


} else {


invokeSubscriber(subscription, event);


}


break;


case ASYNC:


asyncPoster.enqueue(subscription, event);


break;


default:


throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);


}


}


可以看到,在 postToSubscription() 方法中,对我们配置的 threadMode 值进行了处理。


这端代码逻辑非常的简单,接下来我们看看它们执行的细节。

2.2 切换至主线程接收事件

想在主线程接收消息,需要配置 threadMode 为 MAIN。


case MAIN:


if (isMainThread) {


invokeSubscriber(subscription, event);


} else {


mainThreadPoster.enqueue(subscription, event);


}


这一段的逻辑很清晰,判断是主线程就直接处理事件,如果是非主线程,就是用 mainThreadPoster 处理事件。


追踪 mainThreadPoster 的代码,具体的逻辑代码都在 HandlerPoster 类中,它实现了 Poster 接口,这就是一个普通的 Handler,只是它的 Looper 使用的是主线程的 「Main Looper」,可以将消息分发到主线程中。


为了提高效率,EventBus 在这里还做了一些小优化,值得我们借鉴学习。



为了避免频繁的向主线程 sendMessage(),EventBus 的做法是在一个消息里尽可能多的处理更多的消息事件,所以使用了 while 循环,持续从消息队列 queue 中获取消息。


同时为了避免长期占有主线程,间隔 10ms (maxMillisInsideHandleMessage = 10ms)会重新发送 sendMessage(),用于让出主线程的执行权,避免造成 UI 卡顿和 ANR。


MAIN 可以确保事件的接收,在主线程中,需要注意的是,如果事件就是在主线程中发送的,则使用 MAIN 会直接执行。为了让开发和可配置的成都更高,在 EventBus v3.1.1 新增了 MAIN_ORDERED,它不会区分当前线程,而是通通使用 mainThreadPoster 来处理,也就是必然会走一遍 Handler 的消息分发。


当事件需要在主线程中处理的时候,要求不能执行耗时操作,这没什么好说的,另外对于 MAIN 或者 MAIN_ORDERED 的选择,就看具体的业务要求了。

2.3 切换至子线程执行

想要让消息在子线程中处理,可以配置 threadMode 为 BACKGROUND 或者 AYSNC,他们都可以实现,但是也有一些区别。


先来看看 BACKGROUND,通过 postToSubscription() 中的逻辑可以看到,BACKGROUND 会区分当前发生事件的线程,是否是主线程,非主线程这直接分发事件,如果是主线程,则 backgroundPoster 来分发事件。


case BACKGROUND:


if (isMainThread) {


backgroundPoster.enqueue(subscription, event);


} else {


invokeSubscriber(subscription, event);


}


break;


BackgroundPoster 也实现了 Poster 接口,其中也维护了一个用链表实现的消息队列 PendingPostQueue,


在一些编码规范里就提到,不要直接创建线程,而是需要使用线程池。EventBus 也遵循这个规范,在 BackgroundPoster 中,就使用了 EventBus 的 executorService 线程池对象去执行。


为了提高效率,EventBus 在处理 BackgroundPoster 时,也有一些小技巧值得我们学习。


可以看到,在 BackgroundPoster 中,处理主线程抛出的事件时,同一时刻只会存在一个线程,去循环从队列中,获取事件处理事件。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
EventBus 发送的消息,如何做到线程切换?