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

EventBus是一个机遇观察者模式的时间订阅/发布框架,利用EventBus可以在不同模块之间,实现低耦合的消息通信。EventBus使用简单且稳定,被广泛用在一些生产项目中。

通常我们使用EventBus分发一些消息给消息的订阅者,除此之外我们还可以通过EventBus将消息传递到不同的线程中去执行,处理消息。

本文结合源码分析EventBus切换线程的原理。

EventBus切换线程

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

线程切换,大概有一些几种情况:

  • UI线程切子线程
  • 子线程切UI线程
  • 子线程切其他线程

在使用EventBus订阅消息的时候,使用@Subscribe来订阅消息,@Subscribe 中可以通过参数 threadMode 来指定使用那个线程来接收消息。

  @Subscribe(sticky = true,threadMode = ThreadMode.MAIN)
    public void onTestEvent(TestEvent event) {
        if(event!=null){
           //处理消息
        }
    }

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);
        }
    }

代码比较简单,下面分别讲解如何切换到主线程和如何切换到子线程中执行。

线程切换至主线程

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

 			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;

这里先分析下threadMode为MAIN的情况,判断是主线程就直接处理事件,如果是非主线程,则通过mainThreadPoster来处理事件。
追踪mainThreadPoster发现:

EventBus(EventBusBuilder builder) {
...
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
...
}

mainThreadPoster通过mainThreadSupport的createPoster方法创建。
EventBusBuilder的getMainThreadSupport()方法如下:

MainThreadSupport getMainThreadSupport() {
        if (mainThreadSupport != null) {
            return mainThreadSupport;
        } else if (Logger.AndroidLogger.isAndroidLogAvailable()) {
            Object looperOrNull = getAndroidMainLooperOrNull();
            return looperOrNull == null ? null :
                    new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
        } else {
            return null;
        }
    }

    Object getAndroidMainLooperOrNull() {
        try {
            return Looper.getMainLooper();
        } catch (RuntimeException e) {
            // Not really a functional Android (e.g. "Stub!" maven dependencies)
            return null;
        }
    }

可以看到这里通过主线程的Looper:Looper.getMainLooper()来创建了一个MainThreadSupport.AndroidHandlerMainThreadSupport。

 class AndroidHandlerMainThreadSupport implements MainThreadSupport {

        private final Looper looper;

        public AndroidHandlerMainThreadSupport(Looper looper) {
            this.looper = looper;
        }

        @Override
        public boolean isMainThread() {
            return looper == Looper.myLooper();
        }

        @Override
        public Poster createPoster(EventBus eventBus) {
            return new HandlerPoster(eventBus, looper, 10);
        }
    }

其中的createPoster通过传入EventBus和looper来创建一个HandlerPoster,所以最终的逻辑是在HandlerPoster中进行。

public class HandlerPoster extends Handler implements Poster {

    private final PendingPostQueue queue;
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    private boolean handlerActive;

    protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            //循环处理消息事件,避免重复sendMessage()
            while (true) {
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                //避免长期占用主线程,间隔10ms重新sendMassage()
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
}

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

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

threadMode为MAIN时,可以确保时间的接收在主线程中,如果事件是在主线程呢个中发送的,则直接执行,否则使用mainThreadPoster来执行。在EventBus v3.1.1 新增了 MAIN_ORDERED,它不会区分当前线程,而是通通使用mainThreadPoster来处理,也就是必然会走一遍Handler的消息分发。

需要注意的是,在主线程中处理事件的时候,要求不能执行耗时操作。

切换至子线程执行

想要让消息在子线程中处理,可以配置threadMode为BACKGROUND或者AYSNC。

BACKGROUND会区分当前发生事件的线程,是否是主线程,非主线程则直接分发事件,否则用backgroundPoster来处理事件。BackgroundPoster也是实现了Poster接口,其中也维护了一个用链表实现的消息队列PendingPostQueue,内部使用了EventBus 的 executorService 线程池对象去执行。

final class BackgroundPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;
//volatile确保多线程下的可见性
    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            //当前有线程执行时,不重复执行execute()
            if (!executorRunning) {
                executorRunning = true;
                eventBus.getExecutorService().execute(this);
            }
        }
    }

    @Override
    public void run() {
        try {
            try {
                while (true) {
                    PendingPost pendingPost = queue.poll(1000);
                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();
                            if (pendingPost == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }

}

在 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);
}

到这里应该就理解了 BACKGROUND 和 ASYNC ,虽然都可以保证在子线程中接收处理事件,但是内部实现是不同的。
BACKGROUND 同一时间,只会利用一个子线程,来循环从事件队列中获取事件并进行处理,也就是前面的事件的执行效率,会影响后续事件的执行。例如你分发了一个事件,使用的是 BACKGROUND 但是队列前面还有一个耗时操作,那你分发的这个事件,也必须等待队列前面的事件都处理完成才可以继续执行。所以如果你追求执行的效率,立刻马上就要执行的事件,可以使用 ASYNC。
那是不是都用 ASYNC 就好了?当然这种一揽子的决定都不会好,具体问题具体分析,ASYNC 也有它自己的问题。

ASYNC 会无脑的向线程池 executorService 发送任务,而这个线程池,如果你不配置的话,默认情况下使用的是 Executors 的 newCachedThreadPool() 创建的。
这里我又要说到编码规范了,不推荐使用 Executors 直接创建线程,之所以这样,其中一个原因在于线程池对任务的拒绝策略。 newCachedThreadPool 则会创建一个无界队列,来存放线程池暂时无法处理的任务,说到无界队列,拍脑袋就能想到,当任务(事件)过多时,会出现的 OOM。
这也确实是 EventBus 在使用 ASYNC 时,真实存在的问题。

但是其实这里让开发者自己去配置,也很难配置一个合理的线程池的拒绝策略,拒绝时必然会放弃一些任务,也就是会放弃掉一些事件,任何放弃策略都是不合适的,这在 EventBus 的使用中,表现出来就是出现逻辑错误,该收到的事件,收不到了。所以你看,这里无界队列不合适,但是不用它呢也不合适,唯一的办法就是合理的使用 ASYNC,只在必要且合理的情况下,才去使用它。

总结

1.EventBus可以通过threadMode来配置接收事件的线程
2.MAIN和MAIN_ORDERED都会在主线程中接收事件,区别在于是否区分发送事件的线程是否是主线程。
3.BACKGROUND确保在子线程中接收线程,会通过线程池,使用一个线程循环处理所有的事件。所以事件的执行时机,会收到事件队列前面的事件处理效率的影响。
4.ASYNC 确保在子线程中接收事件,区别于 BACKGROUND,ASYNC 会每次向线程池中发送任务,通过线程池的调度去执行。但是因为线程池采用的是无界队列,会导致 ASYNC 待处理的事件太多时,会导致 OOM。

参考链接:
https://juejin.im/post/5d81c6e8e51d4561ad65497c

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值