一 概述
Android消息機制主要指Handler的運行機制,主要包括上層的Handler接口以及下層的MessageQueue和Looper。
- Handler : 消息處理。通常我們需要繼承並實現handleMessage方法或者設置一個Callback。
- MessageQueue: 消息隊列。用於存儲消息。
- Looper: 消息循環。無限循環從消息隊列中查找消息。
三者的關係如圖:
二 MessageQueue工作原理
MessageQueue負責存儲和讀取消息,雖然MessageQueue名字叫消息隊列,但是實際內部是用一個單鏈表結構來維護消息列表的。
消息隊列存儲的消息指的是Message的實例。在分析MessageQueue工作原理前先了解一下Message的一些重要參數。
- msg.target 代表發送這個Message的Handler實例
- msg.when 代表執行這個Message的時間
- msg.isInUse()和msg.markInUse() 判斷當前Message是否正在使用和設置使用狀態
- msg.next 下一個需要執行的Message對象
- msg.callback 一個Runnable對象,通過Handler.post方法設置。
MessageQueue插入操作對應的方法是enqueueMessage方法
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) {
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;
Message p = mMessages;
boolean needWake;
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
插入方法接收的參數是需要插入的消息以及執行消息的時間,主要工作流程如下
- 如果msg.target爲空或者msg已經在使用中,則拋出異常。(無法重複插入同一msg)
- 如果Looper已經退出(調用過Looper.quit()方法),則拋出異常。
- mMessage表示單鏈表頭部,鏈表頭部爲空,或者執行時間早於頭部消息的執行時間,則直接插入頭部。
- 如果不滿足3,則開啓循環,從頭部開始遍歷,直到當前msg的執行時間晚於待插入msg的執行時間,插入鏈表。否則插入隊尾。
- 如果執行消息的線程處於Block等待狀態,則喚醒線程。
插入流程分析結束,上述過程也證明了消息隊列本質不是隊列,而是單鏈表,通過插入排序,根據執行時間正序排列。
接着分析消息出隊流程,精簡後代碼如下
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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 {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
}
mBlocked = true;
}
}
這裏調用了一個很重要的方法nativePollOnce,這是一個native方法,第二個參數爲超時等待時間。如果爲-1,則表示無限等待,直到被喚醒爲止。如果值爲0,則無須等待立即返回。
循環體大致流程如下:
- 調用nativePollOnce,首次調用參數爲0,不等待立即返回。
- 進入同步代碼塊,如果隊列裏沒有消息,則賦值 nextPollTimeoutMilli爲-1,重新進入循環,進入無限等待Block狀態。
- 如果有消息,對比隊首消息的執行時間和當前時間,如果當前時間晚於隊首執行時間,則消息出隊並返回該消息,否則賦值 nextPollTimeoutMilli爲時間差,重新進入循環,調用nativePollOnce,線程進入等待狀態。
三 Looper工作原理
Looper在Android消息機制裏負責取出消息,然後調用Handler接口處理消息。首先看一下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));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
Looper的構造方法是私有的,創建了MessageQueue的實例和獲取了當前Thread的引用,向外暴露的創建實例的方法是prepare和prepareMainLooper,創建出來的實例存儲在sThreadLocal中,它是Looper類的靜態變量。意味着每一個線程最多隻有一個Looper和一個MessageQueue。(ThreadLocal原理可以參考https://blog.csdn.net/Justwen26/article/details/103450958)
Looper有一個核心方法loop(),作用是開啓消息循環。
public 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;
}
......
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
......
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
......
}
}
Loop()是一個靜態方法,流程相對簡單
- 獲取當前線程Looper對象和MessageQueue對象,並開啓死循環。
- 進入循環體,調用MessageQueue.next()方法獲取消息。(可能會阻塞當前線程)
- 如果observer不爲空,則調用observer.messageDispatchStarting方法
- 調用msg.target.dispatchMessage()方法進行消息分發,這裏的msg.target就是Handler的一個實例。
- 如果observer不爲空,則調用observer.messageDispatched方法
- 重複2~5
2和5步驟中的observer在消息分發的前後調用,我們可以註冊observer進行消息處理的監聽。著名的卡頓檢測框架BlockCanary就是基於這個observer實現的。
子線程的需要我們手動調用Looper.preapre()創建Looper對象和Looper.loop()開啓循環,而主線程則是由系統在ActivityThread的main()方法中調用Looper.prepareMainLooper()創建。
Handler
Handler在Android消息機制中負責發送消息和處理消息,並對外暴露對應的API供開發者調用。
發送消息
Handler提供了多個API進行發送消息。
public 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 sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), 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);
}
最終都會調用到sendMessageAtTime方法,將消息插入消息隊列,他們區別在於
- sendEmptyMessage和sendEmptyMessageDelay 只需要傳入一個what參數,方法內部會創建一個Message實例。
- sendMessage和sendMessageDelay 需要我們自己去創建Message實例。
- post和postDelay 傳入一個Runnable對象,方法內部會創建一個Message實例。
- 最終的sendMessageAtTime方法的第二個參數是當前時間加上延遲時間,在調用不帶delay的方法時,延遲時間爲0。
處理消息
處理消息的代碼比較簡單
public 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();
}
- 如果msg設置了callback,則調用callback的run方法。(這裏的msg.callback則是通過調用post方法傳遞的Runnabl對象)
- 如果Handler設置了callback,則調用callback的handleMessage方法
- 如果步驟2callback.handleMessage方法返回false,則繼續調用Handle自己的handleMessage方法。
消息機制工作流程總結
上面分析了Handler,MessageQueue和Looper三者分別的工作流程,下面總結三者是如何協同工作,完成了整個消息工作流程。
- 準備階段
- 調用Looper.preapre() 創建當前線程Looper和MessageQueue對象。
- 調用Looper.loop() 開啓消息循環,並調用MessgeQueue.next()獲取消息。因爲MessageQueue裏沒有消息,則調用nativePollOnce(-1),線程進入無限等待狀態。
- 開始工作
- 創建Handler併發送一個消息。
- Handler調用enqueueMessage方法向MessageQueue插入消息,如果線程處於等待狀態,則喚醒線程。
- 線程喚醒後,next方法繼續執行,取出下一個消息,並返回給Looper,Looper調用Handler.dispatchMessage進行消息分發
- 分發完畢,如果MessageQueue裏沒有消息,則線程繼續進入無限等待狀態,否則計算下一個消息的間隔時間,等待對應時間。
- 重複2~5
- 結束工作
- 調用Looper.quit()方法,結束Looper循環,此時不允許再使用Handler發送消息。
其他問題
Handler內存泄漏
現象:
我們在編寫代碼工程中,如果定義了一個Handler成員變量,AS會提示我們Handler應該定義爲靜態變量否則可能會導致內存泄漏。
原因:
Java中非靜態內部類會隱式持有外部類引用。
我們通常在使用Handler的時候,會以非靜態內部的時候方式去重寫handleMessage方法,導致這個Handle的實例會隱式持有外部類的引用。
當發送一個消息時,將產生如下引用鏈
OutterClass -> Handler -> Message -> MessageQueue -> Looper -> ThreadLocal -> Thread
如果是一個延遲消息,那麼在消息被執行前,整條引用鏈會一直存在,從而導致Handler的外部類無法被釋放,出現內存泄漏。
解決方案:
- 將Handler設置爲靜態變量,這裏就不會持有外部類的引用了。
- 在確定不需要外部類實例的時候,主動調用removeCallbacksAndMessages方法移除所有使用該Handler發送的消息。
消息執行的時間
消息隊列是基於單鏈表實現的,且內部是根據執行時間先後排序的。當消息依次執行的時候,當前消息必須等前一個消息執行完畢才能執行,即使當前時間已經晚於當前消息理論的執行時間。所以Handler的sendDelay方法不適合需要精確計時的場景。
Looper死循環
這是一個老生常談的問題, 首先看一下這段代碼
new Thread() {
@Override
public void run() {
Looper.prepare();
Looper.loop();
Log.d("Looper", "Looper starts successfully");
}
}.start();
創建了一個線程,開啓消息循環,最後打了一行log。
但是實際上,最後一行log並不會打出,也就是說loop方法的確會阻塞當前線程。
那麼問題來了爲什麼主線程沒有卡死?
- 首先loop方法雖然內部是一個死循環,但是並不是時時刻刻都在循環,在沒有消息的時候,會因爲nativePollOnce導致線程休眠,消息被插入後纔會喚醒線程。
- Android本質是消息驅動,也就是說任何代碼都是在消息裏執行(包括觸摸事件),當沒有消息處理的時候,線程休眠不會產生任何影響。如果一旦有消息處理,線程就會被喚醒進行消息的處理。