百度阿里騰訊頭條面試Android高階崗必問!Handler原始碼解析!
前言
Handler 是Android中常用的非同步通訊的一個類,Android是一個訊息驅動的作業系統,各種型別的訊息都是由Handler發出,再由Handler處理,那麼對於Handler機制的理解就至關重要。
這篇文章將會從原始碼角度分析 Handler 機制以及一些常見的疑惑點。設計內容比較深篇幅較長, 可對照文末的Handler原始碼詳解視訊觀看琢磨。
目錄
1. 作用
2. 基本用法
3. 原始碼解析
3.1 為什麼 Handler 能夠切換執行緒執行?
3.2 Handler.post(Runnable) 方法是執行在新的執行緒嗎?
3.3 Handler(Callback) 跟 Handler() 這兩個構造方法的區別在哪?
3.4 子執行緒可以建立 Handler 嗎?
3.5 為什麼主執行緒不用呼叫 Looper.prepare() ?
3.6 為什麼建立 Message 物件推薦使用 Message.obtain()獲取?
3.7 梳理
4. 常見問題&技巧
- 4.1 為什麼 Handler 會造成記憶體洩漏?
- 4.2 怎麼防止 Handler 記憶體洩漏?
- 4.3 Loop.loop() 為什麼不會造成應用卡死?
5. 總結
1. 作用
Handler 是一種用於執行緒間的訊息傳遞機制。
因為 Android 中不允許在非主執行緒更新UI,所以最常使用的地方就是用於子執行緒獲取某些資料後進行UI的更新。
2.基本用法
step1:建立Handler例項
//1.自定義Handler類 static class CustomHandler extends Handler{ @Override public void handleMessage(Message msg) { //更新UI等操作 } } CustomHandler customHandler = new CustomHandler(); //2.內部類 Handler innerHandler = new Handler(){ @Override public void handleMessage(Message msg) { //更新UI等操作 } }; //3.callback方式 Handler callbackHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { //更新UI等操作 return true; } });
step2:傳送訊息
//1.傳送普通訊息 Message msg = Message.obtain(); msg.what = 0; //標識 msg.obj = "這是訊息體"; //訊息內容 innerHandler.sendMessage(msg); //2.傳送Runnale訊息 innerHandler.post(new Runnable() { @Override public void run() { //更新UI等操作,訊息接收後執行此方法 } });
Handler 的建立以及訊息的傳送都有很多種方法,各種方式的異同會在下面講到。
3. 原始碼分析
帶著問題看原始碼 —— 魯某
先丟擲我們的第一個問題:
3.1為什麼 Handler 能夠切換執行緒執行?
我們在傳送 Message 的時候在子執行緒,為什麼執行的時候就切換成了主執行緒?想要知道答案,基本就要把 Handler 的執行流程給瞭解一遍。
因為最終的處理是在 handleMessage
方法中進行的,所以我們看看 handleMessage
方法是怎麼被呼叫起來的。
先打個 debug , 看看呼叫鏈:

畫個圖直觀一點:

可能有點奇怪,整個呼叫流程都沒有出現我們傳送訊息的方法,那我們傳送的 Message
物件在哪裡被使用了呢?
看下上圖的 Step 2 ,在 loop()
方法裡面呼叫了 msg.target.dispatchMessage(msg)
方法,debug 中檢視 msg 物件的屬性,發現這個 msg
正是我們傳送的那個 Message
物件,這個 target
就是在 MainActivity
中建立的 Handler 物件。
也就是說,我們傳送訊息後,不知道什麼原因, Looper.loop()
方法內會拿到我們傳送的訊息,並且最終會呼叫傳送該訊息的 Handler 的 handleMessage(Message msg)
方法。先看看 loop()
方法是怎麼拿到我們的 Message
的:
// Looper.java ,省略部分程式碼 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, 從佇列取出一個msg if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); //Handler處理訊息 msg.recycleUnchecked();//回收msg } }
首先, loop()
方法會判斷當前執行緒是否已經呼叫了 Looper.prepare()
,如果沒有,則拋異常,這就是我們建立非主執行緒的 Handler 為什麼要呼叫 Looper.prepare()
的原因。而主執行緒中會在上面流程圖的 Step 1 中,即 ActivityThread.main()
方法裡面呼叫了 prepare 方法,所以我們建立預設(主執行緒)的 Handler 不需要額外建立 Looper 。
loop()
裡面是一個死迴圈,只有當msg為空時才退出該方法。msg 是從 queue.next
中取出來的,這個 queue
就是我們經常聽到的訊息隊列了(MessageQueue ),看看 next 方法的實現:
//MessageQueue.java ,刪減部分程式碼 Message next() { final long ptr = mPtr; if (ptr == 0) { //如果佇列已經停止了(quit or dispose) return null; } for (;;) { synchronized (this) { final long now = SystemClock.uptimeMillis();//獲取當前時間 Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { //msg == target 的情況只能是屏障訊息,即呼叫postSyncBarrier()方法 //如果存在屏障,停止同步訊息,非同步訊息還可以執行 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 { // 訊息已準備,可以取出 if (prevMsg != null) { //有屏障,prevMsg 為非同步訊息 msg 的前一節點,相當於拿出 msg ,連結前後節點 prevMsg.next = msg.next; } else { //沒有屏障,msg 即頭節點,將 mMessages 設為新的頭結點 mMessages = msg.next; } msg.next = null;//斷開即將執行的 msg msg.markInUse(); //標記為使用狀態 return msg;//返回取出的訊息,交給Looper處理 } } // Process the quit message now that all pending messages have been ha if (mQuitting) { //佇列已經退出 dispose(); return null;//返回null後Looper.loop()方法也會結束迴圈 } } }
原始碼中可以發現,雖然 MessageQueue
叫訊息佇列,但卻是使用了連結串列的資料結構來儲存訊息。 next()
方法會從連結串列的頭結點開始,先看看頭結點是不是訊息屏障( ViewRootImpl
使用了這個機制),如果是,那麼就停止同步訊息的讀取,非同步訊息照常運作。
如果有訊息,還會判斷是否到了訊息的使用時間,比如我們傳送了延時訊息,這個訊息不會馬上呼叫,而是繼續迴圈等待,直到訊息可用。這裡就有一個新的 問題2 : 問什麼 next()
中一直迴圈卻不會導致應用卡死? 這個問題等下再說。
到這裡,我們就大致能理清 Handler.handleMessage()
方法是怎麼調起來的了。但是 MessageQueue
裡面的訊息是怎麼來的呢?這個其實不看原始碼也能猜出來了,肯定是由我們傳送的訊息那裡傳過來的,但是為了理解更深刻,還是得看看訊息是怎麼傳遞到訊息佇列中的(MessageQueue );
//Handler.java public final boolean sendMessage(Message msg){ return sendMessageDelayed(msg, 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis){ if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(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); }
可以看到, sendMessage()
方法最終是呼叫了 sendMessageAtTime()
方法,分析下這個方法,首先將會拿到一個訊息佇列 mQueue,這個佇列是在建立 Looper
的時候預設初始化的,然後會呼叫 enqueueMessage()
方法進隊:
//Handler.java private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
這個進隊方法裡面會將 msg.target
設為當前Handler,也就是上面說到的 Looper.loop()
方法內最終呼叫的 msg.target.dispatchMessage(msg)
的這個 msg 的 target 來源。
如果當前 Handler 是非同步的話,還會將傳送的訊息置為同步訊息,這個 mAsynchronous
標識是我們構造 Handler 的時候傳遞的引數,預設為 false
。
最後就是真正的進隊方法 MessageQueue.enqueueMessage
:
//MessageQueue.java刪減部分程式碼 boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { msg.recycle(); return false; } msg.markInUse(); msg.when = when;//賦值呼叫時間 Message p = mMessages;//頭結點 if (p == null || when == 0 || when < p.when) { //佇列中沒有訊息 或者 時間為0 或者 比頭結點的時間早 //插入到頭結點中 msg.next = p; mMessages = msg; } else { Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) {//類似插入排序,找到合適的位置 break; } } // 結點插入 msg.next = p; prev.next = msg; } } return true; }
剛開始會進行一系列的判斷,然後根據時間來作為一個排隊依據進行進隊操作,需要注意的是: 訊息佇列是使用連結串列作為資料的儲存結構,是可以插隊的,即不存在傳送了延時訊息不會阻塞訊息佇列。
再跟上面的出隊方法聯絡起來,就會發現, 非同步訊息並不會立刻執行 ,而是根據時間,完全跟同步訊息一樣的順序插入佇列中。 非同步訊息與同步訊息唯一的區別就是當有訊息屏障時,非同步訊息還可以執行,而同步訊息則不行。
整個Handler的大體執行機制到此應該有了一個比較清晰的輪廓了。
總結一下: Handler 傳送的執行緒不處理訊息,只有Looper.loop()將訊息取出來後再進行處理,所以在 Handler
機制中,無論傳送訊息的Handler物件處於什麼執行緒,最終的處理都是執行在 Looper.loop() 所在的執行緒。
比如:一個新的執行緒 Thread1 傳送了一個訊息 Msg1,這個執行緒的工作僅僅是將訊息儲存到訊息佇列而已,並沒有下一步了,然後等待 Looper.loop() 處理到 Msg1 的時候( loop()
方法一直執行在最開始呼叫它的執行緒,比如主執行緒),再將 Msg1 進行處理,所以最終就從 Thread1 切換到了主執行緒中執行。
可以拉到下面3.7小節看下流程圖,更清晰一些
3.2 Handler.post(Runnable) 方法是執行在新的執行緒嗎?
Handler 中傳送訊息的方法多達十幾個,分為 sendXXX 以及 postXXX ,這裡看看主要的幾個 post 型別方法:
//Handler.java public final boolean post(Runnable r){ returnsendMessageDelayed(getPostMessage(r), 0); } public final boolean postAtTime(Runnable r, long uptimeMillis){ return sendMessageAtTime(getPostMessage(r), uptimeMillis); } ...
幾個 post 方法都是呼叫了相應的sendXXX 方法,然後用 getPostMessage(Runnable r)
構建 Message
物件:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
這裡獲取到訊息後,將 Runnable
賦值給 Message.callback ,那這個 callback 有什麼用呢?上面的整體流程分析中,我們知道 Looper.loop()
會呼叫 msg.target.dispatchMessage(msg)
,這個target 就是 Handler 了,那麼看一下這個方法的具體實現:
// Handler.java public void dispatchMessage(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(); }
到這一步終於水落石出了,如果是用 postXXX 方法傳送的訊息,就會呼叫 handleCallback(msg)
方法,即呼叫我們post方法裡傳遞的 Runnable
物件的 run()
方法。
也就是說, Runnable
跟執行緒沒有半毛錢關係,他只是一個回撥方法而已,只不過我們平時建立執行緒的時候使用多了,誤以為他跟執行緒有什麼py交易。
3.3 Handler(Callback) 跟 Handler() 這兩個構造方法的區別在哪?
接著看3.2講到的 dispatchMessage()
方法剩下的邏輯。
如果 msg 沒有 callback 的話,那麼將會判斷 mCallback
是否為空,這個 mCallback
就是構造方法種傳遞的那個 Callback ,如果 mCallback
為空,那麼就呼叫 Handler 的 handleMessage(msg)
方法,否則就呼叫 mCallback.handleMessage(msg)
方法,然後根據 mCallback.handleMessage(msg)
的返回值判斷是否攔截訊息,如果攔截(返回 true),則結束,否則還會呼叫 Handler#handleMessage(msg)
方法。
也就是說: Callback.handleMessage() 的優先順序比 Handler.handleMessage()要高 。如果存在Callback,並且Callback#handleMessage() 返回了 true ,那麼Handler#handleMessage()將不會呼叫。
除了這點,還有什麼區別嗎?暫時真沒發現。
3.4 子執行緒可以建立 Handler 嗎?
問題可能有些模糊,意思是可以在子執行緒回撥 handleMessage()
嗎。
上面理清了 Handler 的執行流程,但是建立流程好像還沒怎麼說,先看看 Handler 是怎麼建立的:
public Handler() { this(null, false); } public Handler(Callback callback) { this(callback, false); } public Handler(boolean async) { this(null, async); } public Handler(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; }
先看上面這部分不傳 Looper 的構造方法,這些方法最終都是呼叫了 Handler(Callback callback, boolean async)
方法,所以直接看這個方法就行,一開始會在方法體內檢測是否有潛在的記憶體洩漏風險,相信大家都有過被這東西煩過,看圖:

