掌握Android Handler消息機(jī)制核心代碼
閱讀前需要對handler有一些基本的認(rèn)識。這里先簡要概述一下:
一、handler基本認(rèn)識
1、基本組成
完整的消息處理機(jī)制包含四個(gè)要素:
Message(消息):信息的載體MessageQueue(消息隊(duì)列):用來存儲(chǔ)消息的隊(duì)列Looper(消息循環(huán)):負(fù)責(zé)檢查消息隊(duì)列中是否有消息,并負(fù)責(zé)取出消息Handler(發(fā)送和處理消息):把消息加入消息隊(duì)列中,并負(fù)責(zé)分發(fā)和處理消息
2、基本使用方法
Handler的簡單用法如下:
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
Message message = new Message();
handler.sendMessage(message);
注意在非主線程中的要調(diào)用Looper.prepare()和 Looper.loop()方法
3、工作流程
其工作流程如下圖所示:

工作流程 從發(fā)送消息到接收消息的流程概括如下:
- 發(fā)送消息
- 消息進(jìn)入消息隊(duì)列
- 從消息隊(duì)列里取出消息
- 消息的處理
下面就一折四個(gè)步驟分析一下相關(guān)源碼:
二、發(fā)送消息
handle有兩類發(fā)送消息的方法,它們在本質(zhì)上并沒有什么區(qū)別:
sendXxxx()
- boolean sendMessage(Message msg)
- boolean sendEmptyMessage(int what)
- boolean sendEmptyMessageDelayed(int what, long delayMillis)
- boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
- boolean sendMessageDelayed(Message msg, long delayMillis)
- boolean sendMessageAtTime(Message msg, long uptimeMillis)
- boolean sendMessageAtFrontOfQueue(Message msg)
postXxxx()
- boolean post(Runnable r)
- boolean postAtFrontOfQueue(Runnable r)
- boolean postAtTime(Runnable r, long uptimeMillis)
- boolean postAtTime(Runnable r, Object token, long uptimeMillis)
- boolean postDelayed(Runnable r, long delayMillis)
- boolean postDelayed(Runnable r, Object token, long delayMillis)
這里不分析具體的方法特性,它們最終都是通過調(diào)用sendMessageAtTime()或者sendMessageAtFrontOfQueue實(shí)現(xiàn)消息入隊(duì)的操作,唯一的區(qū)別就是post系列方法在消息發(fā)送前調(diào)用了getPostMessage方法:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
需要注意的是:sendMessageAtTime()再被其他sendXxx調(diào)用時(shí),典型用法為:
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
若調(diào)用者沒有指定延遲時(shí)間,則消息的執(zhí)行時(shí)間即為當(dāng)前時(shí)間,也就是立即執(zhí)行。Handler所暴露的方法都遵循這種操作,除非特別指定,msg消息執(zhí)行時(shí)間就為:當(dāng)前時(shí)間加上延遲時(shí)間,本質(zhì)上是個(gè)時(shí)間戳。當(dāng)然,你也可以任意指定時(shí)間,這個(gè)時(shí)間稍后的消息插入中會(huì)用到。 代碼很簡單,就是講調(diào)用者傳遞過來的Runnable回調(diào)賦值給message(用處在消息處理中講)。 sendMessageAtTime()和sendMessageAtFrontOfQueue方法都會(huì)通過enqueueMessage方法實(shí)現(xiàn)消息的入棧:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
代碼很簡單,主要有以下操作:
- 讓
message持有發(fā)送它的Handler的引用(這也是處理消息時(shí)能找到對應(yīng)handler的關(guān)鍵) - 設(shè)置消息是否為異步消息(異步消息無須排隊(duì),通過同步屏障,插隊(duì)執(zhí)行)
- 調(diào)用
MessageQueue的enqueueMessage方法將消息加入隊(duì)列
三、消息進(jìn)入消息隊(duì)列
1、入隊(duì)前的準(zhǔn)備工作
enqueueMessage方法是消息加入到MessageQueue的關(guān)鍵,下面分段來分析一下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//...省略下文代碼
}
這端代碼很簡單:判斷message的target是否為空,為空則拋出異常。其中,target就是上文Handler.enqueueMessage里提到到Handler引用。 接下來下來開始判斷和處理消息
boolean enqueueMessage(Message msg, long when) {
//...省略上文代碼
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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;
//...省略下文代碼
}
//...省略下文代碼
}
首先加一個(gè)同步鎖,接下來所有的操作都在synchronized代碼塊里運(yùn)行 然后兩個(gè)if語句用來處理兩個(gè)異常情況:
- 判斷當(dāng)前
msg是否已經(jīng)被使用,若被使用,則排除異常; - 判斷消息隊(duì)列(
MessageQueue)是否正在關(guān)閉,如果是,則回收消息,返回入隊(duì)失?。╢alse)給調(diào)用者,并打印相關(guān)日志
若一切正常,通過markInUse標(biāo)記消息正在使用(對應(yīng)第一個(gè)if的異常),然后設(shè)置消息發(fā)送的時(shí)間(機(jī)器系統(tǒng)時(shí)間)。 接下來開始執(zhí)行插入的相關(guān)操作
2、將消息加入隊(duì)列
繼續(xù)看enqueueMessage的代碼實(shí)現(xiàn)
boolean enqueueMessage(Message msg, long when) {
//...省略上文代碼
synchronized (this) {
//...省略上文代碼
//步驟1
Message p = mMessages;
boolean needWake;
//步驟2
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//步驟3
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
//步驟4
return true;
}
首先說明MessageQueue使用一個(gè)單向鏈表維持著消息隊(duì)列的,遵循先進(jìn)先出的軟解。 分析上面這端代碼:
第一步:mMessages就是表頭,首先取出鏈表頭部。
第二步:一個(gè)判斷語句,滿足三種條件則直接將msg作為表頭:
- 若表頭為空,說明隊(duì)列內(nèi)沒有任何消息,msg直接作為鏈表頭部;
- when == 0 說明消息要立即執(zhí)行(例如
sendMessageAtFrontOfQueue方法,但一般的發(fā)送的消息除非特別指定都是發(fā)送時(shí)的時(shí)間加上延遲時(shí)間),msg插入作為鏈表頭部; when < p.when,說明要插入的消息執(zhí)行時(shí)間早于表頭,msg插入作為鏈表頭部。
第三步:通過循環(huán)不斷的比對隊(duì)列中消息的執(zhí)行時(shí)間和插入消息的執(zhí)行時(shí)間,遵循時(shí)間戳小的在前原則,將消息插入和合適的位置。
第四步:返回給調(diào)用者消息插入完成。
需要注意代碼中的needWake和nativeWake,它們是用來喚醒當(dāng)前線程的。因?yàn)樵谙⑷〕龆?,?dāng)前線程會(huì)根據(jù)消息隊(duì)列的狀態(tài)進(jìn)入阻塞狀態(tài),在插入時(shí)也要根據(jù)情況判斷是否需要喚醒。
接下來就是從消息隊(duì)列中取出消息了
四、從消息隊(duì)列里取出消息
依舊是先看看準(zhǔn)備準(zhǔn)備工作
1、準(zhǔn)備工作
在非主線程中使用Handler,必須要做兩件事
Looper.prepare():創(chuàng)建一個(gè)LoopLooper.loop():開啟循環(huán)
我們先不管它的創(chuàng)建,直接分段看啊循環(huán)開始的代碼:首先是一些檢查和判斷工作,具體細(xì)節(jié)在代碼中已注釋
public static void loop() {
//獲取loop對象
final Looper me = myLooper();
if (me == null) {
//若loop為空,則拋出異常終止操作
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
//loop循環(huán)重復(fù)開啟
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
//標(biāo)記當(dāng)前l(fā)oop已經(jīng)開啟
me.mInLoop = true;
//獲取消息隊(duì)列
final MessageQueue queue = me.mQueue;
//確保權(quán)限檢查基于本地進(jìn)程,
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
//...省略下文代碼
}
2、loop中的操作
接下來就是循環(huán)的正式開啟,精簡關(guān)鍵代碼:
public static void loop() {
//...省略上文代碼
for (;;) {
//步驟一
Message msg = queue.next();
if (msg == null) {
//步驟二
return;
}
//...省略非核心代碼
try {
//步驟三
msg.target.dispatchMessage(msg);
//...
} catch (Exception exception) {
//...省略非核心代碼
} finally {
//...省略非核心代碼
}
//步驟四
msg.recycleUnchecked();
}
}
分步驟分析上述代碼:
步驟一:從消息隊(duì)列MessageQueue中取出消息(queue.next()可能會(huì)造成阻塞,下文會(huì)講到)
步驟二:如果消息為null,則結(jié)束循環(huán)(消息隊(duì)列中沒有消息并不會(huì)返回null,而是在隊(duì)列關(guān)閉才會(huì)返回null,下文會(huì)講到)
步驟三:拿到消息后開始消息的分發(fā)
步驟四:回收已經(jīng)分發(fā)了的消息,然后開始新一輪的循環(huán)取數(shù)據(jù)
2.1 MessageQueue的next方法
我們先只看第一步消息的取出,其他的在稍后小節(jié)再看,queue.next()代碼較多,依舊分段來看
Message next() {
//步驟一
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//步驟二
int pendingIdleHandlerCount = -1;
//步驟三
int nextPollTimeoutMillis = 0;
//...省略下文代碼
}
第一步:如果消息循環(huán)已經(jīng)退出并且已經(jīng)disposed之后,直接返回null,對應(yīng)上文中Loop通過queue.next()取消息拿到null后退出循環(huán)
第二部:初始化IdleHandler計(jì)數(shù)器
第三部:初始化native需要用的判斷條件,初始值為0,大于0表示還有消息等待處理(延時(shí)消息未到執(zhí)行時(shí)間),-1則表示沒有消息了。
繼續(xù)分析代碼:
Message next() {
//...省略上文代碼
for(;;){
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
//...省略下文代碼
}
}
這一段比較簡單:
開啟一個(gè)無限循環(huán)
nextPollTimeoutMillis != 0表示消息隊(duì)列里沒有消息或者所有消息都沒到執(zhí)行時(shí)間,調(diào)用nativeBinder.flushPendingCommands()方法,在進(jìn)入阻塞之前跟內(nèi)核線程發(fā)送消息,以便內(nèi)核合理調(diào)度分配資源
再次調(diào)用native方法,根據(jù)nextPollTimeoutMillis判斷,當(dāng)為-1時(shí),阻塞當(dāng)前線程(在新消息入隊(duì)時(shí)會(huì)重新進(jìn)入可運(yùn)行狀態(tài)),當(dāng)大于0時(shí),說明有延時(shí)消息,nextPollTimeoutMillis會(huì)作為一個(gè)阻塞時(shí)間,也就是消息在多就后要執(zhí)行。
繼續(xù)看代碼:
Message next() {
//...省略上文代碼
for(;;){
//...省略上文代碼
//開啟同步鎖
synchronized (this) {
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 {
//步驟五
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 {
//步驟六
nextPollTimeoutMillis = -1;
}
}
//...省略下文IdleHandler相關(guān)代碼
}
}
分析一下代碼:
第一步:獲取到隊(duì)列頭部
第二步:判斷當(dāng)前消息是否為同步消息(異步消息的target為null),開啟循環(huán)直到發(fā)現(xiàn)同步消息為止
第三步:判斷消息是否為null,不為空執(zhí)行第四步,為空執(zhí)行第六步;
第四步:判斷消息執(zhí)行的時(shí)間,如果大于當(dāng)前時(shí)間,給前文提到的nextPollTimeoutMillis賦新值(當(dāng)前時(shí)間和消息執(zhí)行時(shí)間的時(shí)間差),在這一步基本完成了本次循環(huán)所有的取消息操作,如果當(dāng)前消息沒有到達(dá)執(zhí)行時(shí)間,本次循環(huán)結(jié)束,新循環(huán)開始,就會(huì)使用上文中提到的nativePollOnce(ptr, nextPollTimeoutMillis) ;方法進(jìn)入阻塞狀態(tài)
第五步:從消息隊(duì)列中取出需要立即執(zhí)行的消息,結(jié)束整個(gè)循環(huán)并返回。
第六部:消息隊(duì)列中沒有消息,標(biāo)記nextPollTimeoutMillis,以便下一循環(huán)進(jìn)入阻塞狀態(tài)
剩下的代碼就基本上是IdleHandler的處理和執(zhí)行了,在IdleHandler小節(jié)里進(jìn)行講解,這里就不展開說明了。
五、消息的處理
還記得上文中l(wèi)oop方法中的msg.target.dispatchMessage(msg) ;嗎? 消息就是通過dispatchMessage方法進(jìn)行分發(fā)的。其中target是msg所持有的發(fā)送它的handler的引用,它在發(fā)送消息時(shí)被賦值。 dispatchMessage的源碼如下:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
代碼很簡單,通過判斷Message是否有Runable來決定是調(diào)用callback還是調(diào)用handleMessage方法,交給你定義的Handler去處理。需要注意的是,callback雖然是一個(gè)Runable,但是它并沒有調(diào)用run方法,而是直接執(zhí)行。這說明它并沒有開啟新的線程,就是作為一個(gè)方法使用(如果一開始Handler使用kotlin寫的話,此處或許就是一個(gè)高階函數(shù)了)。
六、其他關(guān)鍵點(diǎn)
上面講完了消息處理的主流程,接下來講一下主流程之外的關(guān)鍵點(diǎn)源碼
1、 Loop的創(chuàng)建
還記得上文中的說到的在非主線程中的要調(diào)用Looper.prepare()和 Looper.loop()方法嗎?這兩個(gè)方法可以理解為初始化Loop和開啟loop循環(huán),而主線程中無需這么做是因?yàn)樵赼pp啟動(dòng)的main方法中,framework層已經(jīng)幫我們做了。我們分別來看這兩個(gè)方法:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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è)靜態(tài)的ThreadLocal確保Loop的唯一性,同時(shí)做到線程隔離,使得一個(gè)線程有且只有一個(gè)Loop實(shí)例。接著初始化Loop,同時(shí)創(chuàng)建MessageQueue (quitAllowed設(shè)置是否允許退出)。在這一步實(shí)現(xiàn)了Loop和消息隊(duì)列的關(guān)聯(lián)。
需要注意的是,Loop的構(gòu)造方式是私有的,我們只能通過prepare 區(qū)創(chuàng)建,然后通過myLooper方法去獲取。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
ThreadLocal.get源碼:
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到,每個(gè)Thread都持有一個(gè)ThreadLocalMap ,它和HashMap使用相同的數(shù)據(jù)結(jié)構(gòu),使用ThreadLocal作為key值,value就是Loop實(shí)例。不難發(fā)現(xiàn):我們只能獲取到當(dāng)前線程的Loop實(shí)例。
Loop也提供了主線程中初始化的辦法prepareMainLooper ,但是這個(gè)方法明確說明不允許調(diào)用,只能由系統(tǒng)自己調(diào)用。 這基本上就是Loop創(chuàng)建的關(guān)鍵了,也是在這里完成了Loop和消息隊(duì)列以及線成之間的關(guān)聯(lián)。
2、Handler的創(chuàng)建
Handler的構(gòu)造函數(shù)有以下幾個(gè):
- public Handler()
- public Handler(Callback callback)
- public Handler(Looper looper)
- public Handler(Looper looper, Callback callback)
- public Handler(boolean async)
- public Handler(Callback callback, boolean async)
- public Handler(Looper looper, Callback callback, boolean async)
其中第一個(gè)和第二個(gè)已經(jīng)被廢棄了,實(shí)際上第1~5個(gè)構(gòu)造方法都是通過調(diào)用public Handler(Callback callback, boolean async)或public Handler(Looper looper, Callback callback, boolean async)實(shí)現(xiàn)的,它們的源碼如下:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
//注意這個(gè)異常,loop不能為空的,首先要Looper.prepare();
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
兩個(gè)方法最大的區(qū)別就是一個(gè)使用傳遞過來的loop,一個(gè)直接使用當(dāng)前線程的loop,然后就是相同的一些初始化操作了。這里就出現(xiàn)了一個(gè)關(guān)鍵點(diǎn)handler處理消息所處的線程和創(chuàng)建它的線程無關(guān),而是和創(chuàng)建它時(shí)loop的線程有關(guān)的。
這也是Handler能實(shí)現(xiàn)線程切換的原因所在: handler的執(zhí)行跟創(chuàng)建handler的線程無關(guān),跟創(chuàng)建looper的線程相關(guān),假如在子線程中創(chuàng)建一個(gè)Handler,但是Handler相關(guān)的Looper是主線程的,那么如果handler執(zhí)行post一個(gè)runnable,或者sendMessage,最終的handle Message都是在主線程中執(zhí)行的。
3、Message的創(chuàng)建、回收和復(fù)用機(jī)制
我們可以直接使用new關(guān)鍵字去創(chuàng)建一個(gè)Message:

