掌握Android Handler消息機制核心代碼
閱讀前需要對handler有一些基本的認(rèn)識。這里先簡要概述一下:
一、handler基本認(rèn)識
1、基本組成
完整的消息處理機制包含四個要素:
Message
(消息):信息的載體MessageQueue
(消息隊列):用來存儲消息的隊列Looper
(消息循環(huán)):負(fù)責(zé)檢查消息隊列中是否有消息,并負(fù)責(zé)取出消息Handler
(發(fā)送和處理消息):把消息加入消息隊列中,并負(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、入隊前的準(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; //...省略下文代碼 } //...省略下文代碼 }
首先加一個同步鎖,接下來所有的操作都在synchronized
代碼塊里運行 然后兩個if語句用來處理兩個異常情況:
- 判斷當(dāng)前
msg
是否已經(jīng)被使用,若被使用,則排除異常; - 判斷消息隊列(
MessageQueue
)是否正在關(guān)閉,如果是,則回收消息,返回入隊失敗(false)給調(diào)用者,并打印相關(guān)日志
若一切正常,通過markInUse
標(biāo)記消息正在使用(對應(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ù)情況判斷是否需要喚醒。
接下來就是從消息隊列中取出消息了
四、從消息隊列里取出消息
依舊是先看看準(zhǔn)備準(zhǔn)備工作
1、準(zhǔn)備工作
在非主線程中使用Handler,必須要做兩件事
Looper.prepare()
:創(chuàng)建一個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; //獲取消息隊列 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)并返回。
第六部:消息隊列中沒有消息,標(biāo)記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
實體。可以發(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í)行,而其返回的布爾值標(biāo)識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),并標(biāo)記循環(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()
方法,難道只是為了你使用方便嗎?這豈不是有點殺雞用牛刀的感覺?
事實上遠(yuǎn)沒有這么簡單,如果你看一下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)致消息隊列Messag
eQueue中后面的消息無法得到及時的處理,就會造成延遲,直至視覺上的卡頓,嚴(yán)重的則會進一步觸發(fā)ANR的發(fā)生。
到此這篇關(guān)于掌握Android Handler消息機制核心代碼的文章就介紹到這了,更多相關(guān)Android Handler消息機制核心代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android 手機SD卡讀寫操作(以txt文本為例)實現(xiàn)步驟
要完成SD卡讀寫操作首先對manifest注冊SD卡讀寫權(quán)限其次是創(chuàng)建一個對SD卡中文件讀寫的類寫一個用于檢測讀寫功能的的布局然后就是UI的類了,感興趣的朋友可以參考下,希望可以幫助到你2013-02-02Android第三方文件選擇器aFileChooser使用方法詳解
這篇文章主要介紹了Android第三方文件選擇器aFileChooser的使用方法詳解,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Android平臺基于Pull方式對XML文件解析與寫入方法詳解
這篇文章主要介紹了Android平臺基于Pull方式對XML文件解析與寫入方法,結(jié)合實例形式分析了Android基于Pull方式對xml文件解析及寫入操作的步驟、原理與操作技巧,需要的朋友可以參考下2016-10-10Android RecyclerView自定義上拉和下拉刷新效果
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView自定義上拉和下拉刷新效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02Android實現(xiàn)點擊AlertDialog上按鈕時不關(guān)閉對話框的方法
這篇文章主要介紹了Android實現(xiàn)點擊AlertDialog上按鈕時不關(guān)閉對話框的方法,涉及設(shè)置監(jiān)聽的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-02-02android 解決ViewPager加載大量圖片內(nèi)存溢出問題
本篇文章是介紹 android 解決ViewPager加載大量圖片內(nèi)存溢出問題,并附有代碼實例,希望能幫到有需要的小伙伴2016-07-07