前言
在前面的文章中啡浊,曾經(jīng)提及過(guò)EventBus的幾個(gè)重要成員觅够,其中就包括以下這些:
private final Poster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
對(duì)于使用過(guò)EventBus的讀者路狮,應(yīng)該可以理解這幾個(gè)對(duì)象的作用,它們就是用來(lái)切換線程的蔚约。今天奄妨,我們就來(lái)詳細(xì)分析這些對(duì)象。
一苹祟、Poster
BackgroundPoster 和AsyncPoster都是Poster的實(shí)現(xiàn)類砸抛,因此,需要先來(lái)分析一下這個(gè)頂級(jí)接口树枫。
/**
* Posts events.
*
* @author William Ferguson
*/
interface Poster {
/**
* Enqueue an event to be posted for a particular subscription.
*
* @param subscription Subscription which will receive the event.
* @param event Event that will be posted to subscribers.
*/
void enqueue(Subscription subscription, Object event);
}
Poster接口的代碼非常簡(jiǎn)單直焙,加上注釋也才30行左右。正如注釋所言砂轻,該接口的作用就是發(fā)布事件的奔誓。Poster接口里只有一個(gè)enqueue方法,用于將事件入隊(duì)處理搔涝。它接收兩個(gè)參數(shù):訂閱信息和事件厨喂。要知道enqueue的具體邏輯,必然要到其實(shí)現(xiàn)類中查找庄呈。
二蜕煌、HandlerPoster
mainThreadPoster在EventBus中比較獨(dú)特,它是通過(guò)MainThreadSupport的內(nèi)部方法createPoster創(chuàng)建的诬留。
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
MainThreadSupport也是一個(gè)接口類斜纪,因此,需要到其實(shí)現(xiàn)類查找createPoster的實(shí)現(xiàn)文兑。遍尋EventBus后盒刚,發(fā)現(xiàn)其唯一實(shí)現(xiàn)類是AndroidHandlerMainThreadSupport,自然绿贞,這就是我們的目標(biāo)了因块。
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);
}
}
AndroidHandlerMainThreadSupport的createPoster方法最終返回了一個(gè)HandlerPoster對(duì)象尽超,并將主線程的looper傳了進(jìn)去散休。因此颓鲜,可以確定嚼吞,mainThreadPoster其實(shí)就是HandlerPoster類型的凿歼。我們進(jìn)一步追蹤代碼鸣哀。
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();
}
...
}
HandlerPoster不僅實(shí)現(xiàn)了Poster接口蝙眶,也繼承了Handler類失晴,覆寫了其handleMessage方法靡狞。HandlerPoster將looper對(duì)象傳遞給了父類Handler耻警,而該looper從前面分析可知是主線程looper,因此handleMessage方法最終也是運(yùn)行在主線程的。HandlerPoster內(nèi)部還封裝了一個(gè)PendingPostQueue隊(duì)列甘穿,該隊(duì)列的元素是PendingPost腮恩,它封裝了事件和訂閱信息,并且持有指向下一個(gè)PendingPost對(duì)象的引用温兼。PendingPost內(nèi)部本身持有一個(gè)List<PendingPost>對(duì)象池秸滴,大小限制是10000,可以避免對(duì)象的頻繁創(chuàng)建和回收募判,我們的應(yīng)用也可以借鑒這種寫法荡含。
接下來(lái)看一下HandlerPost的enqueue實(shí)現(xiàn)
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");
}
}
}
}
enqueue的大致邏輯是這樣的:
- 從PendingPost對(duì)象池獲取一個(gè)PendingPost對(duì)象,該對(duì)象封裝了訂閱信息和事件
- 將PendingPost入隊(duì)
- 如果handler沒(méi)有處于激活態(tài)届垫,則將handlerActive置位成true释液,并調(diào)用Handler的sendMessage發(fā)送消息
之后就需要在Handler中處理消息,即執(zhí)行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;
}
}
這里在while循環(huán)里误债,首先從隊(duì)列的頭部取出PendingPost對(duì)象,如果為null妄迁,就直接跳出while循環(huán)寝蹈。否則就向下調(diào)用EventBus的invokeSubscriber方法。執(zhí)行完成后判族,需要判斷單次執(zhí)行handleMessage的時(shí)間是否超過(guò)預(yù)設(shè)定的最大值10ms躺盛,如果超過(guò),則直接跳出循環(huán)形帮,并將handlerActive置位成true。這樣做周叮,可以防止主線程因執(zhí)行handleMessage過(guò)久而導(dǎo)致阻塞辩撑。
而invokeSubscriber方法的邏輯如下:
void invokeSubscriber(PendingPost pendingPost) {
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
PendingPost.releasePendingPost(pendingPost);
if (subscription.active) {
invokeSubscriber(subscription, event);
}
}
---------------------------------------------------------------------------------------------------------------------
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
首先是從PendingPost對(duì)象里面取出事件和訂閱信息,并將PendingPost對(duì)象釋放回對(duì)象池仿耽;之后直接調(diào)用invoke執(zhí)行訂閱方法合冀。回溯上述流程项贺,invoke方法其實(shí)是在handleMessage當(dāng)中執(zhí)行的君躺,而handleMessage是執(zhí)行在主線程的,因?yàn)槠鋖ooper是mainLooper开缎。到這里棕叫,我們終于知道了,當(dāng)訂閱方法的threadMode是MAIN或MAIN_ORDERED且訂閱方法位于非UI線程時(shí)奕删,EventBus是如何切換到UI線程的了俺泣。
三、BackgroundPoster
BackgroundPoster也是Poster的實(shí)現(xiàn)類,同時(shí)它也實(shí)現(xiàn)了Runnable接口伏钠。在它的內(nèi)部同樣封裝了一個(gè)PendingPost隊(duì)列横漏,同時(shí)還有一個(gè)volatile類型de、用于判斷線程池是否運(yùn)行的布爾變量熟掂。
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
...
}
同樣缎浇,我們來(lái)看一下它的enqueue方法。
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}
enqueue方法首先也是從PendingPost對(duì)象池中取出PendingPost對(duì)象并入隊(duì)赴肚,之后調(diào)用EventBus的內(nèi)置緩存線程池來(lái)執(zhí)行任務(wù)华畏,該任務(wù)就是BackgroundPoster自身,執(zhí)行時(shí)會(huì)運(yùn)行期內(nèi)部的run方法尊蚁。
@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;
}
}
同樣是在while循環(huán)里面亡笑,從隊(duì)列中取出pendingPost,當(dāng)pendingPost 為null時(shí)横朋,需要將executorRunning置位成false并跳出循環(huán)仑乌,這樣可以防止浪費(fèi)CPU。當(dāng)下次有新的事件時(shí)琴锭,再提交任務(wù)到線程池執(zhí)行晰甚。如果pendingPost有值,則去調(diào)用invokeSubscriber方法决帖,這部分邏輯和第二小節(jié)一致厕九,不再贅述。只不過(guò)地回,此時(shí)的invoke方法是執(zhí)行在后臺(tái)線程中的扁远。到這里,BackgroundPoster的切換操作也講完了刻像。
四畅买、AsyncPoster
AsyncPoster也是Poster的實(shí)現(xiàn)類,同時(shí)也實(shí)現(xiàn)了Runnable接口细睡。我們直接看它的enqueue和run方法谷羞。
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}
---------------------------------------------------------------------------------------------------------------------
@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
和BackgroundPoster的enqueue方法是不是非常相似?唯一不同的是溜徙,BackgroundPoster會(huì)先判斷當(dāng)前是否正在使用線程池處理任務(wù)湃缎,如果不是,才允許提交新的任務(wù)蠢壹。這里其實(shí)可以看出嗓违,Background線程和Async線程的區(qū)別:Background線程會(huì)盡可能地在一個(gè)任務(wù)里有序地處理所有需要在Background線程模式下訂閱的事件;而Async線程則是每個(gè)任務(wù)只處理一個(gè)事件知残,是無(wú)序的靠瞎。因此比庄,當(dāng)需要在子線程執(zhí)行訂閱事件時(shí),且訂閱事件耗時(shí)較短或者需要保證順序乏盐,筆者建議使用Background的ThreadMode佳窑;如果訂閱事件耗時(shí)較長(zhǎng)或不在意順序,筆者建議使用Async的ThreadMode父能。
五神凑、結(jié)束語(yǔ)
本章我們分析了EventBus最重要的線程切換相關(guān)的幾個(gè)Poster,最后我們?cè)倏偨Y(jié)一下不同線程模式的區(qū)別:
POSTING:事件從哪個(gè)線程發(fā)出何吝,就在哪個(gè)線程執(zhí)行訂閱方法
MAIN:事件無(wú)論從哪個(gè)線程發(fā)出溉委,都在主線程執(zhí)行訂閱方法
MAIN_ORDERED:事件無(wú)論從哪個(gè)線程發(fā)出,都在主線程執(zhí)行訂閱方法
BACKGROUND:事件從主線程發(fā)出爱榕,則在后臺(tái)線程執(zhí)行訂閱方法瓣喊;事件從子線程發(fā)出,則直接在該線程執(zhí)行訂閱方法
ASYNC:事件無(wú)論從哪個(gè)線程發(fā)出黔酥,都開(kāi)啟新的子線程處理