今天学习了一下EventBus 的源码,将其在接收事件时指定线程执行的大致原理梳理了一下,提炼整理,作为学习总结,也方便日后查阅。
正文
EventBus 可以在不同的线程发送事件、并在指定类型的线程接收和处理事件。在不同线程情况下,发送、接收并处理事件大致有4种情况:
A:在主线程发送,在子线程发送;B:在主线程执行订阅方法,在子线程执行订阅方法;AB两两搭配会有四种组合,也就是在不同线程情况下,发送、接收并处理事件的4种情况。
主要的问题其实只有两个,其一:如何判断当前发送事件的线程是否是主线程;其二:如何在接收事件时指定线程并执行;
一个一个来看。
1.如何判断是否在主线程发送
EventBus在初始化的时候会初始化一个MainThreadSupport对象,它会去获取主线程的Looper对象并存起来。(当前最新版本如果不是Android环境MainThreadSupport会为空,非Android环境也就无需关注是否是UI主线程的问题了)
在发送消息的时候,EventBus会取出当前线程的Looper对象对象与主线程Looper对象做比较,如果相同,说明是在主线程发送消息,如果不同,说明是在子线程发送消息。以下是MainThreadSupport的代码:
public interface MainThreadSupport {
boolean isMainThread();
Poster createPoster(EventBus eventBus);
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);
}
}
}
2.怎么在指定的线程执行订阅者的方法
在找到订阅者之后,判断不同线程情况下执行订阅方法的逻辑基本都在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);
}
}
以上代码中,如何判断是否是主线程上面已经说过了。
invokeSubscriber()这个方法其实就是拿到订阅者的信息,直接执行订阅方法了(通过反射获取)。
subscription对象中有一个SubscriberMethod对象,而SubscriberMethod这个对象基本上包含了订阅者的执行线程、订阅方法、是否粘性事件、优先级等等信息。如下:
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
//省略若干代码...
}
所以,从postToSubscription()方法可以看出,当threadMode是POSTING时,直接在当前线程执行,不做判断,也就是从哪个线程发送,就从哪个线程执行订阅方法;
我们这里主要来看threadMode为MAIN和BACKGROUND的情况:
在主线程执行
当threadMode为MAIN时,如果在主线程发送,直接在当前线程执行,没有问题。如果不在主线程发送,会有一个mainThreadPoster将包含订阅者信息的对象加入队列。这个mainThreadPoster其实是Handler的子类,它利用Handler的消息机制,发送消息并在主线程接收消息,获取到订阅者的信息后在主线程处理事件,从而实现在子线程发送消息,在主线程处理事件。
这里直接利用Hadler的现成机制,可谓简明高效。
在子线程执行
当threadMode为BACKGROUND时,如果不在主线程发送,直接执行,没有问题。如果在主线程发送,这里有一个backgroundPoster将包含订阅者信息的对象加入队列。BackgroundPoster其实是Runnable的子类,在自己的run方法中不断从队列中取出订阅者对象,执行订阅方法。EventBus维护了一个线程池,BackgroundPoster会将自己丢到线程池中,执行自己的run方法,从而实现在在主线程发送事件,在子线程中执行订阅方法。
以上,就是EventBus切换执行线程的主要流程。
其实并不难。
末尾附上一篇讲解EventBus原理的简练博文:戳这里