掌握Android Handler消息機制核心代碼
閱讀前需要對handler有一些基本的認識。這里先簡要概述一下:
一、handler基本認識
1、基本組成
完整的消息處理機制包含四個要素:
Message(消息):信息的載體MessageQueue(消息隊列):用來存儲消息的隊列Looper(消息循環(huán)):負責(zé)檢查消息隊列中是否有消息,并負責(zé)取出消息Handler(發(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ā)送消息
- 消息進入消息隊列
- 從消息隊列里取出消息
- 消息的處理
下面就一折四個步驟分析一下相關(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實現(xiàn)消息入隊的操作,唯一的區(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)用時,典型用法為:
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
若調(diào)用者沒有指定延遲時間,則消息的執(zhí)行時間即為當(dāng)前時間,也就是立即執(zhí)行。Handler所暴露的方法都遵循這種操作,除非特別指定,msg消息執(zhí)行時間就為:當(dāng)前時間加上延遲時間,本質(zhì)上是個時間戳。當(dāng)然,你也可以任意指定時間,這個時間稍后的消息插入中會用到。 代碼很簡單,就是講調(diào)用者傳遞過來的Runnable回調(diào)賦值給message(用處在消息處理中講)。 sendMessageAtTime()和sendMessageAtFrontOfQueue方法都會通過enqueueMessage方法實現(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的引用(這也是處理消息時能找到對應(yīng)handler的關(guān)鍵) - 設(shè)置消息是否為異步消息(異步消息無須排隊,通過同步屏障,插隊執(zhí)行)
- 調(diào)用
MessageQueue的enqueueMessage方法將消息加入隊列
三、消息進入消息隊列
1、入隊前的準備工作
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;
//...省略下文代碼
}
//...省略下文代碼
}
首先加一個同步鎖,接下來所有的操作都在synchronized代碼塊里運行 然后兩個if語句用來處理兩個異常情況:
- 判斷當(dāng)前
msg是否已經(jīng)被使用,若被使用,則排除異常; - 判斷消息隊列(
MessageQueue)是否正在關(guān)閉,如果是,則回收消息,返回入隊失?。╢alse)給調(diào)用者,并打印相關(guān)日志
若一切正常,通過markInUse標記消息正在使用(對應(yīng)第一個if的異常),然后設(shè)置消息發(fā)送的時間(機器系統(tǒng)時間)。 接下來開始執(zhí)行插入的相關(guān)操作
2、將消息加入隊列
繼續(xù)看enqueueMessage的代碼實現(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使用一個單向鏈表維持著消息隊列的,遵循先進先出的軟解。 分析上面這端代碼:
第一步:mMessages就是表頭,首先取出鏈表頭部。
第二步:一個判斷語句,滿足三種條件則直接將msg作為表頭:
- 若表頭為空,說明隊列內(nèi)沒有任何消息,msg直接作為鏈表頭部;
- when == 0 說明消息要立即執(zhí)行(例如
sendMessageAtFrontOfQueue方法,但一般的發(fā)送的消息除非特別指定都是發(fā)送時的時間加上延遲時間),msg插入作為鏈表頭部; when < p.when,說明要插入的消息執(zhí)行時間早于表頭,msg插入作為鏈表頭部。
第三步:通過循環(huán)不斷的比對隊列中消息的執(zhí)行時間和插入消息的執(zhí)行時間,遵循時間戳小的在前原則,將消息插入和合適的位置。
第四步:返回給調(diào)用者消息插入完成。
需要注意代碼中的needWake和nativeWake,它們是用來喚醒當(dāng)前線程的。因為在消息取出端,當(dāng)前線程會根據(jù)消息隊列的狀態(tài)進入阻塞狀態(tài),在插入時也要根據(jù)情況判斷是否需要喚醒。
接下來就是從消息隊列中取出消息了
四、從消息隊列里取出消息
依舊是先看看準備準備工作
1、準備工作
在非主線程中使用Handler,必須要做兩件事
Looper.prepare():創(chuàng)建一個LoopLooper.loop():開啟循環(huán)
我們先不管它的創(chuàng)建,直接分段看啊循環(huán)開始的代碼:首先是一些檢查和判斷工作,具體細節(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.");
}
//標記當(dāng)前l(fā)oop已經(jīng)開啟
me.mInLoop = true;
//獲取消息隊列
final MessageQueue queue = me.mQueue;
//確保權(quá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();
}
}
分步驟分析上述代碼:
步驟一:從消息隊列MessageQueue中取出消息(queue.next()可能會造成阻塞,下文會講到)
步驟二:如果消息為null,則結(jié)束循環(huán)(消息隊列中沒有消息并不會返回null,而是在隊列關(guān)閉才會返回null,下文會講到)
步驟三:拿到消息后開始消息的分發(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計數(shù)器
第三部:初始化native需要用的判斷條件,初始值為0,大于0表示還有消息等待處理(延時消息未到執(zhí)行時間),-1則表示沒有消息了。
繼續(xù)分析代碼:
Message next() {
//...省略上文代碼
for(;;){
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
//...省略下文代碼
}
}
這一段比較簡單:
開啟一個無限循環(huán)
nextPollTimeoutMillis != 0表示消息隊列里沒有消息或者所有消息都沒到執(zhí)行時間,調(diào)用nativeBinder.flushPendingCommands()方法,在進入阻塞之前跟內(nèi)核線程發(fā)送消息,以便內(nèi)核合理調(diào)度分配資源
再次調(diào)用native方法,根據(jù)nextPollTimeoutMillis判斷,當(dāng)為-1時,阻塞當(dāng)前線程(在新消息入隊時會重新進入可運行狀態(tài)),當(dāng)大于0時,說明有延時消息,nextPollTimeoutMillis會作為一個阻塞時間,也就是消息在多就后要執(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)代碼
}
}
分析一下代碼:
第一步:獲取到隊列頭部
第二步:判斷當(dāng)前消息是否為同步消息(異步消息的target為null),開啟循環(huán)直到發(fā)現(xiàn)同步消息為止
第三步:判斷消息是否為null,不為空執(zhí)行第四步,為空執(zhí)行第六步;
第四步:判斷消息執(zhí)行的時間,如果大于當(dāng)前時間,給前文提到的nextPollTimeoutMillis賦新值(當(dāng)前時間和消息執(zhí)行時間的時間差),在這一步基本完成了本次循環(huán)所有的取消息操作,如果當(dāng)前消息沒有到達執(zhí)行時間,本次循環(huán)結(jié)束,新循環(huán)開始,就會使用上文中提到的nativePollOnce(ptr, nextPollTimeoutMillis) ;方法進入阻塞狀態(tài)
第五步:從消息隊列中取出需要立即執(zhí)行的消息,結(jié)束整個循環(huán)并返回。
第六部:消息隊列中沒有消息,標記nextPollTimeoutMillis,以便下一循環(huán)進入阻塞狀態(tài)
剩下的代碼就基本上是IdleHandler的處理和執(zhí)行了,在IdleHandler小節(jié)里進行講解,這里就不展開說明了。
五、消息的處理
還記得上文中l(wèi)oop方法中的msg.target.dispatchMessage(msg) ;嗎? 消息就是通過dispatchMessage方法進行分發(fā)的。其中target是msg所持有的發(fā)送它的handler的引用,它在發(fā)送消息時被賦值。 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雖然是一個Runable,但是它并沒有調(diào)用run方法,而是直接執(zhí)行。這說明它并沒有開啟新的線程,就是作為一個方法使用(如果一開始Handler使用kotlin寫的話,此處或許就是一個高階函數(shù)了)。
六、其他關(guān)鍵點
上面講完了消息處理的主流程,接下來講一下主流程之外的關(guān)鍵點源碼
1、 Loop的創(chuàng)建
還記得上文中的說到的在非主線程中的要調(diào)用Looper.prepare()和 Looper.loop()方法嗎?這兩個方法可以理解為初始化Loop和開啟loop循環(huán),而主線程中無需這么做是因為在app啟動的main方法中,framework層已經(jīng)幫我們做了。我們分別來看這兩個方法:
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();
}
這里首先使用了一個靜態(tài)的ThreadLocal確保Loop的唯一性,同時做到線程隔離,使得一個線程有且只有一個Loop實例。接著初始化Loop,同時創(chuàng)建MessageQueue (quitAllowed設(shè)置是否允許退出)。在這一步實現(xiàn)了Loop和消息隊列的關(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;
}
可以看到,每個Thread都持有一個ThreadLocalMap ,它和HashMap使用相同的數(shù)據(jù)結(jié)構(gòu),使用ThreadLocal作為key值,value就是Loop實例。不難發(fā)現(xiàn):我們只能獲取到當(dāng)前線程的Loop實例。
Loop也提供了主線程中初始化的辦法prepareMainLooper ,但是這個方法明確說明不允許調(diào)用,只能由系統(tǒng)自己調(diào)用。 這基本上就是Loop創(chuàng)建的關(guān)鍵了,也是在這里完成了Loop和消息隊列以及線成之間的關(guān)聯(lián)。
2、Handler的創(chuàng)建
Handler的構(gòu)造函數(shù)有以下幾個:
- 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)
其中第一個和第二個已經(jīng)被廢棄了,實際上第1~5個構(gòu)造方法都是通過調(diào)用public Handler(Callback callback, boolean async)或public Handler(Looper looper, Callback callback, boolean async)實現(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) {
//注意這個異常,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;
}
兩個方法最大的區(qū)別就是一個使用傳遞過來的loop,一個直接使用當(dāng)前線程的loop,然后就是相同的一些初始化操作了。這里就出現(xiàn)了一個關(guān)鍵點handler處理消息所處的線程和創(chuàng)建它的線程無關(guān),而是和創(chuàng)建它時loop的線程有關(guān)的。
這也是Handler能實現(xiàn)線程切換的原因所在: handler的執(zhí)行跟創(chuàng)建handler的線程無關(guān),跟創(chuàng)建looper的線程相關(guān),假如在子線程中創(chuàng)建一個Handler,但是Handler相關(guān)的Looper是主線程的,那么如果handler執(zhí)行post一個runnable,或者sendMessage,最終的handle Message都是在主線程中執(zhí)行的。
3、Message的創(chuàng)建、回收和復(fù)用機制
我們可以直接使用new關(guān)鍵字去創(chuàng)建一個Message:

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

可以看到,四大組件的生命周期甚至內(nèi)存不足,都有handler在參與。
這也解釋了為什么在主線程執(zhí)行耗時任務(wù)會導(dǎo)致UI卡頓或者ANR:因為所有主線程也就是UI線程的邏輯代碼都是在組件的生命周期里執(zhí)行的,而生命周期又受到Handler的事件體系的控制,當(dāng)在任意生命周期做中執(zhí)行耗時操作,這會導(dǎo)致消息隊列MessageQueue中后面的消息無法得到及時的處理,就會造成延遲,直至視覺上的卡頓,嚴重的則會進一步觸發(fā)ANR的發(fā)生。
到此這篇關(guān)于掌握Android Handler消息機制核心代碼的文章就介紹到這了,更多相關(guān)Android Handler消息機制核心代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android學(xué)習(xí)教程之分類側(cè)滑菜單(5)
這篇文章主要為大家詳細介紹了Android學(xué)習(xí)教程之分類側(cè)滑菜單的具體代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
淺談Android studio 生成apk文件時的 key store path 的問題
這篇文章主要介紹了淺談Android studio 生成apk文件時的 key store path 的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03