Message message = new Message(); 但是這種做法并不被推薦,官方提供了以下幾個(gè)方法供用戶創(chuàng)建message:
這些方法除了形參有些區(qū)別,用來給message不同的成員變量賦值之外,本質(zhì)上都是通過 obtain()來創(chuàng)建Message:
public static final Object sPoolSync = new Object();
Message next;
private static Message sPool;
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();
}
這端代碼很簡單,Message內(nèi)部維持了一個(gè)單線鏈表,使用sPool作為頭部,用來存儲(chǔ)Message實(shí)體??梢园l(fā)現(xiàn),每次調(diào)用者需要一個(gè)新的消息的時(shí)候,都會(huì)先從鏈表的頭部去取,有消息就直接返回。沒有消息才會(huì)創(chuàng)建一個(gè)新的消息。
那么這個(gè)鏈表是在何時(shí)插入消息的呢?接下來看Message的回收:
public static final Object sPoolSync = new Object();
private static final int MAX_POOL_SIZE = 50;
void recycleUnchecked() {
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++;
}
}
}
該方法在每次消息從MessageQueue 隊(duì)列取出分發(fā)時(shí)都會(huì)被調(diào)用,就是在上文提到的Loop.loop()方法里。 代碼也很簡單,首先將Message的成員變量還原到初始狀態(tài),然后采用頭插法將回收后的消息插入到鏈表之中(限制了最大容量為50)。而且插入和取出的操作,都是使用的同一把鎖,保證了安全性。
注意插入和取出都是對鏈表的頭部操作,這里和消息隊(duì)列里就不太一樣了。雖然都是使用單向鏈表,回收時(shí)使用頭插和頭取,先進(jìn)后出,是個(gè)棧。而在MessageQueue里是個(gè)隊(duì)列,遵循先進(jìn)先出的原則,而且插入的時(shí)候是根據(jù)消息的狀態(tài)確定位置,并沒有固定的插入節(jié)點(diǎn)。
這是一個(gè)典型的享元模式,最大的特點(diǎn)就是復(fù)用對象,避免重復(fù)創(chuàng)建導(dǎo)致的內(nèi)存浪費(fèi)。這也是為什么android官方推薦使用這種方式創(chuàng)建消息的原因:就是為了提高效率減少性能開銷。
4、 IdleHandler
IdleHandler 的定義很簡單,就是一個(gè)定義在MessageQueue里的接口:
public static interface IdleHandler {
boolean queueIdle();
}
根據(jù)官方的解釋,在 Looper循環(huán)的過程中,每當(dāng)消息隊(duì)列出現(xiàn)空閑:沒有消息或者沒到任何消息的執(zhí)行時(shí)間需要滯后執(zhí)行的時(shí)候,queueIdle 方法就會(huì)被執(zhí)行,而其返回的布爾值標(biāo)識IdleHandler 是永久的還是一次性的:
ture:永久的,一旦空閑,就會(huì)執(zhí)行false:一次性的,只有第一次空閑時(shí)會(huì)執(zhí)行
它的使用方法如下:
Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return true;
}
});
看一下addIdleHandler 的實(shí)現(xiàn)
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
代碼很簡單,就是一個(gè)List來保存接口的實(shí)現(xiàn)。那么它是怎么實(shí)現(xiàn)在出現(xiàn)空閑時(shí)調(diào)用呢?
還記得在上文MessageQueue的next方法中省略的代碼嗎?
Message next() {
//...省略不相關(guān)代碼
//步驟一
int pendingIdleHandlerCount = -1; // -1 只存在第一次迭代中
for (;;) {
//...省略不相關(guān)代碼
//步驟二
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);
}
//步驟五
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;
}
}
接下來分步驟分析一下代碼:
第一步:在取消息的循環(huán)開始前創(chuàng)建局部變量pendingIdleHandlerCount用來記錄IdleHandler的數(shù)量,只在循環(huán)開始時(shí)為-1;
第二步:當(dāng)沒有取到Message消息(沒有消息或者沒有可立即執(zhí)行的消息,也沒有進(jìn)去阻塞狀態(tài))或者消息需要延后執(zhí)行,為pendingIdleHandlerCount 賦值記錄IdleHandler的數(shù)量;
第三步:判斷IdleHandler的數(shù)量,如果沒有IdleHandler,則直接結(jié)束當(dāng)前循環(huán),并標(biāo)記循環(huán)可進(jìn)入掛起狀態(tài)。
第四步:判斷是否是第一次,初始化IdleHandler 的List
第五步:開始遍歷所有的IdleHandler
第六步:依次執(zhí)行IdleHandler 的queueIdle方法
第七部:根據(jù)各IdleHandler 的queueIdle的返回值判斷IdleHandler 是永久的還是一次性的,將非永久的從數(shù)組里移除;
第八步:修改IdleHandler 的數(shù)量信息pendingIdleHandlerCount ,避免IdleHandler 重復(fù)執(zhí)行。
這就是IdleHandler 的核心原理,它只在消息隊(duì)列為空時(shí),或者消息隊(duì)列的頭部消息為延時(shí)消息時(shí)才會(huì)被觸發(fā)。當(dāng)消息隊(duì)列頭部為延時(shí)消息時(shí),它只會(huì)觸發(fā)一次哦。在前文中取消息的小節(jié)中我們講過:延時(shí)消息在結(jié)束當(dāng)前循環(huán)后進(jìn)入下一路循環(huán)會(huì)觸發(fā)阻塞。
5、Handler在Framework層的應(yīng)用
不知道你有沒有想過為什么Android在主線程里直接幫你調(diào)用了Looper.prepare()和 Looper.loop()方法,難道只是為了你使用方便嗎?這豈不是有點(diǎn)殺雞用牛刀的感覺?
事實(shí)上遠(yuǎn)沒有這么簡單,如果你看一下framework的源碼你就會(huì)發(fā)現(xiàn),整個(gè)android app的運(yùn)轉(zhuǎn)都是基于Handler進(jìn)行的。四大組件的運(yùn)行,它們生命周期也是基于Handler事件模型進(jìn)行的,以及點(diǎn)擊事件等。這一切均是由Android系統(tǒng)框架層產(chǎn)生相應(yīng)的message再交由一個(gè)Handler進(jìn)行處理的。這個(gè)Handler就是ActivityThread內(nèi)部類H,貼一段它的代碼截圖

