前言
作為 Android 開發者,相信對於 Handler 的使用早已爛熟於心。Handler 對於 Android 非常重要,可以說,沒有它,Android App 就是一堆“破銅爛鐵”,它就像 Android 的血液,穿梭在 App 的各個角落,輸送養分。
理解 Handler 的執行原理,可以幫助我們更好地認識 Android 系統的本質。
而且 Handler 作為 Android 非常重要的一個工具,其原始碼卻非常清晰、簡潔,非常適合作為【Android原始碼閱讀】 的一個起點。
本文可以了解到一、如何使用Handler 內部是如何運轉的?
它和 NDK 層有什麼關聯?
Android 系統是如何利用 Handler 來驅動 App 執行的?
解答一些關於 Handler 的經典問題
相信大家對 Handler 的使用已經很熟悉了,主要分為兩種情況:一是在主執行緒使用,二是在子執行緒使用。
簡單來看看如何使用 Hanlder ,並從中可以得到一些什麼疑問和結論。帶著疑問來看原始碼,將會有更多的收穫。
主執行緒中class HandlerActivity: AppCompatActivity() { private val mHandler = MyHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. 通過自定義的 Handler 發訊息 mHandler.sendEmptyMessageDelayed(1, 1000) // 2. 通過預設的 Handler 發訊息 Handler().post { Log.i("HandlerActivity", "run postDelay") } } // 自定義一個 Handler class MyHandler: Handler() { override fun handleMessage(msg: Message) { Log.i("HandlerActivity", "主執行緒:handleMessage: ${msg.what}") } }}
可以看到,兩種不同的傳送訊息方式,都可以實現訊息的處理,一個是在 Handler 中處理,一個是在 Runnable 處理。
可以得出一個結論:
Handler 可以既自己處理訊息,也可以將訊息發到 Runnable 中處理。
同時存在一個疑問:
子執行緒中實現執行緒切換在主執行緒中建立好 Handler如果兩個同時存在,那麼哪一個會接收到訊息呢?
class HandlerActivity: AppCompatActivity() { private var mHandler = MyHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Thread { mHandler.sendEmptyMessageDelayed(1, 1000) }.start() } class MyHandler: Handler() { override fun handleMessage(msg: Message) { Log.i("HandlerActivity", "主執行緒:handleMessage: ${msg.what}") } }}
2. 在子執行緒中建立 Handler
class HandlerActivity: AppCompatActivity() { private var mHandler: Handler? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Thread { mHandler = MyHandler(Looper.getMainLooper()) mHandler!!.sendEmptyMessageDelayed(1, 1000) }.start() } class MyHandler(looper: Looper): Handler(looper) { override fun handleMessage(msg: Message) { Log.i("HandlerActivity", "主執行緒:handleMessage: ${msg.what}") } }}
建立屬於子執行緒的 Handler
class HandlerActivity: AppCompatActivity() { private var mHandler: Handler? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Thread { // 準備 Looper Looper.prepare() mHandler = MyHandler() mHandler!!.sendEmptyMessageDelayed(1, 1000) // 開啟 loop 迴圈 Looper.loop() }.start() } class MyHandler(): Handler() { override fun handleMessage(msg: Message) { Log.i("HandlerActivity", "子執行緒:handleMessage: ${msg.what}") } }}
以上3種情況中,前2種雖然 Handler 初始化的位置不一樣,但是效果卻是一樣的,第2種方式雖然是在子執行緒中初始化 Handler ,但是它接受了 Looper.getMainLooper() 作為引數,表明它的訊息回撥處理跟主執行緒同一個 Looper 。
而第3種情況就不一樣了,首先是在子執行緒中準備了一個 Looper ,在最後開啟了 loop 迴圈 。並且,Handler 的訊息處理最終是在子執行緒中回撥處理的。
以上分析,可以得出一個結論:
Handler 的訊息最終在哪個執行緒中進行處理,與 Looper 有著直接的關聯。
同時也有一個疑問:
為什麼在子執行緒中,要傳遞主執行緒的 Main Looper,或者自己準備一個 Looper ,才能正常使用 Handler ,而主執行緒卻不需要?
帶著以上的結論和疑問,開啟 Handler 探祕之旅。
二、如何運作在日常開發中,雖然最常使用的是 Handler 和 Message 這兩個類,實際上,整個訊息處理機制中,核心部分卻不是這兩個,而是另外兩個:輪詢器 Looper 和 訊息佇列 MessageQueue 。
首先來看下這幾個類的依賴關係:
先看左邊 Java 部分,整個訊息機制使用到的類其實環環相扣的,
Handle 作為一個對外暴露的工具,其內部包含了一個 Looper ;Looper 作為訊息迴圈的核心,其內部包含了一個訊息佇列 MessageQueue ,用於記錄所有待處理的訊息;MessageQueue 則作為一個訊息佇列,則包含了一系列連結在一起的 Message ;Message 則是訊息體,內部又包含了一個目標處理器 target ,這個 target 正是最終處理它的 Handler 。為什麼還有右邊的 NDK 層的部分?
實際上,Java 層的四個類構成了整個訊息迴圈的主體,而迴圈的等待阻塞則是通過 NDK 層,藉助 Linux 的 epoll 實現阻塞等待功能。
什麼是 epoll ?
來自百度百科
epoll 是 Linux 核心為處理大批量檔案描述符而作了改進的 poll,是 Linux 下多路複用IO介面 select/poll 的增強版本,它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統 CPU 利用率。
epoll 相關的知識已經超出 Handler 核心內容,本文不會深入探討,請自行 Google吧~
簡單理解:epoll 是 Linux 中用來監聽 IO 事件的工具。一個進入等待的控制代碼,一旦監聽到事件發生,就會被喚醒,繼續往下執行。
下面按照這個思路,逐一對它們進行分析。
訊息處理者:HandlerHandler 是發起訊息的入口,看看它是如何被構造出來的。
預設構造
// Handler.javapublic Handler() { this(null, false);}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) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async;}
最終呼叫的方法,這個方法的核心程式碼:
// Handler.javapublic Handler(@Nullable Callback callback, boolean async) { // 省略其他程式碼... mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async;}
從這裡就可以看出幾個核心類的基本關係: Handler 持有一個 Looper,Looper 持有一個 MessageQueue。最主要來看第一行程式碼 mLooper = Looper.myLooper();
// Looper.java// sThreadLocal.get() will return null unless you've called prepare().static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();public static @Nullable Looper myLooper() { return sThreadLocal.get();}
看到 ThreadLocal 馬上就可以知道,Looper 是和執行緒相關的,同時也說明了一個執行緒中只會有一個 Looper 。
從註釋中還能得到一個資訊,如果要獲取到一個非空的 Looper ,那麼必須在這個執行緒中呼叫一次 Looper.prepare() 。
到這裡,論證上面的一個結論了:
Handler 的訊息最終在哪個執行緒中進行處理,與 Looper 有著直接的關聯
從上面的分析知道,Looper 確實是和執行緒直接關聯的。
傳入 Looper 構造
// Handler.javapublic Handler(@NonNull Looper looper) { this(looper, null, false);}public Handler(@NonNull Looper looper, @Nullable Callback callback) { this(looper, callback, false);}public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async;}
這種方式,則比較簡單了,只需將傳遞進來的 Looper 儲存起來即可,但是要注意的是, Looper 不能為 null 。
傳送訊息
// Handler.javapublic final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0);}public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis);}public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis);}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);}
無論哪個傳送訊息的方法,最終的都是呼叫 enqueueMessage 方法,將 Message 放入 MessageQueue 佇列中。
那麼,訊息放入佇列後,是如何在到達時間後,被呼叫執行的呢?這就要來看 Looper 了。
輪詢器:Looper首先來看 Looper 是如何初始化的。
前面 Handler 初始化的時候,有一段註釋說,Looper.myLooper() 要獲取到 Looper ,必須要先呼叫 Looper.prepare(),下面就來看看這個方法。
// Looper.javapublic 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();}
可以看到,一個執行緒只能呼叫一次 Looper.prepare(), 否則就會報 RuntimeException 。
如果是第一次呼叫,會建立一個 Looper 放到 sThreadLocal 中。
在構造 Looper 的時候,建立了屬於這個 Looper 的訊息佇列 MessageQueue ,暫且不管,後面在細看。
開啟訊息迴圈
想要開啟訊息迴圈,需要通過 Looper.loop() 。
下面來看這個方法(這裡只貼出核心程式碼,去除一些異常判斷程式碼):
// Looper.javapublic static void loop() { 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; } try { msg.target.dispatchMessage(msg); //省略一些程式碼... } catch (Exception exception) { //省略一些程式碼... throw exception; } finally { //省略一些程式碼... } //省略一些程式碼... msg.recycleUnchecked(); }}
雖然整個方法比較長,但是主幹程式碼其實非常簡單:拿到訊息佇列 queue 以後,進入死迴圈。
迴圈中就做一件事:從訊息佇列中獲取到一個可以執行的 Message,接著呼叫這個訊息的 target(即 Handler)的 dispatchMessage 方法,訊息就分發出去了。
如何從佇列中拿到一個可執行的訊息,就要靠 MessageQueue 了。
訊息佇列:MessageQueue前面講過,構造 Looper 的時候,會建立訊息佇列,即 MessageQueue 。
// MessageQueue.javaprivate native static long nativeInit();MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit();}
這裡就和 NDK 發生關聯了,在構造方法中,通過本地方法 nativeInit 建立了一個物件 mPtr ,這個物件就是 NDK 層的 NativeMessageQueue 。這裡暫且不說,第三節再介紹。
訊息壓入
在了解如何獲取可執行訊息之前,我們需要先知道訊息是如何被壓入佇列的。
說明:Hanlder 的訊息佇列是一個單向連結串列佇列,從佇列的頭部一直鏈接到佇列的尾部。
還記得 Handler 傳送訊息的時候,最後呼叫的是 MessageQueue 的 enqueueMessage 嗎?來看看這個方法(注:程式碼省略了一些異常判斷片段):
// MessageQueue.javaboolean enqueueMessage(Message msg, long when) { // 省略一些程式碼... synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); msg.recycle(); return false; } msg.markInUse(); msg.when = when; //【1】拿到佇列頭部 Message p = mMessages; boolean needWake; //【2】如果訊息不需要延時,或者訊息的執行時間比頭部訊息早,插到佇列頭部 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 { //【3】訊息插到佇列中間 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; } if (needWake) { nativeWake(mPtr); } } return true;}
主要分為3個步驟(見以上程式碼標註)。
mMessages 是佇列的第一訊息,獲取到它判斷訊息佇列是不是空的,是則將當前的訊息放到佇列頭部;如果當前訊息不需要延時,或當前訊息的執行時間比頭部訊息早,也是放到佇列頭部。如果不是以上情況,說明當前佇列不為空,並且佇列的頭部訊息執行時間比當前訊息早,需要將它插入到佇列的中間位置。如何判斷這個位置呢?依然是通過訊息被執行的時間。
通過遍歷整個佇列,當佇列中的某個訊息的執行時間比當前訊息晚時,將訊息插到這個訊息的前面。
可以看到,訊息佇列是一個根據訊息【執行時間先後】連線起來的單向連結串列。
想要獲取可執行的訊息,只需要遍歷這個列表,對比當前時間與訊息的執行時間,就知道訊息是否需要執行了。
獲取可執行訊息
在 Looper.loop() 迴圈中,通過 queue.next() 來獲取可執行訊息,直接來看看這個方法。
// MessageQueue.javaMessage next() { final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //【1】呼叫 NDK 層方法,執行緒阻塞掛起,進入等待 // nextPollTimeoutMillis = -1 時,進入無限等待,直到有人喚醒 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //【2】判斷佇列是否插入了同步屏障,是則只執行非同步訊息 if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //【3】訊息沒到時間,重新計算等待時間 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //【4】訊息時間已到,重新拼接連結串列,並返回該訊息 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { // 沒有訊息,進入無限等待 nextPollTimeoutMillis = -1; } if (mQuitting) { dispose(); return null; } //【5】判讀是否有空閒監聽器,有的話,進行回撥 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // 只有for迴圈的第一次為 -1 , // 執行一次以後,pendingIdleHandlerCount變成 0, // 不再執行空閒監聽回撥 if (pendingIdleHandlerCount <= 0) { // mBlocked 為true,表明執行緒阻塞掛起 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); } } } // 這裡設定為0,則上面的空閒監聽器不再執行了 pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; }}
獲取訊息主要分為 5 個步驟(見以上程式碼標註):
進入 for 迴圈來遍歷訊息佇列,呼叫 NDK 方法 nativePollOnce 掛起執行緒,第一次進入時,等待時間為 0 ,繼續往下執行;判斷佇列是否插入了同步屏障,是則只執行非同步訊息(同步屏障暫且放一邊,後面詳細介紹),如果沒有同步屏障,則獲取到的是佇列頭部訊息;對比當前時間與訊息執行時間,如果時間沒到,計算需要等待的時間,重新進入等待;如果訊息執行時間已到,重新拼接連結串列,並返回該訊息,此時,Looper 將會得到一個訊息,將它分發給 Handler 處理。最後是一個空閒監聽處理,作用是當佇列中沒有需要執行的訊息時,說明執行緒進入空閒狀態,這時候可以去執行一些其他的任務。喚醒執行緒當訊息佇列為空的時候,Loop 會進入無限阻塞掛起,如果這時候使用者傳送了一個訊息,這時候如何喚醒執行緒呢?
細心的你可能已經發現了,在上面 enqueueMessage 的時候,有一個地方會喚醒執行緒,看回這個方法:
// MessageQueue.javaboolean enqueueMessage(Message msg, long when) { // 省略一些程式碼... synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); msg.recycle(); return false; } msg.markInUse(); msg.when = when; //【1】拿到佇列頭部 Message p = mMessages; boolean needWake; //【2】如果訊息不需要延時,或者訊息的執行時間比頭部訊息早,插到佇列頭部 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 { //【3】訊息插到佇列中間 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; } if (needWake) { // 喚醒 nativeWake(mPtr); } } return true;}
注意到最後的這個 needWake 標誌了嗎?如果該變數為 true ,就會通過 NDK 方法 nativeWake 來喚醒掛起的執行緒。
兩種情況會喚醒執行緒:
【佇列為空 || 訊息無需延時 || 或訊息執行時間比佇列頭部訊息早】 && 【執行緒處於掛起狀態時(mBlocked = true)】【執行緒掛起(mBlocked = true)&& 訊息迴圈處於同步屏障狀態】,這時如果插入的是一個非同步訊息,則需要喚醒以上,基本就是整個訊息機制的流程,簡潔、清晰。
簡單總結一下:
準備 Looper:Looper.looper()建立訊息佇列 MessageQueue:在構造 Looper 的時候被建立建立 Handler: 使用者自定義 Handler開啟 Loop 迴圈:Looper.loop()。迴圈遍歷訊息佇列,判斷是否到達執行時間使用者傳送訊息:通過 Handler 的 sendMessageDelay 等方法,將訊息壓入佇列,等待被 Looper 遍歷和執行訊息體:Message訊息體的結構
public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; Handler target; Runnable callback; Message next; // 省略其他...}
以上僅貼出 Message 幾個重點的成員,前面幾個相信不必再說,主要來看看 target 和 callback ,這兩個就是最終處理訊息的地方。
先來看下這兩個是在哪裡賦值的,回到 Handler 傳送訊息的地方:
// Handler.javaprivate 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);}
在壓入訊息佇列之前,target 被賦值為 this 也就是傳送這個訊息的 Handler。
再看下 callback 的賦值:
// Handler.javapublic final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m;}
在使用 post 方法的時候,Handler 同樣是構造了一個訊息體,並把 Runnable 賦值給了 callback 。
第一節提出了一個疑問:當通過 post 傳送訊息的時候,target 和 post 都被賦值了,那麼最後處理訊息的是哪一個呢?
上文訊息迴圈的分析知道,當 Looper 獲取到一個可執行的訊息時,會呼叫 Handler 的 dispatchMessage 方法:
// Handler.javapublic void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}private static void handleCallback(Message message) { message.callback.run();}/** * Subclasses must implement this to receive messages. */public void handleMessage(@NonNull Message msg) {}
可以看到,訊息首先會分發給 callback ,也就是 Runnable 處理;
如果 callback == null 並且 Handler 自己的 mCallback != null 就分發給 mCalllback 處理;
最後才是給到自己的 handleMessage 處理,使用者繼承 Handler 並重寫這個方法就可以處理訊息事件了。
那麼這個 mCallback 是什麼呢?它是 Handler 的一個介面:
// Handler.javapublic interface Callback { /** * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ boolean handleMessage(@NonNull Message msg);}
如何使用呢?來看 Handler 的一個構造方法:
// Handler.javapublic Handler(@Nullable Callback callback) { this(callback, false);}public Handler(@Nullable Callback callback, boolean async) { // 省略無關程式碼... mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async;}
也就是說,在使用 Handler 的時候,並不一定要繼承 Handler 來處理訊息,也可以通過實現 Callback 這個介面,來處理訊息。
訊息池
網上常有文章提到,在生成訊息的時候,最好是用 Message.obtain() 來獲取一個訊息,這是為什麼呢?先來看看這個方法:
// Message.javapublic 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();}
可以看到,這是一個訊息池,如果訊息池不為空,就會從池中獲取一個訊息,達到複用的效果。
訊息是如何被回收到池子裡面的呢?
在 Looper 訊息迴圈的最後有一句程式碼:
// Looper.javapublic static void loop() { // 省略無關程式碼... for (;;) { // 省略無關程式碼... try { msg.target.dispatchMessage(msg); } catch (Exception exception) { // 省略無關程式碼... } finally { // 省略無關程式碼... } // 省略無關程式碼... msg.recycleUnchecked(); }}
訊息處理完畢後,對訊息進行回收。
// Message.javaprivate 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++; } }}
原來,訊息池也是一個訊息連結串列佇列,除去了訊息所有的資訊以後,就會把訊息加入佇列頭部。
同時可以看到,訊息池的最大快取數量為 50 。
三、NDK 層的訊息工具訊息池,可以避免大量的訊息被建立,節省記憶體。所以,儘量通過 Message.obtain() 來建立訊息。
前面就介紹過,Java 層實現了整個訊息迴圈的核心功能,而訊息迴圈執行緒的等待掛起則是通過 NDK 的 epoll 實現的。
與 NDK 層互動的是 MessageQueue ,看下其中的本地方法:
// MessageQueue.javaprivate native static long nativeInit();private native static void nativeDestroy(long ptr);private native void nativePollOnce(long ptr, int timeoutMillis); private native static void nativeWake(long ptr);private native static boolean nativeIsPolling(long ptr);private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
重點看下初始化、掛起、喚醒 三個方法。
private native static long nativeInit();private native void nativePollOnce(long ptr, int timeoutMillis); private native static void nativeWake(long ptr);
訊息佇列:NativeMessageQueue
以上本地方法的實現,是在 Android 原始碼 的 AOSP/platform_frameworks_base-android10-c2f2-release/core/jni/android_os_MessageQueue.cpp 中。
//android_os_MessageQueue.cppstatic jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) { jniThrowRuntimeException(env, "Unable to allocate native queue"); return 0; } nativeMessageQueue->incStrong(env); return reinterpret_cast<jlong>(nativeMessageQueue);}static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->pollOnce(env, obj, timeoutMillis);}static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->wake();}
在 NDK 層,建立了一個 NativeMessageQueue ,你肯定會有疑問,這個訊息佇列和 Java 層的訊息佇列有什麼關係呢?
可以說,這兩個訊息佇列其實沒有太大的關係,Java 層 的MessageQueue 其實只是 “借用” 了 NativeMessageQueue 的執行緒掛起功能而已。
// MessageQueue.javaMessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit();}
Java 層的 MessageQueue 初始化後,儲存了 NDK 層的 NativeMessageQueue 指標引用,後續的執行緒掛起和喚醒,都是通過這個指標完成的。
初始化
// NativeMessageQueue.cppNativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); Looper::setForThread(mLooper); }}
這裡和 Java 層倒過來了,NativeMessageQueue 藉助 Looper 實現訊息迴圈。
⚠️ 這裡的 Looper 不是 Java 層的,而是 NDK 層的 Looper.cpp 。
掛起和喚醒
// NativeMessageQueue.cppvoid NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) { mPollEnv = env; mPollObj = pollObj; // 進入等待 mLooper->pollOnce(timeoutMillis); mPollObj = NULL; mPollEnv = NULL; if (mExceptionObj) { env->Throw(mExceptionObj); env->DeleteLocalRef(mExceptionObj); mExceptionObj = NULL; }}void NativeMessageQueue::wake() { // 喚醒 mLooper->wake();}
關於 epoll 的部分,都封裝在 Looper 中,下面就來看看具體的實現。
輪詢器:Looper先來了解下 epoll ,前面介紹過,epoll 是 Linux 平臺用來監聽 IO 事件的工具。
其使用非常簡單,三步曲:
#include <sys/epoll.h>// 建立控制代碼int epoll_create(int size);// 新增/刪除/修改 監聽事件int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);// 進入等待int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
下面就來看看 Android 系統是如何實現這三步曲的。
Looper.cpp 位於 : AOSP/platform_system_core-android10-c2f2-release/libutils/Looper.cpp
構造
// Looper.cppLooper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mPolling(false), mEpollRebuildRequired(false), mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno)); AutoMutex _l(mLock); rebuildEpollLocked();}void Looper::rebuildEpollLocked() { if (mEpollFd >= 0) { mEpollFd.reset(); } //【1】建立 epoll 控制代碼 mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC)); //【2】新建喚醒監聽事件 struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union // 設定監聽內容可讀事件 eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd.get(); //【3】將喚醒監聽事件註冊到控制代碼中 int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem); // 註冊其他監聽事件,Handle 時為空 for (size_t i = 0; i < mRequests.size(); i++) { const Request& request = mRequests.valueAt(i); struct epoll_event eventItem; request.initEventItem(&eventItem); int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem); if (epollResult < 0) { ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s", request.fd, strerror(errno)); } }}
在構造方法中呼叫了 rebuildEpollLocked() ,這個方法中建立了 epoll 的控制代碼,並將喚醒監聽事件註冊進去,詳見以上程式碼註釋。
執行緒掛起
Java 層呼叫 NativeMessageQueue 的 pollOnce 時,最終呼叫的是 Looper 的 pollOnce 。
// Looper.cppint Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { int result = 0; for (;;) { while (mResponseIndex < mResponses.size()) { const Response& response = mResponses.itemAt(mResponseIndex++); int ident = response.request.ident; if (ident >= 0) { int fd = response.request.fd; int events = response.events; void* data = response.request.data; if (outFd != nullptr) *outFd = fd; if (outEvents != nullptr) *outEvents = events; if (outData != nullptr) *outData = data; return ident; } } if (result != 0) { if (outFd != nullptr) *outFd = 0; if (outEvents != nullptr) *outEvents = 0; if (outData != nullptr) *outData = nullptr; return result; } result = pollInner(timeoutMillis); }}
進入 for 迴圈以後,如果只是 Handler 使用的情況下,mResponses 是空的,所以可以忽略。最後會進入 pollInner 方法。
由於 NDK 層的 Looper 也是類似於 Java 層 Handler 的訊息機制功能,需要實現類似的功能,這裡剔除掉一些 pollInner 中的程式碼,只看 掛起/喚醒 相關的功能,會更直觀:
int Looper::pollInner(int timeoutMillis) { // 省略一些程式碼... int result = POLL_WAKE; mResponses.clear(); mResponseIndex = 0; mPolling = true; struct epoll_event eventItems[EPOLL_MAX_EVENTS]; // 【1】進入等待掛起 int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis); mPolling = false; mLock.lock(); if (mEpollRebuildRequired) { mEpollRebuildRequired = false; rebuildEpollLocked(); goto Done; } if (eventCount < 0) { if (errno == EINTR) { goto Done; } result = POLL_ERROR; goto Done; } if (eventCount == 0) { result = POLL_TIMEOUT; goto Done; } //【2】遍歷所有監聽到的事件 for (int i = 0; i < eventCount; i++) { int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; //【3】如果是喚醒事件 if (fd == mWakeEventFd.get()) { if (epollEvents & EPOLLIN) { //【4】清空喚醒事件寫入的內容 awoken(); } else { ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); } } else { // 處理其他事件,Handler沒有 // 省略一些程式碼... } }Done: ; //省略一些程式碼... // Release lock. mLock.unlock(); //省略一些程式碼... return result;}
首先會呼叫 epoll_wait 進入等待,其中
timeoutMillis 如果為 0 ,則立即返回,繼續執行;如果為 -1 ,進入無限等待,直到外部喚醒;如果大於 0 ,則等待指定時間沒有被喚醒的話,自動超時喚醒,繼續往下執行。
當 timeoutMillis == 0 或者超時喚醒時,會跳轉到 Done ,結束呼叫,返回。這時 Java 層的 MessageQueue 的 next 方法就會繼續執行,開始檢查是否有可執行的訊息。
當 timeoutMillis == -1 進入無限等待時,如果被外部喚醒,需要判斷事件是否為指定的事件。如果是 mWakeEventFd 事件,則需要將 事件清空 ,然後繼續執行,結束呼叫,返回。
詳細請見以上程式碼標註。
執行緒喚醒
epoll 的功能是監聽事件,如果要喚醒它,其實就是發起一個事件,可以監聽的事件有以下幾種:
EPOLLIN :表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);EPOLLOUT:表示對應的檔案描述符可以寫;EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);EPOLLERR:表示對應的檔案描述符發生錯誤;EPOLLHUP:表示對應的檔案描述符被結束通話;EPOLLET:將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL佇列裡。在 rebuildEpollLocked() 中監聽的是 EPOLLIN 事件:
void Looper::rebuildEpollLocked() { // 省略無關程式碼... mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC)); struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); // 監聽可讀事件 eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd.get(); int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem); // 省略無關程式碼...}
因此,只需要往 mWakeEventFd 中隨便寫入點內容,就可以喚醒了。
來看看 Looper 的 wake 方法:
// Looper.cppvoid Looper::wake() { uint64_t inc = 1; // 寫入一個 1 來喚醒等待 ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t))); if (nWrite != sizeof(uint64_t)) { if (errno != EAGAIN) { LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s", mWakeEventFd.get(), nWrite, strerror(errno)); } }}
清除寫入的內容也很簡單,讀取出來即可:
// Looper.cppvoid Looper::awoken() { uint64_t counter; // 讀取寫入的內容 TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));}
以上,就是 Handler 從 Java 層到 NDK 層的整個實現過程。
接下里,來看一些 Handler 中比較有意思的東西。
四、Handler 同步訊息屏障什麼是同步訊息屏障前文中,多次提到同步訊息屏障的問題,到底什麼是同步訊息屏障呢?
屏障,可以阻擋一些訊息,什麼訊息呢?同步訊息!
也就是說,同步屏障是用來阻擋同步訊息執行的。
在日常使用中,很少去關心 Handler 的訊息是同步還是非同步,這是因為預設的訊息都是 同步訊息 。來看看訊息的同步和非同步是怎麼設定的。
通過 Handler 設定// Handler.javapublic Handler() { this(null, false);}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) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; // 設定為非同步 mAsynchronous = async;}
可以看到使用者可以建立的 Handler 都是同步的,是否同步會保在 mAsynchronous 中。
當然有一個系統可以用,但對開發者不可見的方法,是可以建立非同步 Handler 的。
// Handler.java@UnsupportedAppUsagepublic Handler(boolean async) { this(null, async);}
那麼 mAsynchronous 是如何使用的呢?
來看看訊息入佇列的方法:
// Handler.javaprivate 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);}
沒錯又是這個方法,可以看到,如果 Handler 的 mAsynchronous == true ,訊息就會設定為 非同步 。
所以,開發者是無法通過 Handler 來實現非同步訊息的。
但是可以直接通過訊息設定呀~
通過訊息體配置
其實就是上面的方法啦:
msg.setAsynchronous(true);
同步訊息屏障如何運作
讓我們回到訊息佇列獲取可執行訊息的地方,看程式碼標註【2】:
// MessageQueue.javaMessage next() { final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //【1】呼叫 NDK 層方法,執行緒阻塞掛起,進入等待 // nextPollTimeoutMillis = -1 時,進入無限等待,直到有人喚醒 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //【2】判斷佇列是否插入了同步屏障,是則只執行非同步訊息 if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //【3】訊息沒到時間,重新計算等待時間 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //【4】訊息時間已到,重新拼接連結串列,並返回該訊息 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { // 沒有訊息,進入無限等待 nextPollTimeoutMillis = -1; } if (mQuitting) { dispose(); return null; } // 省略無關程式碼... } // 省略無關程式碼... }}
可以看到,當訊息佇列的頭部插入了一個訊息,並且這個訊息的 target == null 的時候,就是啟用了同步訊息屏障。
這時候,會判斷訊息是否是同步的,如果是同步的則直接跳過,繼續尋找佇列中的非同步訊息。
換而言之,同步訊息都被擋住了,只有非同步訊息可以執行。
如何啟動/清除同步訊息屏障
依然是在 MessageQueue 中:
/** * @hide */public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis());}private int postSyncBarrier(long when) { synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; }}/** * @hide */public void removeSyncBarrier(int token) { synchronized (this) { Message prev = null; Message p = mMessages; while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } if (p == null) { throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed."); } final boolean needWake; if (prev != null) { prev.next = p.next; needWake = false; } else { mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } p.recycleUnchecked(); if (needWake && !mQuitting) { nativeWake(mPtr); } }}
很簡單,同步訊息屏障就是根據屏障啟動的時間,插入到訊息佇列中對應的位置,和普通的訊息壓入是類似的。
同步訊息屏障的作用你可能已經注意到了,這幾個方法對開發者都是不可見的,都有 @hide 的標記,也就是說,開發者是不被允許啟動同步訊息屏障的。
那了解同步訊息屏障有啥作用啊?“只可遠觀,不可褻玩” 呀!別急,往下看吧~
對於前端來說,UI 可以說是系統中最重要的東西,耽誤了誰都不能耽誤 UI 做事。
如果你看過 Android 系統 View 的繪製流程,應該會知道 View 的繪製也是通過 Handler 來驅動的。
如果在啟動繪製之前,使用者(開發者)插入了一個非常耗時的訊息到佇列中,那就會導致 UI 不能按時繪製,導致卡頓掉幀。
同步訊息屏障就可以用來保證 UI 繪製的優先性。
當你請求繪製 View 的時候,最終呼叫是在系統的 ViewRootImpl 中,來看下相關的程式碼:
// ViewRootImpl.javavoid scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; // 啟動同步訊息屏障 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}
先啟動同步訊息屏障,之後 mChoreographer 中的訊息都採用了非同步的方式,保證訊息的流暢,最終會回撥 mTraversalRunnable 。最後在繪製時,解除同步訊息屏障,詳見以下程式碼:
// ViewRootImpl.javafinal TraversalRunnable mTraversalRunnable = new TraversalRunnable();final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); }}void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // 清除同步訊息屏障 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } // 啟動繪製流程 performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } }}
具體就不再展開了,那又是一片原始碼的汪洋 -_-!。
五、關於 Hanlder 的經典問題只有理解了 Handler 的同步訊息屏障,才能完整地理解 Android 系統 View 的繪製流程。
關於 Handler 經典問題有很多,了解了整個的原始碼之後,基本都可以得到解答,下面就來看幾個經常碰到的問題。
為什麼說 Android 是基於訊息驅動的在開啟 Apk 的時候,系統會為 Apk 建立一個程序,而我們知道,Linux 程序至少包含一個執行緒,在 Apk 中,這個執行緒就是主執行緒,也就是 UI 執行緒。
Apk 主線的入口就在 ActivityThread.java 中的 main() 方法。
// ActivityThread.javapublic static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); AndroidOs.install(); CloseGuard.setEnabled(false); Environment.initForCurrentUser(); final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); //【1】準備主執行緒 Looper Looper.prepareMainLooper(); long startSeq = 0; if (args != null) { for (int i = args.length - 1; i >= 0; --i) { if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = Long.parseLong( args[i].substring(PROC_START_SEQ_IDENT.length())); } } } ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); //【2】獲取 Handler if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); //【3】開啟訊息迴圈 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");}
這根本就是啟動 Handler 最基本的方法,來看看 getHandler() 方法:
簡單地返回了 H
// ActivityThread.javafinal H mH = new H();final Handler getHandler() { return mH;}
簡單看下 H 的部分程式碼:
// ActivityThread.javaclass H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage public static final int EXIT_APPLICATION = 111; @UnsupportedAppUsage public static final int RECEIVER = 113; @UnsupportedAppUsage public static final int CREATE_SERVICE = 114; @UnsupportedAppUsage public static final int SERVICE_ARGS = 115; @UnsupportedAppUsage public static final int STOP_SERVICE = 116; // 省略部分程式碼... public static final int EXECUTE_TRANSACTION = 159; public static final int RELAUNCH_ACTIVITY = 160; // 省略部分程式碼... public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case BIND_APPLICATION: AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); break; case EXIT_APPLICATION: if (mInitialApplication != null) { mInitialApplication.onTerminate(); } Looper.myLooper().quit(); break; case CREATE_SERVICE: handleCreateService((CreateServiceData)msg.obj); break; // 省略部分程式碼... // 處理 Activity 生命週期相關的事件 // 如 onCreate/onResume/onPause等 case EXECUTE_TRANSACTION: final ClientTransaction transaction = (ClientTransaction) msg.obj; mTransactionExecutor.execute(transaction); // 省略部分程式碼... break; case RELAUNCH_ACTIVITY: handleRelaunchActivityLocally((IBinder) msg.obj); break; case PURGE_RESOURCES: schedulePurgeIdler(); break; } // 省略部分程式碼... }}
可以看到,幾乎從 App 啟動,到 Activity 相關的生命週期,Services 相關的生命週期,到 App 退出等等,都是通過 Handler 來驅動的。
可以看出,Android 就是基於 Handler 訊息驅動的。
Loop.looper() 死迴圈為什麼不會阻塞主執行緒同時也解答了第一節中的疑問:為什麼主執行緒不需要呼叫 Looper.prepare() 來準備一個 Looper 就可以發訊息?
因為 App 啟動的時候,系統已經建立好了 Looper 。
經過上面的分析,App 的主執行緒中通過 Loop.looper() 開啟了訊息“死迴圈”,那為什麼 App 依然可以正常運轉,沒有阻塞到主執行緒呢?
其實,這個問題就是個偽命題,Loop.looper() 根本就不是死的,而是在事件的驅動下動態運轉的,只要有訊息來了,執行緒就會 “活過來” 。
恰恰相反,Loop.looper() 保證了 App 可以活著,因為如果沒有 “死迴圈” ,ActivityThread.main() 在執行完以後,就主執行緒退出了,再也接收不到訊息了,反而是 “死了” !
Handler 如何導致記憶體洩漏通常 Handler 導致的記憶體洩漏,都是引起 Activity 的記憶體洩漏。
可能日常是這樣使用 Handler 的:
class HandlerActivity: AppCompatActivity() { private val mHandler = MyHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mHandler.sendEmptyMessageDelayed(1, 1000) } // 自定義一個 Handler class MyHandler: Handler() { override fun handleMessage(msg: Message) { Log.i("HandlerActivity", "handleMessage: ${msg.what}") } }}
MyHandler 是 HandlerActivity 的內部類,會持有 HandlerActivity 的引用。
在進入頁面以後,傳送了一個延時 1s 的訊息,如果 HandlerActivity 在 1s 內退出了,由於 Handler 會被 Message 持有,儲存在其 target 變數中,而 Message 又會被儲存在訊息佇列中,這一系列關聯,導致 HandlerActivity 在退出的時候,依然會被持有,因此不能被 GC 回收,這就是記憶體洩漏!
那麼當這個 1s 延時的訊息被執行完以後,HandlerActivity 會被回收嗎?答案是會的!
因為訊息被執行以後,Message 在放入快取池之前,target 已經被清除,所以持有鏈就斷開了,最終 HandlerActivity 將會被回收。
但這樣並不意味著我們就可以不管記憶體洩漏的問題,記憶體洩漏始終是記憶體洩漏,如果是佔用記憶體很大的頁面沒有被及時回收,有可能會導致 OOM 。
有兩個辦法可以解決這個問題:
將 MyHandler 改為靜態類,這樣它將不再持有外部類的引用。可以將 HandlerActivity 作為弱引用放到 MyHandler 中使用,頁面退出的時候可以被及時回收。頁面退出的時候,在 onDestroy 中,呼叫 Handler 的 removeMessages 方法,將所有的訊息 remove 掉,這樣也能消除持有鏈。啊啊啊 非常感謝能看到最後我囉裡囉嗦的朋友們!
本文所列出的知識點還不完全,要比較系統的學習,我這裡可以分享一份大佬收錄整理的Android學習PDF+架構視訊+面試文件+原始碼筆記,高階架構技術進階腦圖、Android開發面試專題資料,高階進階架構資料
這些都是我現在閒暇還會反覆翻閱的精品資料。裡面對近幾年的大廠面試高頻知識點都有詳細的講解。相信可以有效的幫助大家掌握知識、理解原理。
如果你覺得自己學習效率低,缺乏正確的指導,可以加入資源豐富,學習氛圍濃厚的技術圈一起學習交流吧!