关于EventBus的线程切换,我们单独放在这篇文章里。我们直接进入正题。
我们先看看枚举类ThreaMode
public enum ThreadMode {
POSTING,
MAIN,
MAIN_ORDERED,
BACKGROUND,
ASYNC
}
POSTING:消息订阅者将与发送者所处在同一线程中。这是默认值。该模式避免了线程切换所带来的开销。这是处理简单事务所推荐的模式。如果发布线程是主线程,使用该模式必须立即返回,以避免阻塞主线程。
MAIN:消息订阅者将在Android主线程(即UI线程)中被调用。 如果发布线程是主线程,事件处理方法将直接调用。 使用此模式的事件处理必须快速返回以避免阻塞主线程。
MAIN_ORDERED:订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
BACKGROUND:消息订阅者将在后台线程中被调用。如果发布线程不是在主线程。事件处理方法将被直接调用而不需要切换线程。如果发布线程是在主线程,EventBus使用了单一的后台线程有序地传递所有事件。 使用此模式的事件处理必须快速返回而避免阻塞后台线程。
ASYNC:事件处理方法将在单独的一个线程中调用。这个线程往往独立于发布线程和主线程(即UI线程)。发布事件不需要等待事件处理方法。如果事件处理方法比较耗时,则需要使用该模式。例如:用于网络访问。 避免触发大量数据长时间运行的异步处理程序方法同时限制并发线程的数量。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);
}
}
我们下面讨论三种情况:
1.发送者处于子线程,消息订阅者处于主线程
2.发送者处于主线程,消息订阅者处于子线程
3.消息订阅者处于子线程(不考虑发送者的线程,重新开辟线程发送)
发送者处于子线程,消息订阅者处于主线程
这时候会执行case MAIN中的以下方法:
mainThreadPoster.enqueue(subscription, event);
mainThreadPoster类是Poster接口,具体的实现类是什么呢?追本溯源的话,就是HandlerPoster。很容易找到这个类,我就不再这里赘述了。我们直接看HandlerPoster源码。HandlerPoster中有三个方法:构造方法,enqueue,handleMessage
构造方法
protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
很简单,eventBus就是我们所知道的eventBus对象;maxMillisInsideHandleMessage是一个时间阈值;queue是PendingPostQueue的对象,而PendingPostQueue查看源码的话,不难发现是一个链表,其存储的对象是PendingPost,进入PendingPost源码中查看,主要是一些数据的存储和一个对象列表,对象列表主要做对象池。
还有一个地方,千万不要忘记,那就是super(looper),这个地方是线程切换的重点。我们通过看HandlerPoster可以知道,HandlerPoster的父类是就是Handler,如果想要从子线程中切换到主线程,我想大家应该都不会陌生,用到的肯定是Handler这个类。而这个looper就是主线程的looper,通过Looper.getMainLooper获取的。
enqueue方法
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");
}
}
}
}
加入队列的操作,没什么说的。
handleMessage方法
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
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;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
看着代码很多,其实中心思想只有一个:从队列中取数据,再调用eventBus的invokeSubscriber方法,而invokeSubscriber这个方法最终会调用到同名不同参数的invokeSubscribe方法,通过反射进行事件的发送。
其实三个方法介绍完了,大家对这个HandlerPoster基本上也就有了了解,对于子线程切换到主线程也就明白了,就是Handler的操作!
发送者处于主线程,消息订阅者处于子线程
这个时候执行的是case BACKGROUND中的以下方法:
backgroundPoster.enqueue(subscription, event);
backgroundPoster是BackgroundPoster对象,而BackgroundPoster实现了Runnable接口,本身就是一个线程。类中有三个方法:构造方法,enqueue,run方法。
相关的代码我就不贴了,不是我懒,而是因为如果上一个HandlerPoster理解了的话,这个根本就不是什么问题。中心思想就是:事件加入到队列中,通过eventBus的线程池执行BackgroundPoster,会执行run方法,run方法中会从队列中取出PendingPost,传递到eventBus中,通过反射将事件发送到观察者。
消息订阅者处于子线程(不考虑发送者的线程,重新开辟线程发送)
这种情况会执行
asyncPoster.enqueue(subscription, event);
我们查看AsyncPoster,可以发现和BackgroundPoster相似,但是代码更加简单,我们通过比较可以发现:AsyncPoster没有考虑到PendingPost加入的顺序问题,加入队列立即执行,而BackgroundPoster是有执行顺序的,也就是说如果只有上一个事件传递出去了,下一个事件才会执行。
所以threadMode=ASYNC和threadMode=BACKGROUND时的区别:
1.如果发送者处于子线程,threadMode=BACKGROUND时,不会把事件加入到队列,而是直接进行反射,不需要再开辟线程;而threadMode=ASYNC时,无论发送者处于什么线程,都会重新开辟新线程进行事件的发送
2.BACKGROUND对于事件发送是有顺序的,如果上一个事件没有发送完成,那么不会进行下一个事件的发送;而ASYNC则不会考虑顺序问题,有事件的加入,就立即执行。
总结
其实关于EventBus的线程切换,我们很容易理解。但是我们在看源码的时候,一定要记住看的目的。我们不是为了看源码而看源码,而是要从源码中汲取知识,学习思想。在看源码的时候要学会思考。比如这个线程切换,看完这个之后,我们要学会如何把它应用到我们的项目中去,我们在开发过程中会遇到很多线程切换的问题。当然也有很多处理的办法。我们完全可以仿照EventBus一样,写一个注解,就可以进行线程切换。我就把这个办法应用到了我自己的项目中。通过AOP和注解的结合,完美进行了线程的切换。
以上就是EventBus线程切换的内容,有问题欢迎留言~