這種被黃色支配的感覺不太舒服,可以在例項上面添加註解 @SuppressLint("HandlerLeak")
來去掉提示,但是這只是去掉提示而已,別忘了處理潛在的記憶體洩漏。
接著看下面,首先會呼叫 Looper.myLooper()
方法拿到當前執行緒的 Looper 例項,如果為空,則拋異常,看看 myLooper()
具體是怎樣的:
//Looper.java public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
直接就是呼叫了 sThreadLocal
的 get 方法,這個 sThreadLocal
是一個靜態的 ThreadLocal 常量,看名字就能猜到與執行緒相關,具體的就不深究了。可以先把他看成一個執行緒id 與 Looper 的 map 鍵值對。既然有 get() ,那麼就應該有 set() ,那麼 Looper 是在哪裡被存進去的呢?
//Looper.java 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)); }
原來是在 Looper.prepare() 方法中被傳進去的,並且 sThreadLocal
中每個執行緒都只能有一個 Looper 例項。需要注意的是, prepare()
方法並沒有呼叫 Looper#loop()
方法,經過上面的流程分析也知道,這個 loop()
方法啟動才能處理髮送的訊息,所以子執行緒建立 Handler 除了需要呼叫 Looper.prepare()
外,還需要呼叫 Looper.loop()
啟動。
也就說明,任何執行緒都可以建立 Handler, 只要當前執行緒呼叫了 Looper.prepare()
方法,那麼就可以使用 Handler 了,而且同一執行緒內就算建立 n 個 Handler 例項,也只對應一個 Looper,即對應一個訊息佇列。
理一理邏輯:Handler 機制要求建立 Handler 的執行緒必須先呼叫 Looper.prepare() 方法來初始化,初始化過程中會將當前執行緒的 Looper 存起來,如果沒有進行 Looper 的初始化,將會拋異常,要啟動 Looper ,還需要呼叫 loop() 方法。
3.5 為什麼主執行緒不用呼叫 Looper.prepare() ?
上面說了,每個執行緒要建立 Handler 就必須要呼叫 Looper.prepare
進行初始化,那麼為什麼我們平時在主執行緒建立 Handler 則不需要呼叫?
通過3.1 中的 debug 呼叫鏈就可以知道,主執行緒的 loop()
方法是在 ActivityThread#main()
方法中被呼叫的,那麼看看 main() 方法:
//ActivityThread.java 刪減部分程式碼 public static void main(String[] args) { Looper.prepareMainLooper(); Looper.loop(); }
到這裡就能明白了,在App啟動的時候系統預設啟動了一個主執行緒的 Looper, prepareMainLooper()
也是呼叫了 prepare()
方法,裡面會建立一個不可退出的 Looper,並 set 到 sThreadLocal
物件當中。
3.6 為什麼建立 Message 物件推薦使用 Message.obtain()
獲取?
Message 物件有兩種方式可以獲得,一種是直接 new 一個例項,另一種就是呼叫 Message.obtain()
方法了, Handler.obtainMessage()
也是呼叫 Message.obtain()
實現的,看看這個方法:
//Message.java 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(); }
可以看到,如果訊息池不為空, obtain()
方法會將頭結點 sPool
取出,並置為非使用狀態,然後返回,如果訊息池為空,則新建一個訊息。
知道有訊息池這個東西了,那麼這個訊息池的訊息是怎麼來的呢?
使用 AS 搜尋一下,發現只有兩個方法對 sPool
這個節點進行了賦值,一個是上面的 obtain()
,另一個是下面這個:
//Message.java private static final int MAX_POOL_SIZE = 50; void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
看方法名也可以知道,這是一個回收的方法,方法體內將 Message 物件的各種引數清空,如果訊息池的數量小於最大數量(50)的話,就當前訊息插入快取池的頭結點中。
已經知道 Message 是會被回收的了,那麼什麼情況才會被回收呢?
繼續檢視呼叫鏈:
// Looper.java ,省略部分程式碼 loop(){ final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); // might block, 從佇列取出一個msg if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); //Handler處理訊息 ... msg.recycleUnchecked();//回收msg } }
其中的一個呼叫是在 Looper.loop()
方法中,呼叫時機是在 Handler 處理事件之後,既然是 Handler 處理後就會回收,那麼如果在 Handler.handleMessage()
中用新的執行緒使用這個 msg 會怎樣呢?
//MainActivity.java @SuppressLint("HandlerLeak") static Handler innerHandler = new Handler() { @Override public void handleMessage(final Message msg) { new Thread(new Runnable() { @Override public void run() { boolean isRecycle = msg.obj == null; Log.e("====是否已經回收===", "" + isRecycle); } }).start(); } }; private void send(){ Message msg = Message.obtain(); msg.what = 0; //標識 msg.obj = "這是訊息體"; //訊息內容 innerHandler.sendMessage(msg); }
當呼叫 send()
方法傳送訊息後,發現打出 log:
E/====是否已經回收===: true
也就說明我們的推斷是正確的。 所以在平時使用中,不要在 handleMessage(Message msg)
方法中對 msg 進行非同步處理,因為非同步處理後,該方法會馬上返回,相當於告訴 Looper 已經處理完成了,Looper 就會將其回收。
如果真要在非同步中使用,那麼可以建立一個新的 Message 物件,並將值賦值過去。
回到前面的問題,我們目前發現了一個 Message 被回收的地方,那麼其他地方有呼叫這個 Message .recycleUnchecked()
嗎?接著看看:
//Message.java public void recycle() { if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } recycleUnchecked(); }
Message 還有一個公共的回收方法,就是上面這個了,我們可以手動呼叫這個進行回收。還有就是訊息佇列中各種 removeMessage 也會觸發回收,呼叫鏈太多了,就不貼程式碼了。
總而言之,因為 Handler 機制在整個 Android 系統中使用太頻繁,所以 Android 就採用了一個快取策略。就是 Message 裡面會快取一個靜態的訊息池,當訊息被處理或者移除的時候就會被回收到訊息池,所以推薦使用 Message.obtain()
來獲取訊息物件。
3.7 梳理
到此就把Handler的大致流程分析完了,再畫個圖重新梳理一下思路:

