一、引言
Handler 在安卓中的地位是不言而喻的,幾乎維系著整個安卓程序運行的生命周期,但是這么重要的一個東西,我們真的了解它嗎?下面跟隨著我的腳步,慢慢揭開Hanler的神秘面紗吧!
本文將介紹Handler 的運行機制、MessageQueue、Looper 的關系,ThreadLocal,以及Handler 導致的內存泄漏問題
二、Handler 系統組成概覽
在 Handler
的源碼中,主要涉及以下核心組件:
Message
:封裝消息的數據結構。MessageQueue
:存儲Message
的隊列,內部是單鏈表。Looper
:負責循環讀取MessageQueue
并分發消息。Handler
:對外提供sendMessage()
、post()
發送消息,并處理MessageQueue
中的消息。
它們之間關系如下圖所示:
三、Handler
的創建
當 Handler
被創建時,它會綁定當前線程的 Looper
:
public Handler() {this(Looper.myLooper(), null, false);
}
public Handler(Looper looper) {this(looper, null, false);
}
最終調用:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,boolean shared) {mLooper = looper;mQueue = looper.mQueue;mCallback = callback;mAsynchronous = async;mIsShared = shared;
}
mLooper
通過Looper.myLooper()
獲取當前線程的Looper
。mQueue
由Looper
提供,確保所有Handler
在同一個Looper
線程內共享MessageQueue
。
重點:主線程默認初始化
Looper
,但子線程默認沒有,需要手動Looper.prepare()
。如果一定要在子線程中使用,推薦使用
HandlerThread
,比于手動創建Looper
,HandlerThread
封裝了Looper
的創建和管理邏輯,代碼更加簡潔,也更易于維護。同時,HandlerThread
有自己獨立的消息隊列,不會干擾主線程或其他線程的消息處理。
四、sendMessage()
如何發送消息
當我們調用 sendMessage()
時:
handler.sendMessage(msg);
實際上調用:
public boolean sendMessage(Message msg) {return sendMessageDelayed(msg, 0);
}
最終:
public boolean sendMessageDelayed(Message msg, long delayMillis) {return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
最終調用 enqueueMessage()
:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this; // 綁定 Handlerreturn queue.enqueueMessage(msg, uptimeMillis);
}
@UnsupportedAppUsage
/*package*/ Handler target;
也就是說 Message 引用了 Handler,這也為內存泄漏埋下伏筆
五、MessageQueue
插入消息
boolean enqueueMessage(Message msg, long when) {synchronized (this) {// 插入 MessageQueueMessage 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.nextprev.next = msg;}}return true;
}
enqueueMessage
方法負責將消息按照時間順序正確地插入到單鏈表結構的隊列中,按 when
進行排序。
六、Looper
如何處理消息
Looper.loop() 讀取消息
public static void loop() {for (;;) {Message msg = queue.next(); // 取出消息//...msg.target.dispatchMessage(msg); // 交給 Handler 處理}
}
MessageQueue.next()
Message next() {// 檢查消息隊列是否已銷毀,若銷毀則返回 nullif (mPtr == 0) return null;int nextPollTimeoutMillis = 0;for (;;) {// 若有超時時間,刷新 Binder 待處理命令if (nextPollTimeoutMillis != 0) Binder.flushPendingCommands();// 阻塞線程,等待新消息或超時nativePollOnce(mPtr, nextPollTimeoutMillis);synchronized (this) {final long now = SystemClock.uptimeMillis();Message msg = mMessages;// 若為屏障消息,找下一個異步消息if (msg != null && msg.target == null) {do { 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 {// 若消息到處理時間,從隊列移除并返回mMessages = msg.next;msg.next = null;msg.markInUse();return msg;}} else {// 若無消息,一直阻塞nextPollTimeoutMillis = -1;}// 若消息隊列正在退出,釋放資源并返回 nullif (mQuitting) {dispose();return null;}}}
}
nativePollOnce()
讓當前線程進入阻塞狀態,直到有新的消息到來或者超時
nativePollOnce()
的主要功能是:
- 線程阻塞:讓當前線程進入等待狀態,避免空轉消耗CPU資源
- 事件喚醒:當有新消息到達或超時發生時,立即喚醒線程處理
- Native 層集成:與 Linux 的 epoll 機制對接,實現高效I/O多路復用
void nativePollOnce(long ptr, int timeoutMillis)
ptr
:指向 Native Looper 對象的指針(C++對象地址)
timeoutMillis 的含義:
- 如果 timeoutMillis > 0
epoll_wait
最多阻塞 timeoutMillis 毫秒,期間如果有事件發生,則提前返回。
- 如果 timeoutMillis == 0
epoll_wait
立即返回(非阻塞)。
- 如果 timeoutMillis < 0
epoll_wait
無限等待,直到有事件觸發。
最終調用了 Linux epoll 機制 來監聽消息事件。
七、nativePollOnce 方法調用流程
Java 層調用
// MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis);
JNI 本地方法,由 MessageQueue
調用,用于等待消息。
在 MessageQueue.next()
方法中:
// MessageQueue.java
nativePollOnce(mPtr, nextPollTimeoutMillis);
它的作用是:
- 如果
MessageQueue
里有消息,立即返回。 - 如果沒有消息,則阻塞,直到有新的消息到來或
timeoutMillis
超時。
JNI 層調用
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jlong ptr, jint timeoutMillis) {MessageQueue* mq = reinterpret_cast<MessageQueue*>(ptr);mq->pollOnce(timeoutMillis);
}
將 Java 傳來的 mPtr
轉換成 MessageQueue*
對象,并調用 pollOnce()
方法。
Native 層 pollOnce()
在 MessageQueue.cpp
:
void MessageQueue::pollOnce(int timeoutMillis) {mLooper->pollOnce(timeoutMillis);
}
調用了 Looper::pollOnce()
,進入 消息輪詢 邏輯。
Looper 的 pollOnce()
在 Looper.cpp
:
int Looper::pollOnce(int timeoutMillis) {return pollInner(timeoutMillis);
}
這里調用 pollInner(timeoutMillis)
,它的核心邏輯是 使用 epoll_wait()
監聽事件。
epoll
監聽消息事件
pollInner(timeoutMillis)
的核心邏輯:
int Looper::pollInner(int timeoutMillis) {struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);if (eventCount > 0) {for (int i = 0; i < eventCount; i++) {// 處理事件}}
}
其中:
mEpollFd
是epoll
文件描述符,用于監聽多個文件描述符(FD)。epoll_wait()
會 阻塞當前線程,直到:- 有 新消息可讀
- 有 文件描述符事件觸發
- 超時(
timeoutMillis
毫秒后自動返回)
到這里,我們清楚了 nativePollOnce
的主要作用是等待新消息到達消息隊列。當調用這個方法時,如果當前消息隊列中沒有需要立即處理的消息,線程會被阻塞,從而釋放 CPU 資源,直到有新消息到來或者發生其他喚醒條件。
那么 epoll_wait()
如何監聽消息?
epoll_wait() 監聽哪些事件?
MessageQueue 的 pipe(管道):當 Handler
發送消息時,寫入 pipe,觸發 epoll
事件。
輸入事件:當用戶觸摸屏幕或按鍵時,觸發 epoll
事件。
文件描述符(FileDescriptor):例如 Binder
進程間通信(IPC)事件。
等等…
消息如何觸發 epoll?
Handler.sendMessage()
會向MessageQueue
寫入數據:
write(mWakeEventFd, "W", 1);
-
epoll_wait()
監聽到pipe
有數據,返回。 -
Looper
處理新消息,Java 層Handler
開始執行handleMessage()
。
epoll_wait
阻塞等待wakeFd
上的可讀事件,當有數據寫入wakeFd
,epoll_wait
返回,線程被喚醒,這里并不關心寫入wakeFd
的具體數據是什么,只關心可讀事件的發生。
pipe 的作用
讓 Handler.sendMessage()
觸發 epoll
事件,立即喚醒 Looper。
至此,綜上,我們可以知道 epoll_wait() 只負責等待事件,不會提前返回“第一條消息”,它只會返回“有事件觸發”的信號,具體執行哪個消息是 MessageQueue.next()
的邏輯,它會選擇最早應該執行的消息,這就是 Handler
的阻塞喚醒的核心邏輯所在!
八、Handler 處理消息
public void dispatchMessage(Message msg) {if (msg.callback != null) {msg.callback.run();} else {handleMessage(msg);}
}
最終執行:
@Override
public void handleMessage(Message msg) {// 需要用戶實現
}
九、核心組件之間的關系
Thread└── ThreadLocal<Looper>└── Looper└── MessageQueue└── Message1 → Message2 → ...↑Handler
Handler
持有對MessageQueue
的引用(間接通過Looper
)因為Handler中的MessageQueue
是從Looper
中獲取的;
public Handler(@Nullable Callback callback, boolean async) {//..mQueue = mLooper.mQueue;//..}
- 每個線程通過 ThreadLocal 綁定自己的 Looper;
- Looper 管理其對應的 MessageQueue;
這樣它們的關系就清晰了,每個線程只有一個Looper(是由ThreadLocal確保的),可以有多個Handler。
public final class Looper {// 線程本地存儲,每個線程一個Looper實例static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}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));}
}
關于ThreadLocal的詳細介紹可以看這篇文章:深入剖析Java中ThreadLocal原理
十、內存泄漏問題分析及解決方案
我們都知道判斷內存泄漏的依據是:短生命周期對象是否被長生命周期對象引用!既然使用Handler
不當會導致內存泄漏,那么我們只需要找到被引用的源頭,然后去解決。
Handler 導致內存泄漏的完整引用流程
- 匿名內部類或非靜態內部類的隱式引用:
眾所周知,在Java中 匿名內部類或非靜態內部類會持有外部類的引用,如下:
public class MainActivity extends AppCompatActivity {private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 處理消息}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mHandler.sendEmptyMessageDelayed(0, 10000);}
}
這里的mHandler
是一個非靜態內部類。非靜態內部類會隱式持有外部類(這里是MainActivity
)的引用。這意味著mHandler
對象中包含了對MainActivity
實例的引用。
- MessageQueue 對 Message 的持有:
在上面示例中,我們發送了一個延遲的Message,盡管只傳了一個0,但是其內部也會封裝為Message,這時候Handler 會將 Message
對象并將其發送到與之關聯的MessageQueue
中,MessageQueue
會持有這個Message
對象,直到該消息被處理。
- Message 對 Handler 的持有:
由上面第四小節的sendMessage()
可知,在放入隊列的時候,會將Handler
與 Message
關聯:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this; // 綁定 Handlerreturn queue.enqueueMessage(msg, uptimeMillis);
}
主要作用是,讓Message
知道是從哪個Handler
發送的,并最終讓那個Handler
的 handleMessage
去處理。
public final class Looper {static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();@UnsupportedAppUsageprivate static Looper sMainLooper; // guarded by Looper.class//...
}
我們都知道,在主線程中,主線程的Looper
會一直運行下去(或者說 Looper
被 靜態 ThreadLocal<Looper>
所引用),不能被停止,而MessageQueue
又被Looper
所引用,這就產生了一條完整的引用鏈:ThreadLocal<Looper>
- Looper
- MessageQueue
- Message
- Handler
- MainActivity
** 解決方案**
- 使用靜態內部類 + WeakReference:
要解決內存泄漏,就是把引用鏈上任意一條引用斷開,讓GC不可達就行了,其實我們能操作的就只有 Handler
- **MainActivity
**這一條引用:
static class MyHandler extends Handler {private final WeakReference<MyActivity> ref;MyHandler(MyActivity activity) {ref = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {MyActivity activity = ref.get();if (activity != null) {// Safe to use activity}}
}
- 在 Activity 的
onDestroy()
中清除消息:
handler.removeCallbacksAndMessages(null);
其實,只要消息不是延遲很久或者反復堆積,就不會在 MessageQueue 中長時間滯留,從而也就不會延長 Handler 或其持有對象的生命周期。
想想,在實際開發中,誰會在Activity中延遲發送一個很長時間的消息,所以我們不必為 Handler
導致內存泄漏,過度緊張,稍微留意一下就可以避免了 😃
十一、最后
Handler 是 Android 消息機制的基礎組成部分。通過對 Handler、Looper、MessageQueue 之間關系的理解,我們可以更深入掌握 Android 的線程模型與 UI 更新流程。
由于本人能力有限,并沒有對 Handler
進行過度深入全面了解,比如同步屏障等,如果文章內容解讀有誤,還望不吝賜教。