Android Handler機(jī)制的工作原理詳析
寫(xiě)在前面
上一次寫(xiě)完Binder學(xué)習(xí)筆記之后,再去看一遍Activity的啟動(dòng)流程,因?yàn)榱私饬薆inder的基本原理,這次看印象會(huì)更深一點(diǎn),學(xué)習(xí)效果也比以前好很多。本來(lái)打算直接來(lái)寫(xiě)Activity的啟動(dòng)流程的,但總覺(jué)得Handler也需要寫(xiě)一下,知道Handler和Binder的原理后,再去看Activity的啟動(dòng)流程,應(yīng)該也沒(méi)什么問(wèn)題了。雖然網(wǎng)上已經(jīng)有很多Handler相關(guān)的文章了,而且Handler機(jī)制的上層原理也并不難,還是決定寫(xiě)一下,因?yàn)槲蚁霕?gòu)建自己的知識(shí)體系。也希望給看我博客的朋友們一個(gè)無(wú)縫銜接的閱讀體驗(yàn)。
Handler機(jī)制涉及到的類(lèi)主要有Handler、Message、Looper、MessageQueue、ThreadLocal等。雖然我們最熟悉的是Handler和Message這兩個(gè)類(lèi),但是在我們開(kāi)始可以使用Handler之前,Looper是為我們做了一些事情的。
本文的源碼是基于android-28的
Looper
在使用Handler之前,我們必須得初始化Looper,并讓Looper跑起來(lái)。
Looper.prepare(); ... Looper.loop();
執(zhí)行上面兩條語(yǔ)句之后,Looper就可以跑起來(lái)了。先來(lái)看看對(duì)應(yīng)的源碼:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
必須保證一個(gè)線程中有且只有一個(gè)Looper對(duì)象,所以在初始化Looper的時(shí)候,會(huì)檢查當(dāng)前線程有沒(méi)有Looper對(duì)象。Looper的初始化會(huì)創(chuàng)建一個(gè)MessageQueue。創(chuàng)建完Looper后會(huì)放到ThreadLocal中去,關(guān)于ThreadLocal,后面會(huì)說(shuō)到。
public static void loop() {
// 判斷當(dāng)前線程有沒(méi)有初始化Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// target指的是Handler
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
方法比較長(zhǎng),所以只把最核心的代碼放了出來(lái)。省略掉的代碼中有一個(gè)比較有意思的:我們可以指定一個(gè)閾值比如說(shuō)200,當(dāng)Message的處理超過(guò)200ms時(shí),就會(huì)輸出Log。這可以在開(kāi)發(fā)中幫助我們發(fā)現(xiàn)一些潛在的性能問(wèn)題??上У氖?,設(shè)置閾值的方法是隱藏的,無(wú)法直接調(diào)用,所以這里就不放出代碼了,感興趣的朋友自己翻一下源碼吧。
簡(jiǎn)化后的代碼可以看出邏輯十分簡(jiǎn)單,可以說(shuō)Looper在當(dāng)中扮演著搬磚工的角色,從MessageQueue中取出Message,然后交給Handler去分發(fā),再去MessageQueue中取出Message...無(wú)窮無(wú)盡,就像愚公移山一樣。
看到這里,應(yīng)該多多少少會(huì)覺(jué)得有點(diǎn)不對(duì)勁,因?yàn)檫@里是一個(gè)死循環(huán),按道理來(lái)說(shuō)會(huì)一直占著CPU資源的,并且消息也總有處理完的時(shí)候,難道處理完就從消息隊(duì)列返回Null,然后Looper結(jié)束嗎?顯然不是,注意看注釋might block。
MessageQueue
答案就在MessageQueue里面,直接來(lái)看一下next():
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;
}
}
代碼有點(diǎn)長(zhǎng),這次不打算省略掉一些了,因?yàn)檫@里面還有一個(gè)小彩蛋。
方法中最重要的應(yīng)該就是這一行了
nativePollOnce(ptr, nextPollTimeoutMillis);
簡(jiǎn)單來(lái)說(shuō),當(dāng)nextPollTimeoutMillis == -1時(shí),掛起當(dāng)前線程,釋放CPU資源,當(dāng)nextPollTimeoutMillis >= 0時(shí)會(huì)延時(shí)指定的時(shí)間激活一次線程,讓代碼繼續(xù)執(zhí)行下去。這里涉及到了底層的pipe管道和epoll機(jī)制,就不再講下去了(其實(shí)是因?yàn)橹v不下去了)。這也就可以回答上面的問(wèn)題了,當(dāng)沒(méi)有消息的時(shí)候只需要讓線程掛起就行了,這樣可以保證不占用CPU資源的同時(shí)保住Looper的死循環(huán)。
然后我們來(lái)看消息是如何取出來(lái)的。MessageQueue中有一個(gè)Message,Message類(lèi)中又有一個(gè)Message成員next,可以看出Message是一個(gè)單鏈表結(jié)構(gòu)。消息的順序是根據(jù)時(shí)間先后順序排列的。一般來(lái)說(shuō),我們要取的Message就是第一個(gè)(這里先不考慮異步消息,關(guān)于異步消息以后會(huì)講到的,又成功給自己挖了一個(gè)坑哈哈),如果當(dāng)前時(shí)間大于等于Message中指定的時(shí)間,那么將消息取出來(lái),返回給Looper。由于此時(shí)nextPollTimeoutMillis的值為0,所以當(dāng)前面的消息處理完之后,Looper就又來(lái)取消息了。
如果當(dāng)前的時(shí)間小于Message中指定的時(shí)間,那么設(shè)置nextPollTimeoutMillis值以便下次喚醒。還有另外一種當(dāng)前已經(jīng)沒(méi)有消息了,nextPollTimeoutMillis會(huì)被設(shè)置為-1,也就是掛起線程。別急,還沒(méi)那么快呢,接著往下看。
緊接著的邏輯是判斷當(dāng)前有沒(méi)有IdleHandler,沒(méi)有的話(huà)就continue,該掛起就掛起,該延時(shí)就延時(shí),有IdleHandler的話(huà)會(huì)執(zhí)行它的queueIdle()方法。這個(gè)IdleHandler是干什么的呢?從名字應(yīng)該也能猜出個(gè)一二來(lái),這里就不再展開(kāi)講了。關(guān)于它的一些妙用可以看我之前寫(xiě)的Android 啟動(dòng)優(yōu)化之延時(shí)加載。執(zhí)行完queueIdle()方法后,會(huì)將nextPollTimeoutMillis置為0,重新看一下消息隊(duì)列中有沒(méi)有新的消息。
Handler
上面將取消息的流程都講清楚了,萬(wàn)事俱備,就差往消息隊(duì)列中添加消息了,該我們最熟悉的Handler出場(chǎng)了。Handler往隊(duì)列中添加消息,主要有兩種方式:
Handler.sendXXX(); Handler.postXXX();
第一種主要是發(fā)送Message,第二種是Runnable。無(wú)論是哪種方式,最終都會(huì)進(jìn)入到MessageQueue的enqueueMessage()方法。
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
一般情況下,我們通過(guò)Handler發(fā)送消息的時(shí)候,會(huì)通過(guò)SystemClock.uptimeMillis()獲取一個(gè)開(kāi)機(jī)時(shí)間,然后MessageQueue就會(huì)根據(jù)這個(gè)時(shí)間來(lái)對(duì)Message進(jìn)行排序。所以enqueueMessage()方法中就分了兩種情況,一種是直接可以在隊(duì)頭插入的。一種是排在中間,需要遍歷一下,然后尋一個(gè)合適的坑插入。when == 0對(duì)應(yīng)的是Handler的sendMessageAtFrontOfQueue()和postAtFrontOfQueue()方法。needWake的作用是根據(jù)情況喚醒Looper線程。
上面有一點(diǎn)還沒(méi)有講,就是Looper從MessageQueue中取出Message后,會(huì)交由Handler進(jìn)行消息的分發(fā)。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
優(yōu)先級(jí)順序是Message自帶的callback,接著是Handler自帶的callback,最后才是handleMessage()這個(gè)回調(diào)。
ThreadLocal
還記得Looper中有一個(gè)ThreadLocal吧,把它放到最后來(lái)講是因?yàn)樗梢詥为?dú)拿出來(lái)講,不想在上面干擾到整個(gè)流程。
ThreadLocal是一個(gè)數(shù)據(jù)存儲(chǔ)類(lèi),它最神奇的地方就是明明是同一個(gè)ThreadLocal對(duì)象,但是在不同線程中可以存儲(chǔ)不同的對(duì)象,比如說(shuō)在線程A中存儲(chǔ)了"Hello",而在線程B中存儲(chǔ)了"World"。它們之間互相不干擾。
在Handler機(jī)制中,由于一個(gè)Looper對(duì)應(yīng)著一個(gè)線程,所以將Looper存進(jìn)ThreadLocal最合適不過(guò)了。
ThreadLocal比價(jià)常用的就set()和get()方法。分別來(lái)看看怎么實(shí)現(xiàn)的吧。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
首先是去獲取ThreadLocalMap,找得到的話(huà)直接設(shè)置值,找不到就創(chuàng)建一個(gè)。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
看到這里,大概也能明白了。每個(gè)線程Thread中有一個(gè)ThreadLocalMap對(duì)象。通過(guò)ThreadLocal.set()方法,實(shí)際上是去獲取當(dāng)前線程中的ThreadLocalMap,線程不同,獲取到的ThreadLocalMap自然也不同。
再來(lái)看看這個(gè)ThreadLocalMap是什么來(lái)頭。看類(lèi)的注釋中有這么一句話(huà):
ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.
從注釋中可以知道這就是一個(gè)定制的HashMap,并且它的Entry類(lèi)指定了Key只能為T(mén)hreadLocal類(lèi)型的。所以直接將它看成是一個(gè)HashMap就好了。
get()方法也好理解,就是從Map中取出值而已。大概看一下就好了。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
寫(xiě)在最后
雖然在開(kāi)始寫(xiě)之前,覺(jué)得Handler機(jī)制比較簡(jiǎn)單,好像沒(méi)啥必要寫(xiě),但真正要寫(xiě)起來(lái)的時(shí)候還是得去深入了解代碼的細(xì)節(jié),然后才發(fā)現(xiàn)有些地方以前理解得也不夠好。能理解和能寫(xiě)出來(lái)讓別人理解,其實(shí)是不同的層次了。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
分享Android平板電腦上開(kāi)發(fā)應(yīng)用程序不能全屏顯示的問(wèn)題解決
在一個(gè)8寸屏的Android平板電腦上開(kāi)發(fā)應(yīng)用程序(游戲程序),開(kāi)始的時(shí)候,總是不能全屏顯示,也不知道怎么設(shè)置才可以2013-05-05
如何正確理解和使用Activity的4種啟動(dòng)模式
本文主要介紹了如何正確理解和使用Activity的4種啟動(dòng)模式。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03
Android程序開(kāi)發(fā)之使用PullToRefresh實(shí)現(xiàn)下拉刷新和上拉加載
這篇文章主要介紹了Android程序開(kāi)發(fā)之使用PullToRefresh實(shí)現(xiàn)下拉刷新和上拉加載的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
Android自定義控件之可拖動(dòng)控制的圓環(huán)控制條實(shí)例代碼
這篇文章主要介紹了Android自定義控件之可拖動(dòng)控制的圓環(huán)控制條實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
Android Webview添加網(wǎng)頁(yè)加載進(jìn)度條實(shí)例詳解
這篇文章主要介紹了Android Webview添加網(wǎng)頁(yè)加載進(jìn)度條實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2016-01-01
Flutter交互并使用小工具管理其狀態(tài)widget的state詳解
這篇文章主要為大家介紹了Flutter交互并使用小工具管理其狀態(tài)widget的state詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
詳解關(guān)于MIUI 9沉浸式狀態(tài)欄的最新適配
由于各系統(tǒng)版本的限制,沉浸式狀態(tài)欄對(duì)系統(tǒng)有要求,本篇文章主要介紹了詳解關(guān)于MIUI 9沉浸式狀態(tài)欄的最新適配,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-05-05
Android應(yīng)用中炫酷的橫向和環(huán)形進(jìn)度條的實(shí)例分享
這篇文章主要介紹了Android應(yīng)用中炫酷的橫向和圓形進(jìn)度條的實(shí)例分享,文中利用了一些GitHub上的插件進(jìn)行改寫(xiě),也是一片很好的二次開(kāi)發(fā)教學(xué),需要的朋友可以參考下2016-04-04
Android?Flutter繪制有趣的?loading加載動(dòng)畫(huà)
在網(wǎng)絡(luò)速度較慢的場(chǎng)景,一個(gè)有趣的加載會(huì)提高用戶(hù)的耐心和對(duì)?App?的好感。本篇我們利用Flutter?的?PathMetric來(lái)玩幾個(gè)有趣的?loading?效果,感興趣的可以動(dòng)手嘗試一下2022-07-07

