腾讯Android岗三面:EventBus-发送的消息,如何做到线程切换?(1)

@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(subscription, 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 时,也有一些小技巧值得我们学习。

[图片上传失败…(image-eb9c15-1603110903841)]

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

通过 synchronized 同步锁来保证队列数据的线程安全,同时利用 volatile 标识的 executorRunning 来保证不同线程下看到的执行状态是可见的。

既然 BACKGROUND 在处理任务的时候,只会使用一个线程,但是 EventBus 却用到了线程池,看似有点浪费。但是再继续了解 ASYNC 的实现,才知道怎么样是对线程池的充分利用。

和前面介绍的 threadMode 一样,大多数都对应了一个 Poster,而 ASYNC 对应的 Poster 是 AsyncPoster,其中并没有做任何特殊的处理,所有的事件,都是无脑的抛给 EventBus 的 executorService 这个线程池去处理,这也就保证了,无论如何发生事件的线程,和接收事件的线程,必然是不同的,也保证了一定会在子线程中处理事件。

public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}

到这里应该就理解了 BACKGROUNDASYNC ,虽然都可以保证在子线程中接收处理事件,但是内部实现是不同的。

BACKGROUND 同一时间,只会利用一个子线程,来循环从事件队列中获取事件并进行处理,也就是前面的事件的执行效率,会影响后续事件的执行。例如你分发了一个事件,使用的是 BACKGROUND 但是队列前面还有一个耗时操作,那你分发的这个事件,也必须等待队列前面的事件都处理完成才可以继续执行。所以如果你追求执行的效率,立刻马上就要执行的事件,可以使用 ASYNC

那是不是都用 ASYNC 就好了?当然这种一揽子的决定都不会好,具体问题具体分析,ASYNC 也有它自己的问题。

ASYNC 会无脑的向线程池 executorService 发送任务,而这个线程池,如果你不配置的话,默认情况下使用的是 Executors 的 newCachedThreadPool() 创建的。

这里我又要说到编码规范了,不推荐使用 Executors 直接创建线程,之所以这样,其中一个原因在于线程池对任务的拒绝策略newCachedThreadPool 则会无限创建新的线程来执行任务,当任务(事件)过多时,就会出现的 OOM。

这也确实是 EventBus 在使用 ASYNC 时,真实存在的问题。

最后

**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。



《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
1715360566342)]

[外链图片转存中…(img-X8s6A2yz-1715360566342)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值