一、常見使用場景
訊息機制中主要用於多執行緒的通訊,在 Android 開發中最常見的使用場景是:在子執行緒做耗時操作,操作完成後需要在主執行緒更新 UI(子執行緒不能直接修改 UI)。這時就需要用到訊息機制來完成子執行緒和主執行緒的通訊。
如以下程式碼片段所示:
public class MainActivity extends AppCompatActivity {
private TextView tvText;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tvText.setText(msg.obj.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvText = (TextView) findViewById(R.id.tv_text);
new Thread() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = Message.obtain();
msg.obj = Thread.currentThread().getName();
mHandler.sendMessage(msg);
}
}.start();
}
}複製程式碼
子執行緒阻塞 10 秒後傳送訊息更新 TextView,TextView 顯示來源的執行緒名。
這裡有兩個限制:
不能讓阻塞發生在主執行緒,否則會發生 ANR
不能在子執行緒更新 TextView。
所以只能在子執行緒阻塞 10 秒,然後通過 Handler 傳送訊息,Handler 處理獲取到的訊息並在主執行緒更新 TextView。
二、訊息機制分析
1. 準備階段
1.1 Handler 構造方法
private Handler mHandler = new Handler() {
...
};複製程式碼
檢視 Handler 的原始碼:
...
public Handler() {
this(null, false);
}
...
/**
* @hide 該構造方法是隱藏的,無法直接呼叫
*/
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 that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
...複製程式碼
Handler 的構造方法需要傳入兩個引數,第一個引數是 Handler.Callback 介面的實現,第二個引數是標誌傳遞的 Message 是否是非同步。
構造方法內部首先會檢查 Handler 的使用是否可能存在記憶體洩漏的問題,如果存在會發出一個警告:
所以在使用 Handler 的時候一般宣告為靜態內部類或使用弱引用的方式。
接著會呼叫 Looper.myLooper() 獲取到 Looper 物件,並判斷該 Looper 物件是否為 null,如果為 null 則丟擲異常;如果不為 null 則進行相應的賦值操作。由此可知 Looper.myLooper() 方法並不會構造一個 Looper 物件,而是從某個地方獲取到一個 Looper 物件。
所以,在建立 Handler 物件時必須先建立 Looper 物件。
1.2 Looper 物件的建立
下面檢視 Looper 原始碼:
...
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
...
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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();
}
}
...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
...
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
...複製程式碼
從 Looper 的原始碼可知,Looper 類中對外部提供兩個方法用於建立 Looper 物件:prepare() 和 prepareMainLooper(),並將建立的 Looper 物件儲存到 sThreadLocal 中。myLooper() 方法獲取 Looper 物件也是從 sThreadLocal 中獲取。
sThreadLocal 是一個ThreadLocal 物件,ThreadLocal 用於提供執行緒區域性變數,在多執行緒環境可以保證各個執行緒裡的變數獨立於其它執行緒裡的變數。也就是說 ThreadLocal 可以為每個執行緒建立一個【單獨的變數副本】,相當於一個執行緒的 private static 型別變數。
在 Looper 的真正建立物件方法 prepare(boolean quitAllowed) 中,會先判斷當前執行緒是否已經有 Looper 物件,沒有時才可以建立並儲存到當前執行緒中,每個執行緒只允許有一個 Looper。
上面的示例程式碼中是在主執行緒中例項化 Handler 的,但是並沒有呼叫 Looper 的建立方法,而且也沒有丟擲異常,說明主執行緒中是有 Looper 物件的。
那麼主執行緒中的 Lopper 物件是從哪裡來的呢?
在 Looper 的 prepareMainLooper() 方法註釋中可以看到這樣一句話:
The main looper for your application is created by the Android environment, so you should never need to call this function yourself.
意思是:應用程式的主 Looper 由 Android 環境建立,不應該自己呼叫該方法。
由此可知,在 Android 系統原始碼中應該會呼叫該方法,通過查詢該方法的使用發現,在 ActivityThread 類的 main 方法中呼叫了 Looper.prepareMainLooper():
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}複製程式碼
所以在應用程式啟動時就已經為主執行緒建立了一個 Looper 物件。
2. 傳送訊息
繼續分析示例程式碼,在子執行緒阻塞結束後會建立一個 Message 物件,然後使用 Handler 傳送該 Message 物件。
...
Message msg = Message.obtain();
msg.obj = Thread.currentThread().getName();
mHandler.sendMessage(msg);
...複製程式碼
2.1 建立 Message 物件
呼叫 Message 的 obtain() 方法建立 Message 物件。
檢視 Message 的原始碼:
...
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();
}
...
public Message() {
}
...複製程式碼
Message 是訊息機制中訊息的載體,為了優化效能,避免重複 Message 的建立,Message 使用了訊息池機制,當呼叫 obtain() 方法時,會先嚐試從訊息池中獲取一個 Message 物件,訊息池中沒有時才建立新的物件;Message 物件使用完後會重新回收到訊息池中。Message 的訊息池使用了連結串列的資料結構,Message 類本身時支援連結串列結構的。
所以在建立 Message 物件時不要直接使用構造方法。
建立好 Message 物件後,可以給 Message 的一些屬性賦值,用於描述該訊息或攜帶資料。
2.2 Handler 傳送訊息
呼叫 Handler 的 sendMessage(Message msg) 方法傳送訊息。
通過檢視 Handler 的原始碼可知,在 Handler 中有許多傳送訊息的方法,所有的傳送訊息方法最終都會呼叫 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 方法。
...
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);
}
...
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}複製程式碼
首先給 Message 的 target 屬性賦值,即當前的 Handler;然後根據 Handler 的 mAsynchronous 值設定該 Message 是否是非同步的,mAsynchronous 的值在 Handler 例項化時被賦值;最後呼叫 MessageQueue 的 enqueueMessage(Message msg, long when) 方法。
可以看出在 Handler 中也只是對 Message 物件的屬性進行了相關賦值操作,最終是呼叫了 MessageQueue 的 enqueueMessage(Message msg, long when) 方法。
2.3 MessageQueue
enqueueMessage() 方法中的 MessageQueue 物件來自於 Handler 的 mQueue 屬性:
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);
}複製程式碼
而 mQueue 屬性在 Handler 例項化時賦值的:
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}複製程式碼
mQueue 是 Looper 中的 MessageQueue 物件,在 Looper 建立時被例項化:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}複製程式碼
檢視 MessageQueue 的構造方法:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}複製程式碼
MessageQueue 是訊息佇列,Handler 傳送訊息其實就是將 Message 物件插入到訊息佇列中,該訊息佇列也是使用了連結串列的資料結構。同時 Message 也是訊息機制中 Java 層和 native 層的紐帶,這裡暫且不關心 native 層相關實現。
MessageQueue 在例項化時會傳入 quitAllowed 引數,用於標識訊息佇列是否可以退出,由 ActivityThread 中 Looper 的建立可知,主執行緒的訊息佇列不可以退出。
MessageQueue 插入訊息:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { // target 即 Handler 不允許為 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(); // 回收 Message,回收到訊息池
return false;
}
msg.markInUse(); // 標記為正在使用
msg.when = when;
Message p = mMessages; // 獲取當前訊息佇列中的第一條訊息
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 訊息佇列為空 或 新訊息的觸發時間為 0 或 新訊息的觸發時間比訊息佇列的第一條訊息的觸發時間早
// 將新訊息插入到佇列的頭,作為訊息佇列的第一條訊息。
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 當阻塞時需要喚醒
} else {
// 將新訊息插入到訊息佇列中(非佇列頭)
// 當阻塞 且 訊息佇列頭是 Barrier 型別的訊息(訊息佇列中一種特殊的訊息,可以看作訊息屏障,用於攔截同步訊息,放行非同步訊息) 且 當前訊息是非同步的 時需要喚醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 迴圈訊息佇列,比較新訊息的觸發時間和佇列中訊息的觸發時間,將新訊息插入到合適的位置
for (;;) {
prev = p; // 將前一條訊息賦值給 prev
p = p.next; // 將下一條訊息賦值給 p
if (p == null || when < p.when) {
// 如果已經是訊息佇列中的最後一條訊息 或 新訊息的觸發時間比較早 則退出迴圈
break;
}
if (needWake && p.isAsynchronous()) {
// 需要喚醒 且 下一條訊息是非同步的 則不需要喚醒
needWake = false;
}
}
// 將新訊息插入佇列
msg.next = p;
prev.next = msg;
}
if (needWake) {
// 如果需要喚醒呼叫 native 方法喚醒
nativeWake(mPtr);
}
}
return true;
}複製程式碼
MessageQueue 根據訊息的觸發時間,將新訊息插入到合適的位置,保證所有的訊息的時間順序。
3. 處理訊息
訊息的傳送已經分析過了,下面需要分析的是如何獲取訊息並處理訊息,繼續分析例項程式碼:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tvText.setText(msg.obj.toString());
}
};複製程式碼
從示例程式碼中可以看到 Handler 的 handleMessage(Message msg) 負責處理訊息,但是並沒有看到是如何獲取到訊息的。需要在 Handler 的原始碼中查詢是在哪裡呼叫 handleMessage(Message msg) 方法的。
通過在 Handler 的原始碼中查詢,發現是在 dispatchMessage(Message msg) 方法中呼叫 handleMessage(Message msg) 的。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}複製程式碼
3.1 Looper 迴圈從訊息佇列中取訊息
dispatchMessage(Message msg) 方法中,根據不同的情況呼叫不同的訊息處理方法。繼續向上查詢 dispatchMessage(Message msg) 的引用,發現是在 Looper 的 loop() 方法中呼叫的,而在之前分析 Looper 的建立時,可以知道在 ActivityThread 的 main 方法中有呼叫 loop() 方法。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}複製程式碼
下面分析 loop() 方法:
public static void loop() {
final Looper me = myLooper(); // 獲取當前執行緒的 Looper 物件
if (me == null) { // 當前執行緒沒有 Looper 物件則丟擲異常
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; // 獲取到當前執行緒的訊息佇列
// 清空遠端呼叫端程式的身份,用本地程式的身份代替,確保此執行緒的身份是本地程式的身份,並跟蹤該身份令牌
// 這裡主要用於保證訊息處理是發生在當前 Looper 所在的執行緒
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// 無限迴圈
for (;;) {
Message msg = queue.next(); // 從訊息佇列中獲取訊息,可能會阻塞
if (msg == null) {
// 沒有訊息則退出迴圈,正常情況下不會退出的,只會阻塞在上一步,直到有訊息插入並喚醒返回訊息
return;
}
// 預設為 null,可通過 setMessageLogging() 方法來指定輸出,用於 debug 功能
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// 開始跟蹤,並寫入跟蹤訊息,用於 debug 功能
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// 通過 Handler 分發訊息
msg.target.dispatchMessage(msg);
} finally {
// 停止跟蹤
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// 確保在分發訊息的過程中執行緒的身份沒有改變,如果改變則發出警告
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked(); // 回收訊息,將 Message 放入訊息池
}
}複製程式碼
在 loop() 方法中,會不停的迴圈以下操作:
呼叫當前執行緒的 MessageQueue 物件的 next() 方法獲取訊息
通過訊息的target,即 Handler 分發訊息
回收訊息,將分發後的訊息放入訊息池
3.1 從訊息佇列中獲取訊息
在 loop() 方法中獲取訊息時有可能會阻塞,來看下 MessageQueue 的 next() 方法的實現:
Message next() {
// 如果訊息佇列退出,則直接返回
// 正常執行的應用程式主執行緒的訊息佇列是不會退出的,一旦退出則應用程式就會崩潰
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // 記錄空閒時處理的 IdlerHandler 數量,可先忽略
int nextPollTimeoutMillis = 0; // native 層使用的變數,設定的阻塞超時時長
// 開始迴圈獲取訊息
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 呼叫 native 方法阻塞,當等待nextPollTimeoutMillis時長,或者訊息佇列被喚醒,都會停止阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 嘗試獲取下一條訊息,獲取到則返回該訊息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; // 獲取訊息佇列中的第一條訊息
if (msg != null && msg.target == null) {
// 如果 msg 為 Barrier 型別的訊息,則攔截所有同步訊息,獲取第一個非同步訊息
// 迴圈獲取第一個非同步訊息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果 msg 的觸發時間還沒有到,設定阻塞超時時長
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲取訊息並返回
mBlocked = false;
if (prevMsg != null) {
// 如果 msg 不是訊息佇列的第一條訊息,上一條訊息的 next 指向 msg 的 next。
prevMsg.next = msg.next;
} else {
// 如果 msg 是訊息佇列的第一條訊息,則 msg 的 next 作為訊息佇列的第一條訊息 // msg 的 next 置空,表示從訊息佇列中取出了 msg。
mMessages = msg.next;
}
msg.next = null; // msg 的 next 置空,表示從訊息佇列中取出了 msg
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); // 標記 msg 為正在使用
return msg; // 返回該訊息,退出迴圈
}
} else {
// 如果沒有訊息,則設定阻塞時長為無限,直到被喚醒
nextPollTimeoutMillis = -1;
}
// 如果訊息正在退出,則返回 null
// 正常執行的應用程式主執行緒的訊息佇列是不會退出的,一旦退出則應用程式就會崩潰
if (mQuitting) {
dispose();
return null;
}
// 第一次迴圈 且 (訊息佇列為空 或 訊息佇列的第一個訊息的觸發時間還沒有到)時,表示處於空閒狀態
// 獲取到 IdleHandler 數量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 沒有 IdleHandler 需要執行,迴圈並等待
mBlocked = true; // 設定阻塞狀態為 true
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 執行 IdleHandler,只有第一次迴圈時才會執行
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // 釋放 IdleHandler 的引用
boolean keep = false;
try {
keep = idler.queueIdle(); // 執行 IdleHandler 的方法
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler); // 移除 IdleHandler
}
}
}
// 重置 IdleHandler 的數量為 0,確保不會重複執行
// pendingIdleHandlerCount 置為 0 後,上面可以通過 pendingIdleHandlerCount < 0 判斷是否是第一次迴圈,不是第一次迴圈則 pendingIdleHandlerCount 的值不會變,始終為 0。
pendingIdleHandlerCount = 0;
// 在執行 IdleHandler 後,可能有新的訊息插入或訊息佇列中的訊息到了觸發時間,所以將 nextPollTimeoutMillis 置為 0,表示不需要阻塞,重新檢查訊息佇列。
nextPollTimeoutMillis = 0;
}
}複製程式碼
nativePollOnce(ptr, nextPollTimeoutMillis) 是呼叫 native 層的方法執行阻塞操作,其中 nextPollTimeoutMillis 表示阻塞超時時長:
nextPollTimeoutMillis = 0 則不阻塞
nextPollTimeoutMillis = -1 則一直阻塞,除非訊息佇列被喚醒
三、總結
訊息機制的流程如下:
準備階段:
在子執行緒呼叫 Looper.prepare() 方法或 在主執行緒呼叫 Lopper.prepareMainLooper() 方法建立當前執行緒的 Looper 物件(主執行緒中這一步由 Android 系統在應用啟動時完成)
在建立 Looper 物件時會建立一個訊息佇列 MessageQueue
Looper 通過 loop() 方法獲取到當前執行緒的 Looper 並啟動迴圈,從 MessageQueue 不斷提取 Message,若 MessageQueue 沒有訊息,處於阻塞狀態
傳送訊息
使用當前執行緒建立的 Handler 在其它執行緒通過 sendMessage() 傳送 Message 到 MessageQueue
MessageQueue 插入新 Message 並喚醒阻塞
獲取訊息
重新檢查 MessageQueue 獲取新插入的 Message
Looper 獲取到 Message 後,通過 Message 的 target 即 Handler 呼叫 dispatchMessage(Message msg) 方法分發提取到的 Message,然後回收 Message 並繼續迴圈獲取下一個 Message
Handler 使用 handlerMessage(Message msg) 方法處理 Message
阻塞等待
- MessageQueue 沒有 Message 時,重新進入阻塞狀態