前言
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){
return sendMessageDelayed(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開發框架體系;
6.2 BAT主流Android高級架構技術大綱+學習路線+資料分享
架構技術詳解,學習路線與資料分享都在博客這篇文章裏《“寒冬未過”,阿里P9架構分享Android必備技術點,讓你offer拿到手軟!》
(包括自定義控件、NDK、架構設計、混合式開發工程師(React native,Weex)、性能優化、完整商業項目開發等)
-
阿里P8級Android架構師技術腦圖
全套體系化高級架構視頻;七大主流技術模塊,視頻+源碼+筆記
下面我將繼續深入講解 Android中的Handler異步通信傳遞機制的相關知識,如 工作機制流程、源碼解析等,感興趣的同學可以繼續關注本人