把整個Handler機制比作一個流水線的話,那麼 Handler 就是工人,可以在不同執行緒傳遞 Message
到傳送帶(MessageQueue),而傳送帶是被馬達(Looper)運輸的,馬達又是一開始就運行了( Looper.loop()
),並且只會在一開始的執行緒,所以無論哪個工人(Handler)在哪裡(任意執行緒)傳遞產品(Message),都只會在一條傳送帶(MessageQueue)上被唯一的馬達(Looper)運送到終點處理,即 Message 只會在呼叫 Looper.loop() 的執行緒被處理。
4 常見問題&技巧
4.1 為什麼 Handler 會造成記憶體洩漏?
先來回顧下基礎知識,可能造成記憶體洩漏的原因可以大致概括如下:
生命週期長的物件引用了生命週期短的物件。
Handler 跟其他一些類一樣,本身是不會造成記憶體洩漏的,Handler 造成記憶體洩漏的一般原因都是由於匿名內部類引起的,因為匿名內部類隱性地持有外部類的引用(如果不持有引用怎麼可以使用外部類的變數方法呢?)。
所以當內部類的生命週期比較長,如跑一個新的執行緒,碰巧又碰到生命週期短的物件(如Activity)需要回收,就會導致生命週期短的物件還在被生命週期長的物件所引用,進而回收不了。
典型的例子:
public class Main { int _10m = 10*1024*1024; byte[] bytes = new byte[4*_10m]; public void run() { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100*1000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } public static void main(String args[]) { Main object = new Main(); object.run(); object =null; System.gc(); } }
輸出log:
[GC (System.gc()) [PSYoungGen: 3341K->880K(38400K)] 44301K->41848K(125952K)[Full GC (System.gc()) [PSYoungGen: 880K->0K(38400K)] [ParOldGen: 40968K->41697K(87552K)] 41848K->41697K(125952K)
可以看到,即使object引用為空,object 物件還是沒有被回收。這就會發生了記憶體洩漏,如果出現很多次這樣的情況,那麼就很有可能發生記憶體溢位(OutOfMemery)。
在 Handler 裡面其實是類似的道理,匿名內部類的 Handler 持有 Activity 的引用,而傳送的 Message 又持有 Handler 的引用,Message 又存在於 MessageQueue 中,而 MessageQueue 又是 Looper 的成員變數,並且 Looper 物件又是存在於靜態常量 sThreadLocal 中。
所以反推回來,因為 sThreadLocal 是方法區常量,所以不會被回收,而 sThreadLocal 又持有 Looper 的引用...balabala...還是看圖吧:

即 sThreadLocal 間接的持有了 Activity 的引用,當 Handler 傳送的訊息還沒有被處理完畢時,比如延時訊息,而 Activity 又被使用者返回了,即 onDestroy()
後,系統想要對 Activity 物件進行回收,但是發現還有引用鏈存在,回收不了,就造成了記憶體洩漏。
4.2 怎麼防止 Handler 記憶體洩漏?
從上面的分析中,可以知道,想要防止 Handler 記憶體洩漏,一種方法是把 sThreadLocal 到 Activity 的引用鏈斷開就行了。
最簡單的方法就是在 onPause()
中使用 Handler 的 removeCallbacksAndMessages(null)
方法清除所有訊息及回撥。 就可以把引用鏈斷開了。
Android 原始碼中這種方式也很常見,不在 onDestroy()
裡面呼叫主要是 onDestroy()
方法不能保證每次都能執行到。
第二種方法就是使用靜態類加弱引用的方式:
public class MainActivity extends AppCompatActivity { public TextView textView; static class WeakRefHandler extends Handler { //弱引用 private WeakReference<MainActivity> reference; public WeakRefHandler(MainActivity mainActivity) { this.reference = new WeakReference<MainActivity>(mainActivity); } @Override public void handleMessage(Message msg) { MainActivity activity = reference.get(); if (activity != null) { activity.textView.setText("雞湯程式設計師"); } } } }
因為靜態類不會持有外部類的引用,所以需要傳一個 Activity 過來,並且使用一個弱引用來引用 Activity 的例項,弱引用在 gc 的時候會被回收,所以也就相當於把強引用鏈給斷了,自然也就沒有記憶體洩漏了。
4.3 Loop.loop() 為什麼不會造成應用卡死?
上面也提了這個問題,按照一般的想法來說,loop() 方法是一個死迴圈,那麼肯定會佔用大量的 cpu 而導致應用卡頓,甚至說 ANR 。
但是 Android 中即使使用大量的 Looper ,也不會造成這種問題,問什麼呢?
由於這個問題涉及到的知識比較深,主要是通過 Linux 的 epoll 機制實現的,這裡需要 Linux 、 jni 等知識,我等菜鳥就不分析了,大家可以去搜這類似的文章瞭解下。
5. 總結
以上就是篇文章的全部分析了,這裡總結一下:
1. Handler 的回撥方法是在 Looper.loop()
所呼叫的執行緒進行的;
2. Handler 的建立需要先呼叫 Looper.prepare()
,然後再手動呼叫 loop()
方法開啟迴圈;
3. App 啟動時會在 ActivityThread.main()
方法中建立主執行緒的 Looper ,並開啟迴圈,所以主執行緒使用 Handler 不用呼叫第2點的邏輯;
4. 延時訊息並不會阻塞訊息佇列;
5. 非同步訊息不會馬上執行,插入佇列的方式跟同步訊息一樣,唯一的區別是當有訊息屏障時,非同步訊息可以繼續執行,同步訊息則不行;
6. Callback.handleMessage() 的優先順序比 Handler.handleMessage()要高*
7. Handler.post(Runnable)
傳遞的 Runnale 物件並不會在新的執行緒執行;
8. Message 的建立推薦使用 Message.obtain()
來獲取,內部採用快取訊息池實現;
9. 不要在 handleMessage()
中對訊息進行非同步處理;
10. 可以通過 removeCallbacksAndMessages(null)
或者靜態類加弱引用的方式防止記憶體洩漏;
11. Looper.loop()
不會造成應用卡死,裡面使用了 Linux 的 epoll 機制。
6. APP開發框架體系,Android架構師腦圖,全套視訊
-
6.1 APP開發框架體系;
APP開發框架體系
-
6.2 BAT主流Android高階架構技術大綱+學習路線+資料分享
架構技術詳解,學習路線與資料分享都在部落格這篇文章裡 “寒冬未過”,阿里P9架構分享Android必備技術點,讓你offer拿到手軟!
(包括自定義控制元件、NDK、架構設計、混合式開發工程師(React native,Weex)、效能優化、完整商業專案開發等)
-
阿里P8級Android架構師技術腦圖
-
全套體系化高階架構視訊;七大主流技術模組,視訊+原始碼+筆記

Handler
下面我將繼續深入講解 Android中的Handler非同步通訊傳遞機制的相關知識,如 工作機制流程、原始碼解析等,感興趣的同學可以繼續關注本人