EventBus源碼分析(三)
在前面的兩篇文章中分別對EventBus中的構(gòu)建刺彩,分發(fā)事件以及注冊方法的查詢做了分析,本篇文章是最后一部分介粘,主要針對EventBus對于注冊方法的調(diào)用作學(xué)習(xí)了解沟涨,前面兩篇文章的鏈接如下:
EventBus源碼分析(一)
EventBus源碼分析(二)
EventBus方法調(diào)用機(jī)制需要從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 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情況下,注冊方法在不同線程調(diào)用的情況桨仿。
在理解注冊方法在哪個線程被調(diào)用之前需要首先明確postToSubscription()方法在哪個線程被調(diào)用睛低,該方法是由EventBus對象的post()方法經(jīng)過一系列方法調(diào)用的,所以它運(yùn)行在用戶發(fā)送事件的線程中服傍,參數(shù)isMainThread表示該運(yùn)行線程是否為主線程钱雷,下面四種threadMode中,注冊方法被調(diào)用的線程分別為:
- POSTING:最簡單的一種模式吹零,即直接在事件發(fā)送的線程中調(diào)用注冊方法罩抗;
- MAIN:在主線程中調(diào)用注冊方法,如果事件發(fā)送線程不是主線程灿椅,則由mainThreadPoster將方法調(diào)用派送到主線程中調(diào)用套蒂,其實(shí)就是使用的Handler機(jī)制钞支,后面再做分析;
- BACKGROUND:在后臺線程中調(diào)用操刀,如果事件發(fā)送不是在主線程烁挟,則注冊方法則直接在該線程中被調(diào)用,如果在主線程發(fā)送事件骨坑,則注冊方法由backgroundPoster派發(fā)到工作線程中調(diào)用撼嗓;
- ASYNC:直接由asyncPoster派發(fā)到工作線程中調(diào)用,至于它與BACKGROUND的區(qū)別欢唾,后面會根據(jù)源碼做分析且警。
所以從該方法中可以看出,在切換不同線程調(diào)用注冊方法礁遣,是通過三個Poster完成的斑芜,下面分別對它們的源碼做分析。
1. HandlerPoster
首先mainThreadPoster的類型為HandlerPoster亡脸,從名字可以看出它應(yīng)該與Handler有關(guān)押搪。在android的編程中,如果一些耗時(shí)操作的結(jié)果需要更新UI浅碾,那么需要通過定義Handler的形式大州,將結(jié)果加入到主線程的消息隊(duì)列,然后由自定義在主線程的Handler對消息做處理垂谢。那么這里的原理也是一致的厦画。首先看HandlerPoster的代碼:
final class HandlerPoster extends Handler {
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
...
}
可以看到HandlerPoster是繼承自Handler,那么它則是根據(jù)構(gòu)造器中傳入的Looper對象滥朱,在handleMessage()方法中處理對應(yīng)線程中消息隊(duì)列的消息根暑,那么我們再看在EventBus對象初始化時(shí),mainThreadPoster初始化是否為主線程的Looper, EventBus的構(gòu)造器中的部分代碼為:
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
Looper.getMainLooper()說明了mainThreadPoster的handleMessage方法是在主線程中被調(diào)用徙邻,就可以實(shí)現(xiàn)將注冊方法派發(fā)到主線程中調(diào)用了排嫌。其他參數(shù)后面部分一一分析。
首先來看PendingPostQueue這個明顯是一個隊(duì)列缰犁,其代碼有興趣的可以自行查看淳地,它是自定義的隊(duì)列,沒有實(shí)現(xiàn)Java定義的Queue接口帅容,這里只是簡單定義了內(nèi)部的隊(duì)列數(shù)據(jù)結(jié)構(gòu)以及入隊(duì)和出隊(duì)方法颇象,只不過有一個與時(shí)間相關(guān)的出隊(duì)方法,后面用到時(shí)會提到并徘。PendingPostQueue內(nèi)部的隊(duì)列結(jié)構(gòu)中的元素即為PendingPost遣钳,它封裝了一次注冊方法調(diào)用所需要的注冊信息和事件類型,即Subscription類型對象和事件對象麦乞。類的代碼很簡單蕴茴,不再貼出劝评,但是PendingPost中有一個對象的緩存池,最多可以保存10000個PendingPost對象荐开,避免了對象的創(chuàng)建和回收付翁。有興趣的可以查看源碼。
下面看HandlerPoster的入隊(duì)方法:
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");
}
}
}
}
首先根據(jù)注冊信息和事件對象構(gòu)建一個PendingPost加入隊(duì)列晃听,然后通過發(fā)送消息通知Handler自身調(diào)用handleMessage()方法百侧,這里的handlerActive是一個標(biāo)志位,標(biāo)志當(dāng)前是否正在執(zhí)行處理PendingPostQueue隊(duì)列中的PendingPost能扒,也就是正在調(diào)用隊(duì)列中的注冊方法佣渴。下面為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;
}
}
這里代碼的邏輯就是循環(huán)遍歷隊(duì)列,通過eventBus.invokeSubscriber(pendingPost)方法執(zhí)行注冊方法初斑,invokeSubscriber()方法在第一篇文章中已經(jīng)分析過辛润。兩次判斷pendingPost是否為空是處理多線程的同步問題,這里與單例模式是同樣的道理见秤。不過這里需要注意的是EventBus為HandlerPoster處理調(diào)用注冊方法設(shè)定了時(shí)間上限砂竖,也就是構(gòu)造器中初始化的maxMillisInsideHandleMessage,在EventBus中初始化mainThreadPost時(shí)為其指定了10ms, 也就是while循環(huán)超過10ms之后會退出handleMessage方法鹃答,并將handleActive置位true乎澄,并再次發(fā)送消息,使得該handler再次調(diào)用handleMessage()方法测摔,繼續(xù)處理隊(duì)列中的注冊信息置济,這是為了避免隊(duì)列過長是,while循環(huán)阻塞主線程造成卡頓锋八。
以上就是HandlerPoster的全部代碼浙于,十分簡短,通過Handler的方式將注冊方法的調(diào)用派發(fā)到其他線程挟纱,在EventBus中只是將其用于在主線程中調(diào)用注冊方法的情況羞酗。
2. BackgroundPoster
下面為BackgroundPoster的源碼:
final class BackgroundPoster implements Runnable {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
...
}
首先BackgroundPoster是繼承自Runnable,是一個可執(zhí)行的任務(wù)紊服,這里看到BackgroundPoster中也有隊(duì)列和標(biāo)志位檀轨,其執(zhí)行邏輯與HandlerPoster應(yīng)該極為相似,下面看一下它的入隊(duì)方法:
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);
}
}
}
代碼邏輯很簡單围苫,構(gòu)建PendingPost裤园,并在同步塊中將該對象入隊(duì)撤师,然后調(diào)用線程池中的線程執(zhí)行該Runnable剂府,即在其他線程中執(zhí)行run方法,進(jìn)而調(diào)用注冊方法剃盾,下面看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) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
這里的while循環(huán)腺占,除了沒有設(shè)定時(shí)間上限之外淤袜,幾乎與HandlerPoster中handleMessage方法中的處理邏輯完全一致,只不過標(biāo)志位改成了executorRunning衰伯,其作用也幾乎是一致的铡羡。但是這里需要注意有另外一個與時(shí)間有關(guān)的方法,即queue.poll(1000)意鲸,這里有一個等待過程烦周,即從隊(duì)列中取PendingPost對象時(shí),如果沒有PendingPost會等待1000毫秒怎顾,其代碼如下:
synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
if (head == null) {
wait(maxMillisToWait);
}
return poll();
}
這里是典型的生產(chǎn)者和消費(fèi)者模型读慎,并且設(shè)定了時(shí)間上限。因此也就說明了BackgroundPoster會將1000毫秒以內(nèi)入隊(duì)的注冊信息在同一個線程中調(diào)用槐雾,這一點(diǎn)也是與AsyncPoster最重要的區(qū)別夭委。BackgroundPoster的代碼也介紹完了,同樣十分簡單募强,下面繼續(xù)分析AsyncPoster中的代碼株灸,下面你會看到,其實(shí)現(xiàn)更簡單擎值。
3. AsyncPoster
由于AsyncPoster的代碼實(shí)現(xiàn)比較簡單慌烧,這里一次性全部貼出它的代碼:
class AsyncPoster implements Runnable {
private final PendingPostQueue queue;
private final EventBus eventBus;
AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
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);
}
}
AsyncPoster同樣是繼承自Runnable,但是可以看到這里沒有了標(biāo)志位和while循環(huán)幅恋,入隊(duì)的邏輯就是構(gòu)建PendingPost杏死,然后加入隊(duì)列,這里注意enqueue是同步的方法捆交,不用擔(dān)心同步問題淑翼,然后調(diào)用線程池的線程執(zhí)行該Runnable,run方法的邏輯就是取出隊(duì)列的pendingPost, 并執(zhí)行invokeSubscriber方法品追,由于是入隊(duì)一次調(diào)用一次execute方法玄括,所以正常情況下隊(duì)列中不會取不出pendingPost, 也不會剩余pendingPost。
這里AsyncPoster更為簡單肉瓦,但是重點(diǎn)是需要思考一下它與BackgroundPoster的區(qū)別遭京,前面也提到過BackgroundPoster設(shè)定一個時(shí)間上限,并且在該段時(shí)間內(nèi)入隊(duì)的注冊信息會在同一個線程中調(diào)用泞莉,而AsyncPoster則是完全異步的調(diào)用哪雕。也就是說BackgroundPoster會盡可能在同一線程中調(diào)用注冊方法,但不保證鲫趁,可是它保證有一定的順序斯嚎,也就是注冊信息收集的順序,而AsyncPoster對注冊方法的調(diào)用則是完全異步,不確定在哪個線程堡僻,也不確定順序糠惫。
至此,對于注冊方法調(diào)用的線程調(diào)度分析源碼已經(jīng)分析完了钉疫,總結(jié)起來只是三個不同的Poster, HandlerPoster是繼承自Handler, 通過handler機(jī)制可以將線程調(diào)度分派給主線程硼讽,而BackgroundPoster和AsyncPoster則是繼承自Runnable,覆寫run方法定義調(diào)用注冊方法的任務(wù)牲阁,并有線程池負(fù)責(zé)調(diào)度啟動該任務(wù)固阁,進(jìn)而完成對注冊方法的調(diào)用。
通過三篇文章城菊,對EventBus的源碼大致分析了一遍您炉,下圖對分析的流程大致做一個總結(jié),EventBus主要負(fù)責(zé)不同對象間傳遞消息役电,并且可以很方便地切換線程調(diào)用注冊方法赚爵,EventBus的工作機(jī)制大概可以分為圖中三部分,即注冊法瑟,發(fā)送和調(diào)度三個步驟冀膝,其中構(gòu)建部分主要是幾個數(shù)據(jù)結(jié)構(gòu)用來記錄信息,后面三個則是三個步驟霎挟,圖中總結(jié)了其中最主要的方法調(diào)用窝剖。
后記
EventBus可以很方便地在不同對象之間傳遞消息或者對象,并且很容易做到線程的切換酥夭,但是它也有它的局限性赐纱,就是不適用于多進(jìn)程的應(yīng)用,在多進(jìn)程的情況下熬北,EventBus中無論是單例還是同步方法以及同步塊都會失效疙描,所以便會失去了傳遞消息的作用。