可以看到,四大組件的生命周期甚至內(nèi)存不足,都有handler在參與。
這也解釋了為什么在主線程執(zhí)行耗時(shí)任務(wù)會(huì)導(dǎo)致UI卡頓或者ANR:因?yàn)樗兄骶€程也就是UI線程的邏輯代碼都是在組件的生命周期里執(zhí)行的,而生命周期又受到Handler的事件體系的控制,當(dāng)在任意生命周期做中執(zhí)行耗時(shí)操作,這會(huì)導(dǎo)致消息隊(duì)列MessageQueue中后面的消息無法得到及時(shí)的處理,就會(huì)造成延遲,直至視覺上的卡頓,嚴(yán)重的則會(huì)進(jìn)一步觸發(fā)ANR的發(fā)生。
到此這篇關(guān)于掌握Android Handler消息機(jī)制核心代碼的文章就介紹到這了,更多相關(guān)Android Handler消息機(jī)制核心代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Android實(shí)現(xiàn)ListView圓角效果
這篇文章主要為大家詳細(xì)介紹了基于Android實(shí)現(xiàn)ListView圓角效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06
Android學(xué)習(xí)教程之分類側(cè)滑菜單(5)
這篇文章主要為大家詳細(xì)介紹了Android學(xué)習(xí)教程之分類側(cè)滑菜單的具體代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android如何從實(shí)現(xiàn)到封裝一個(gè)MVP詳解
原生的 MVC 框架遇到大規(guī)模的應(yīng)用,就會(huì)變得代碼難讀,不好維護(hù),無法測試的囧境。因此,Android 開發(fā)方面也有很多對應(yīng)的框架來解決這些問題。所以這篇文章主要給大家介紹了關(guān)于Android如何從實(shí)現(xiàn)到封裝一個(gè)MVP的相關(guān)資料,需要的朋友可以參考下。2017-09-09
Android 校驗(yàn)email是否合法實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 校驗(yàn)email是否合法實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01
淺談Android studio 生成apk文件時(shí)的 key store path 的問題
這篇文章主要介紹了淺談Android studio 生成apk文件時(shí)的 key store path 的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android中SeekBar拖動(dòng)條使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android中SeekBar拖動(dòng)條使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03

