一、消息機(jī)制原理
Handler消息機(jī)制老生常談了,必備八股之一。但是每次看都有新收獲劳秋,故好好總結(jié)一下Handler相關(guān)知識(shí)。
1.1 基本概念
1呀伙、Handler
用于發(fā)送和處理消息的類雨女,有多種重載的構(gòu)造方法氛堕,通過(guò)一系列sendXXX
和postXXX
方法來(lái)發(fā)送消息到消息隊(duì)列,然后通過(guò)實(shí)現(xiàn)Handler.Callback
接口或重寫handleMessage
方法處理消息
2赠摇、MessageQueue
消息隊(duì)列浅蚪,它是一個(gè)鏈表結(jié)構(gòu)藕帜,用以存放handler發(fā)送的消息,實(shí)現(xiàn)了獲取消息的方法next()
和移除消息及消息處理回調(diào)的方法(removeXXX
系列方法)
3掘鄙、Message
消息耘戚,承載一些基本數(shù)據(jù),消息隊(duì)列存放對(duì)象操漠。維護(hù)了一個(gè)消息對(duì)象池,可以復(fù)用消息,避免創(chuàng)建太多消息對(duì)象占用過(guò)多內(nèi)存,導(dǎo)致APP卡頓。
消息分類:
4榨惠、Looper
消息機(jī)制的靈魂掉奄,用以不斷調(diào)度消息對(duì)象并且分發(fā)給handler處理狂芋。Looper是同線程綁定的珍剑,不同線程的Looper不一樣,通過(guò)ThreadLocal實(shí)現(xiàn)線程隔離塌衰。
1.2 消息機(jī)制主流程
1舔箭、發(fā)送消息
可以使用sendMessage(以及一系列 sendXXX的消息發(fā)送方法)和post方法發(fā)送即時(shí)同步消息檬寂,或通過(guò)sendXXXDelayed和postDelayed發(fā)送延遲同步消息价涝。
如果是通過(guò)sendXXX方法發(fā)送即時(shí)或延時(shí)消息,最終都會(huì)輾轉(zhuǎn)調(diào)用到sendMessageAtTime(@NonNull Message msg, long uptimeMillis)
方法,然后調(diào)用enqueueMessage
方法见妒。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;// ① 設(shè)置處理該消息的handler對(duì)象
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// ② 設(shè)置消息類型,同步或異步
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// ③ 交由消息隊(duì)列的入隊(duì)方法
return queue.enqueueMessage(msg, uptimeMillis);
}
該方法主要有3個(gè)作用溃卡,注釋中的①②③分別說(shuō)明了。
2、消息入隊(duì)
消息入隊(duì)最終是靠消息隊(duì)列的恩queueMessage方法完成衬浑,其代碼如下
boolean enqueueMessage(Message msg, long when) {
// ①
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// ②
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// ③
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// ④
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// ⑤
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
注釋中標(biāo)明了5個(gè)注意點(diǎn)俭缓,??一一說(shuō)明下:
① 消息對(duì)象必須指定target
坷衍,也就是處理消息的handler對(duì)象;而且message對(duì)象的flag
為FLAG_IN_USE
侈玄。否則將拋出異常爬橡。
②設(shè)置消息對(duì)象標(biāo)志FLAG_IN_USE
和時(shí)間卜范,創(chuàng)建喚醒字段,用于標(biāo)記是否需要喚醒消息隊(duì)列
③如果當(dāng)前消息隊(duì)列沒(méi)有消息或要入隊(duì)的消息when
值小于對(duì)列頭消息when
值伟恶,則將新消息插入到鏈表頭部。設(shè)置needWeak
,它又由mBlocked
變量決定俱尼,mBlocked
的設(shè)置是在next()
方法中,簡(jiǎn)單來(lái)說(shuō)消息隊(duì)列僅有延時(shí)消息或空隊(duì)列時(shí),mBlocked
為true
④不滿足③的情況下矮烹,從消息鏈表頭開始遍歷,將新消息插入到第一個(gè)when值大于新消息when值的消息節(jié)點(diǎn)前方岔绸。
例如當(dāng)前消息隊(duì)里:100 - 30 -20 -10(數(shù)字表示消息的when值)
graph LR
A((100))
B((30))
C((20))
D((10))
A --- B --- C --- D
現(xiàn)要插入一個(gè)新消息50骑歹,那么插入后的隊(duì)列情況是:
graph LR
A((100))
B((30))
C((20))
D((10))
E((50))
A --- E --- B --- C --- D
⑤是否需要喚醒牺蹄,需要?jiǎng)t調(diào)用native方法喚醒
總之,入隊(duì)方法就是讓所有消息根據(jù)when的大小盡量有序排列桑包,when越小則越位于消息鏈表頭部卡辰。
3胞皱、消息出隊(duì)
Looper的loop()
在一個(gè)死循環(huán)中不斷獲取消息,獲取到消息就分發(fā)給handler處理九妈,獲取消息通過(guò)MessageQueue#next()
方法反砌,這個(gè)方法邏輯較多且都比較重要,下面會(huì)詳細(xì)說(shuō)明萌朱。
Message next() {
......
// ①
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// ②
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// ③
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// ④
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// ⑤
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
// ⑥
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// ⑦
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
① pendingIdleHandlerCount
表示IdleHandler的數(shù)量宴树。
② nextPollTimeoutMillis
表示消息隊(duì)列休眠的時(shí)間,是個(gè)阻塞方法晶疼。 -1表示無(wú)限阻塞酒贬,0表示不阻塞
③ 實(shí)現(xiàn)阻塞的native方法,可通過(guò)nativeWake
方法喚醒
④ 針對(duì)同步屏障機(jī)制的處理冒晰,前文已經(jīng)說(shuō)了普通消息在入隊(duì)前一定會(huì)設(shè)置target
屬性同衣,唯獨(dú)有種方式不會(huì),即postSyncBarrier
方法發(fā)出的同步屏障消息是不會(huì)設(shè)置target
屬性的壶运,同步屏障相關(guān)內(nèi)容后面會(huì)詳細(xì)介紹耐齐,這里只要了解普通的同步消息不會(huì)走到這步即可。
⑤ 對(duì)于同步消息蒋情,從此步開始真正去獲取消息對(duì)象埠况。首先明確下代碼里的幾個(gè)對(duì)象含義:mMessage
始終表示消息鏈表頭部,p
表示當(dāng)前節(jié)點(diǎn)棵癣,prevMsg
表示p
節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)辕翰。
對(duì)于即時(shí)消息,設(shè)置
mBlocked=false
狈谊,表示不阻塞喜命。同步消息的prevMsg
始終為null沟沙,所以從頭結(jié)點(diǎn)開始遍歷,獲取當(dāng)前節(jié)點(diǎn)并返回壁榕。對(duì)于延時(shí)消息矛紫,計(jì)算延時(shí)時(shí)間,然后走到⑥牌里,若
now < mMessages.when
表示還沒(méi)到延時(shí)消息執(zhí)行時(shí)間颊咬,然后會(huì)走到if(pendingIdleHandlerCount <= 0)
中,設(shè)置mBlocked=true
牡辽,然后開始下次循環(huán)喳篇,又走到③處,nextPollTimeoutMillis
不等于0态辛,于是阻塞麸澜。
⑥ 用于計(jì)算IdleHandler
個(gè)數(shù),初始化IdleHandler
數(shù)組因妙。IdleHandler
是用于在消息隊(duì)列空閑時(shí)處理一些任務(wù)痰憎,適用于一些不緊急非高優(yōu)的任務(wù),后面也會(huì)詳細(xì)介紹攀涵。
⑦ 重置pendingIdleHandlerCount
和nextPollTimeoutMillis
4铣耘、消息分發(fā)
前文說(shuō)了Looper的loop
方法不斷獲取消息并分發(fā),分發(fā)的關(guān)鍵代碼就是
public static void loop() {
......
for (;;) {
// ①
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
//②
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
......
try {
// ③
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
// ④
msg.recycleUnchecked();
}
}
① 獲取Looper對(duì)象以故,如果為空的話拋出異常蜗细。
② 可以通過(guò)Looper#setMessageLogging
方法設(shè)置打印器,用來(lái)輸出一些開發(fā)者需要的信息怒详,通常在性能監(jiān)控上需要獲取這些信息來(lái)評(píng)估優(yōu)化效果炉媒。
③ 分發(fā)消息給handler處理,target
就是在消息入隊(duì)時(shí)設(shè)置的handler對(duì)象昆烁。
④ 回收消息對(duì)象
步驟③將消息分發(fā)給了對(duì)應(yīng)handler吊骤,看下dispatchMessage
方法的實(shí)現(xiàn)
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
代碼很清晰,首先如果設(shè)置msg.callback
静尼,就調(diào)用handleCallback
方法白粉。那么msg.callback
在哪里設(shè)置的呢?找到賦值的地方鼠渺,發(fā)現(xiàn)post
和postDelayed
方法
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
傳入了getPostMessage
方法鸭巴,繼續(xù)看該方法
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
現(xiàn)在明了了,正是這里設(shè)置了msg.callback
拦盹,并且值就是post的參數(shù)鹃祖,一個(gè)runnable對(duì)象。
看下handleCallback
代碼
private static void handleCallback(Message message) {
message.callback.run();
}
其實(shí)就是執(zhí)行了post傳入的runnable參數(shù)的run方法普舆。
如果不是通過(guò)post方式發(fā)送的消息恬口,就會(huì)走到else邏輯里校读。首先判斷是否實(shí)現(xiàn)了Handler.Callback
接口,可在handler的構(gòu)造函數(shù)傳入祖能,設(shè)置了則調(diào)用Handler.Callback
接口的handleMessage
方法地熄。
否則調(diào)用Handler
的handleMessage
方法,它是一個(gè)空方法芯杀,需要開發(fā)者重寫來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯。
總結(jié):消息的分發(fā)執(zhí)行順序就是post#run
方法 -> Handler.Callback.handlerMessage
方法 -> Handler#handlerMessage
方法
至此雅潭,Handler消息的發(fā)送揭厚、入隊(duì)出隊(duì)、以及分發(fā)執(zhí)行的全流程就闡述完畢了扶供,路徑還是很清晰的筛圆。但是依然遺留了一些問(wèn)題,比如同步屏障椿浓、IdleHandler等太援,所以我們繼續(xù)(:dog:)。
二扳碍、同步屏障
我們知道無(wú)論是應(yīng)用啟動(dòng)還是屏幕刷新都需要完整繪制整個(gè)頁(yè)面內(nèi)容提岔,目前大多數(shù)手機(jī)的屏幕刷新率為60Hz,也就是耳熟能詳?shù)?6ms刷新一次屏幕笋敞。那么問(wèn)題來(lái)了碱蒙,如果主線程的消息隊(duì)列待執(zhí)行的消息非常多,怎么能保證繪制頁(yè)面的消息優(yōu)先得到執(zhí)行夯巷,來(lái)盡力保證不卡頓呢赛惩。
前文分析了整個(gè)消息傳遞處理機(jī)制,有一個(gè)可疑地方趁餐,就是在取消息時(shí)喷兼。2個(gè)疑點(diǎn)
- 消息的target屬性為null
- 消息被設(shè)置為了異步消息
......
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// *
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
......
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
在標(biāo)*處,發(fā)現(xiàn)有個(gè)循環(huán)不斷過(guò)濾掉同步消息后雷,發(fā)現(xiàn)進(jìn)入條件是target對(duì)象為null季惯,而正常情況下入隊(duì)的消息都會(huì)設(shè)置target。
從應(yīng)用啟動(dòng)入手喷面,頁(yè)面啟動(dòng)過(guò)程不詳述了星瘾,大體調(diào)用鏈路是ViewRootImpl#setView
-> ViewRootImpl#requestLayout
-> ViewRootImpl#scheduleTraversals
看下scheduleTraversals方法代碼
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// ①
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
調(diào)用了消息隊(duì)列的postSyncBarrier
方法,進(jìn)去看看
/**
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
//①
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
// ②
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
① 發(fā)現(xiàn)創(chuàng)建了Message對(duì)象惧辈,但沒(méi)有設(shè)置target屬性琳状。通過(guò)前面對(duì)handler的分析知道,loop方法分發(fā)給handler執(zhí)行完后會(huì)回收message對(duì)象盒齿,即msg.recycleUnchecked();
念逞,它會(huì)將message對(duì)象的所有屬性置空困食。
② 這一步跟普通的消息入隊(duì)目的一樣,就是把這個(gè)同步屏障消息按照when值大小插入到鏈表翎承,when越大越靠近鏈表尾部硕盹。由于同步屏障消息設(shè)置的when是系統(tǒng)啟動(dòng)以來(lái)的時(shí)間,非常長(zhǎng)叨咖,所以一般來(lái)說(shuō)同步屏障消息基本都插入在尾部瘩例。
第一個(gè)問(wèn)題什么消息的target是null,那就是postSyncBarrier發(fā)送的同步屏障消息
設(shè)置同步屏障后代碼繼續(xù)執(zhí)行甸各,執(zhí)行 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
一直深入postCallback查看垛贤,發(fā)現(xiàn)執(zhí)行到了postCallbackDelayedInternal
方法
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
......
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
// *
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
:創(chuàng)建了真正繪制頁(yè)面的消息對(duì)象,并且調(diào)用setAsynchronous()
將消息設(shè)置為了異步*
所以第二個(gè)問(wèn)題什么時(shí)候設(shè)置消息為異步也知道了趣倾。
總結(jié):對(duì)于異步消息聘惦,Looper會(huì)遍歷消息隊(duì)列找到異步消息執(zhí)行,確保像刷新屏幕等高優(yōu)任務(wù)及時(shí)得到執(zhí)行儒恋。同步消息得不到處理善绎,這就是為什么叫同步屏障的原因。當(dāng)使用了同步屏障诫尽,記得通過(guò)removeSyncBarrier
移除禀酱,不然同步消息不能正常執(zhí)行。
當(dāng)然牧嫉,正常情況開發(fā)者也不能手動(dòng)發(fā)送和移除同步屏障比勉,因?yàn)樗鼈兌急籬ide注釋了。****不過(guò)了解這一機(jī)制和其中蘊(yùn)含的編程思維還是很有裨益的
三驹止、IdleHandler
IdleHandler提供了一種在消息隊(duì)列空閑時(shí)執(zhí)行的某些操作的手段浩聋,適用于執(zhí)行一些不重要且低優(yōu)先級(jí)的任務(wù)。它的使用也很簡(jiǎn)單調(diào)用MessageQueue#addIdleHandler
方法將任務(wù)添加到消息隊(duì)列臊恋,然后隊(duì)列空閑時(shí)會(huì)自動(dòng)執(zhí)行衣洁,可通過(guò)removeIdleHandler
方法或自動(dòng)回收。
消息隊(duì)列通過(guò)一個(gè)ArrayList來(lái)儲(chǔ)存添加的IdleHandler任務(wù)抖仅。
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 臨時(shí)儲(chǔ)存IdleHandler任務(wù)
private IdleHandler[] mPendingIdleHandlers;
調(diào)用IdleHandler任務(wù)的位置在MessageQueue#next()
方法中坊夫,無(wú)關(guān)代碼已省略
Message next() {
// ①
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
......
synchronized (this) {
// 省略部分為獲取消息對(duì)象的過(guò)程
.....
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
// ②
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
//③
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
// ④
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// ⑤
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
① 每次Looper調(diào)用next
方法時(shí),先將IdleHandler臨時(shí)數(shù)組的大小pendingIdleHandlerCount
重置為 -1撤卢。
② 首次運(yùn)行時(shí)pendingIdleHandlerCount < 0
肯定成立环凿,如果當(dāng)前消息隊(duì)列為空或者只有延時(shí)消息時(shí),認(rèn)為此時(shí)隊(duì)列空閑可以執(zhí)行IdleHandler任務(wù)了放吩。令pendingIdleHandlerCount
為已添加的IdleHandler任務(wù)個(gè)數(shù)智听。
③ mPendingIdleHandlers
是一個(gè)數(shù)組,首次執(zhí)行時(shí)可定為空,所以初始化數(shù)組到推,數(shù)組大小最小為4考赛。并且將mIdleHandlers
列表中的任務(wù)復(fù)制到這個(gè)臨時(shí)數(shù)組。
④ 循環(huán)臨時(shí)數(shù)組執(zhí)行IdleHandler任務(wù)莉测,任務(wù)從mPendingIdleHandlers
數(shù)組中取出后颜骤,會(huì)置空,釋放對(duì)handler對(duì)象的引用捣卤。然后調(diào)用queueIdle()
真正執(zhí)行IdleHandler任務(wù)忍抽。
queueIdle()
是一個(gè)接口方法,需要自己實(shí)現(xiàn)業(yè)務(wù)邏輯董朝。另外它的返回值決定是否要自動(dòng)刪除該IdleHandler任務(wù)梯找,返回true該任務(wù)執(zhí)行后將不會(huì)被刪除
⑤ 重置mPendingIdleHandlers = 0
,開啟下次循環(huán)益涧。
四、消息對(duì)象池
消息是數(shù)據(jù)的載體驯鳖,我們知道創(chuàng)建消息對(duì)象是一般不提倡去new
一個(gè)對(duì)象闲询,而是調(diào)用Message的一系列obtain
的重載方法,原因就是因?yàn)榭梢詮?fù)用已創(chuàng)建的Message對(duì)象浅辙,避免創(chuàng)建過(guò)多對(duì)象占據(jù)大量?jī)?nèi)存扭弧。既然是復(fù)用,那么一定存在某種數(shù)據(jù)結(jié)構(gòu)去保存對(duì)象记舆,這就是消息對(duì)象池鸽捻,使用的是鏈表結(jié)構(gòu)。
3.1 創(chuàng)建Message對(duì)象
消息對(duì)象池有幾個(gè)重要屬性泽腮,分別是:
// 同步對(duì)象
public static final Object sPoolSync = new Object();
// 鏈表頭節(jié)點(diǎn)
private static Message sPool;
// 消息池大杏选(鏈表長(zhǎng)度,表示消息個(gè)數(shù))
private static int sPoolSize = 0;
// 消息池最大容量
private static final int MAX_POOL_SIZE = 50;
看下obtain
方法
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
代碼很簡(jiǎn)潔诊赊,獲取消息對(duì)象是同步操作厚满,從頭節(jié)點(diǎn)開始,如果頭結(jié)點(diǎn)不為空碧磅,取得頭結(jié)點(diǎn)碘箍。然后指針后移,并且消息池大小減1鲸郊。否則的才通過(guò)new
方式創(chuàng)建新對(duì)象丰榴。
3.2 回收Message對(duì)象
可以調(diào)用recycle
方法回收消息對(duì)象
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");
}
return;
}
recycleUnchecked();
}
如果消息標(biāo)記了FLAG_IN_USE
標(biāo)志,不可回收秆撮。然后真正回收的方法是recycleUnchecked();
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
可見回收消息四濒,首先就是將其成員變量全部重置為初始值,然后在消息池大小不超過(guò)限制容量時(shí),讓將要被回收節(jié)點(diǎn)的next
指向頭結(jié)點(diǎn)峻黍,再把頭指針移到當(dāng)前節(jié)點(diǎn)复隆,容量加1。
五姆涩、總結(jié)
本文從Handler消息機(jī)制出發(fā)挽拂,分析了消息從發(fā)送、調(diào)度和分發(fā)處理的全過(guò)程骨饿。在此過(guò)程中亏栈,發(fā)現(xiàn)涉及到了同步屏障、IdleHandler等知識(shí)點(diǎn)宏赘,并對(duì)其做了分析和說(shuō)明绒北。有些東西可能在平時(shí)開發(fā)中用不上,例如消息屏障察署,但其蘊(yùn)含的編程思想也是十分值得學(xué)習(xí)借鑒的闷游。