掌握Android Handler消息機(jī)制核心代碼
閱讀前需要對(duì)handler有一些基本的認(rèn)識(shí)。這里先簡(jiǎn)要概述一下:
一、handler基本認(rèn)識(shí)
1、基本組成
完整的消息處理機(jī)制包含四個(gè)要素:
Message
(消息):信息的載體MessageQueue
(消息隊(duì)列):用來(lái)存儲(chǔ)消息的隊(duì)列Looper
(消息循環(huán)):負(fù)責(zé)檢查消息隊(duì)列中是否有消息,并負(fù)責(zé)取出消息Handler
(發(fā)送和處理消息):把消息加入消息隊(duì)列中,并負(fù)責(zé)分發(fā)和處理消息
2、基本使用方法
Handler的簡(jiǎn)單用法如下:
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ā)送消息的方法,它們?cè)诒举|(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ì)用到。 代碼很簡(jiǎn)單,就是講調(diào)用者傳遞過來(lái)的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); }
代碼很簡(jiǎn)單,主要有以下操作:
- 讓
message
持有發(fā)送它的Handler
的引用(這也是處理消息時(shí)能找到對(duì)應(yīng)handler的關(guān)鍵) - 設(shè)置消息是否為異步消息(異步消息無(wú)須排隊(duì),通過同步屏障,插隊(duì)執(zhí)行)
- 調(diào)用
MessageQueue
的enqueueMessage
方法將消息加入隊(duì)列
三、消息進(jìn)入消息隊(duì)列
1、入隊(duì)前的準(zhǔn)備工作
enqueueMessage
方法是消息加入到MessageQueue
的關(guān)鍵,下面分段來(lái)分析一下:
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } //...省略下文代碼 }
這端代碼很簡(jiǎn)單:判斷message
的target
是否為空,為空則拋出異常。其中,target
就是上文Handler.enqueueMessage
里提到到Handler
引用。 接下來(lái)下來(lái)開始判斷和處理消息
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è)同步鎖,接下來(lái)所有的操作都在synchronized
代碼塊里運(yùn)行 然后兩個(gè)if語(yǔ)句用來(lái)處理兩個(gè)異常情況:
- 判斷當(dāng)前
msg
是否已經(jīng)被使用,若被使用,則排除異常; - 判斷消息隊(duì)列(
MessageQueue
)是否正在關(guān)閉,如果是,則回收消息,返回入隊(duì)失?。╢alse)給調(diào)用者,并打印相關(guān)日志
若一切正常,通過markInUse
標(biāo)記消息正在使用(對(duì)應(yīng)第一個(gè)if的異常),然后設(shè)置消息發(fā)送的時(shí)間(機(jī)器系統(tǒng)時(shí)間)。 接下來(lái)開始執(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; }
首先說(shuō)明MessageQueue
使用一個(gè)單向鏈表維持著消息隊(duì)列的,遵循先進(jìn)先出的軟解。 分析上面這端代碼:
第一步:mMessages
就是表頭,首先取出鏈表頭部。
第二步:一個(gè)判斷語(yǔ)句,滿足三種條件則直接將msg作為表頭:
- 若表頭為空,說(shuō)明隊(duì)列內(nèi)沒有任何消息,msg直接作為鏈表頭部;
- when == 0 說(shuō)明消息要立即執(zhí)行(例如
sendMessageAtFrontOfQueue
方法,但一般的發(fā)送的消息除非特別指定都是發(fā)送時(shí)的時(shí)間加上延遲時(shí)間),msg插入作為鏈表頭部; when < p.when
,說(shuō)明要插入的消息執(zhí)行時(shí)間早于表頭,msg插入作為鏈表頭部。
第三步:通過循環(huán)不斷的比對(duì)隊(duì)列中消息的執(zhí)行時(shí)間和插入消息的執(zhí)行時(shí)間,遵循時(shí)間戳小的在前原則,將消息插入和合適的位置。
第四步:返回給調(diào)用者消息插入完成。
需要注意代碼中的needWake
和nativeWake
,它們是用來(lái)喚醒當(dāng)前線程的。因?yàn)樵谙⑷〕龆?,?dāng)前線程會(huì)根據(jù)消息隊(duì)列的狀態(tài)進(jìn)入阻塞狀態(tài),在插入時(shí)也要根據(jù)情況判斷是否需要喚醒。
接下來(lái)就是從消息隊(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對(duì)象 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中的操作
接下來(lái)就是循環(huán)的正式開啟,精簡(jiǎ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()
代碼較多,依舊分段來(lái)看
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,對(duì)應(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); //...省略下文代碼 } }
這一段比較簡(jiǎn)單:
開啟一個(gè)無(wú)限循環(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í),說(shuō)明有延時(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,不為空?qǐng)?zhí)行第四步,為空?qǐng)?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)行講解,這里就不展開說(shuō)明了。
五、消息的處理
還記得上文中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); } }
代碼很簡(jiǎn)單,通過判斷Message
是否有Runable
來(lái)決定是調(diào)用callback
還是調(diào)用handleMessage
方法,交給你定義的Handler去處理。需要注意的是,callback
雖然是一個(gè)Runable
,但是它并沒有調(diào)用run方法,而是直接執(zhí)行。這說(shuō)明它并沒有開啟新的線程,就是作為一個(gè)方法使用(如果一開始Handler使用kotlin寫的話,此處或許就是一個(gè)高階函數(shù)了)。
六、其他關(guān)鍵點(diǎn)
上面講完了消息處理的主流程,接下來(lái)講一下主流程之外的關(guān)鍵點(diǎn)源碼
1、 Loop的創(chuàng)建
還記得上文中的說(shuō)到的在非主線程中的要調(diào)用Looper.prepare()
和 Looper.loop()
方法嗎?這兩個(gè)方法可以理解為初始化Loop和開啟loop
循環(huán),而主線程中無(wú)需這么做是因?yàn)樵赼pp啟動(dòng)的main方法中,framework
層已經(jīng)幫我們做了。我們分別來(lái)看這兩個(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è)方法明確說(shuō)明不允許調(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è)使用傳遞過來(lái)的loop,一個(gè)直接使用當(dāng)前線程的loop,然后就是相同的一些初始化操作了。這里就出現(xiàn)了一個(gè)關(guān)鍵點(diǎn)handler處理消息所處的線程和創(chuàng)建它的線程無(wú)關(guān),而是和創(chuàng)建它時(shí)loop的線程有關(guān)的。
這也是Handler能實(shí)現(xiàn)線程切換的原因所在: handler的執(zhí)行跟創(chuàng)建handler的線程無(wú)關(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ū)別,用來(lái)給message不同的成員變量賦值之外,本質(zhì)上都是通過 obtain()來(lái)創(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(); }
這端代碼很簡(jiǎn)單,Message
內(nèi)部維持了一個(gè)單線鏈表,使用sPool作為頭部,用來(lái)存儲(chǔ)Message
實(shí)體。可以發(fā)現(xiàn),每次調(diào)用者需要一個(gè)新的消息的時(shí)候,都會(huì)先從鏈表的頭部去取,有消息就直接返回。沒有消息才會(huì)創(chuàng)建一個(gè)新的消息。
那么這個(gè)鏈表是在何時(shí)插入消息的呢?接下來(lái)看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()方法里。 代碼也很簡(jiǎn)單,首先將Message的成員變量還原到初始狀態(tài),然后采用頭插法將回收后的消息插入到鏈表之中(限制了最大容量為50)。而且插入和取出的操作,都是使用的同一把鎖,保證了安全性。
注意插入和取出都是對(duì)鏈表的頭部操作,這里和消息隊(duì)列里就不太一樣了。雖然都是使用單向鏈表,回收時(shí)使用頭插和頭取,先進(jìn)后出,是個(gè)棧。而在MessageQueue
里是個(gè)隊(duì)列,遵循先進(jìn)先出的原則,而且插入的時(shí)候是根據(jù)消息的狀態(tài)確定位置,并沒有固定的插入節(jié)點(diǎn)。
這是一個(gè)典型的享元模式,最大的特點(diǎn)就是復(fù)用對(duì)象,避免重復(fù)創(chuàng)建導(dǎo)致的內(nèi)存浪費(fèi)。這也是為什么android官方推薦使用這種方式創(chuàng)建消息的原因:就是為了提高效率減少性能開銷。
4、 IdleHandler
IdleHandler
的定義很簡(jiǎn)單,就是一個(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)識(shí)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); } }
代碼很簡(jiǎn)單,就是一個(gè)List來(lái)保存接口的實(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; } }
接下來(lái)分步驟分析一下代碼:
第一步:在取消息的循環(huán)開始前創(chuàng)建局部變量pendingIdleHandlerCount
用來(lái)記錄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)沒有這么簡(jiǎ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ì)列Messag
eQueue中后面的消息無(wú)法得到及時(shí)的處理,就會(huì)造成延遲,直至視覺上的卡頓,嚴(yán)重的則會(huì)進(jìn)一步觸發(fā)ANR的發(fā)生。
到此這篇關(guān)于掌握Android Handler消息機(jī)制核心代碼的文章就介紹到這了,更多相關(guān)Android Handler消息機(jī)制核心代碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android 手機(jī)SD卡讀寫操作(以txt文本為例)實(shí)現(xiàn)步驟
要完成SD卡讀寫操作首先對(duì)manifest注冊(cè)SD卡讀寫權(quán)限其次是創(chuàng)建一個(gè)對(duì)SD卡中文件讀寫的類寫一個(gè)用于檢測(cè)讀寫功能的的布局然后就是UI的類了,感興趣的朋友可以參考下,希望可以幫助到你2013-02-02android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09Flutter 日期時(shí)間DatePicker控件及國(guó)際化
這篇文章主要介紹了Flutter 日期時(shí)間DatePicker控件及國(guó)際化,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Android客戶端實(shí)現(xiàn)注冊(cè)、登錄詳解(1)
這篇文章主要為大家詳細(xì)介紹了Android客戶端實(shí)現(xiàn)注冊(cè)、登錄代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Android第三方文件選擇器aFileChooser使用方法詳解
這篇文章主要介紹了Android第三方文件選擇器aFileChooser的使用方法詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android平臺(tái)基于Pull方式對(duì)XML文件解析與寫入方法詳解
這篇文章主要介紹了Android平臺(tái)基于Pull方式對(duì)XML文件解析與寫入方法,結(jié)合實(shí)例形式分析了Android基于Pull方式對(duì)xml文件解析及寫入操作的步驟、原理與操作技巧,需要的朋友可以參考下2016-10-10Android RecyclerView自定義上拉和下拉刷新效果
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView自定義上拉和下拉刷新效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android實(shí)現(xiàn)點(diǎn)擊AlertDialog上按鈕時(shí)不關(guān)閉對(duì)話框的方法
這篇文章主要介紹了Android實(shí)現(xiàn)點(diǎn)擊AlertDialog上按鈕時(shí)不關(guān)閉對(duì)話框的方法,涉及設(shè)置監(jiān)聽的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02android 解決ViewPager加載大量圖片內(nèi)存溢出問題
本篇文章是介紹 android 解決ViewPager加載大量圖片內(nèi)存溢出問題,并附有代碼實(shí)例,希望能幫到有需要的小伙伴2016-07-07