Handler
的原理分析這個標題,很多文章都寫過,最近認真將原始碼逐行一字一句研究,特此也簡單總結一遍。
首先是Handler
整個Android訊息機制的簡單概括:
分三部分對訊息機制的整個流程進行闡述:
Handler
的建立,包括Looper
、MessageQueue
的建立;Handler
傳送訊息,Message
是如何進入訊息佇列MessageQueue
的(入列);Looper
輪詢訊息,Message
出列,Handler
處理訊息。
一、Handler建立流程分析
1.Handler如何被建立的
// 最簡單的建立方式
public Handler() {
this(null, false);
}
// ....還有很多種方式,但這些方式最終都執行這個構造方法
public Handler(Callback callback, boolean async) {
// 1.檢查記憶體洩漏
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());
}
}
// 2.通過Looper.myLooper()獲取當前執行緒的Looper物件
mLooper = Looper.myLooper();
// 3.如果Looper為空,丟擲異常
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;
}
複製程式碼
首先,如何避免Handler
的記憶體洩漏是一個非常常見的面試題,其實Handler
的原始碼中已經將答案非常清晰告知給了開發者,即讓Handler
的匯出類保證為static
的,如果需要,將Context
作為弱引用的依賴注入進來。
同時,在Handler
建立的同時,會嘗試獲取當前執行緒唯一的Looper
物件:
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
複製程式碼
關於ThreadLocal
,我在上一篇文章中已經進行了分析,現在我們知道了ThreadLocal
保證了當前執行緒內有且僅有唯一的一個Looper
。
2.Looper是如何保證執行緒單例的
那就是需要呼叫Looper.prepare()
方法:
public final class Looper {
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
複製程式碼
這也就說明了,為什麼當前執行緒沒有Looper
的例項時,會丟擲一個異常並提示開發者需要呼叫Looper.prepare()
方法了。
也正如上述程式碼片段所描述的,如果當前執行緒已經有了Looper
的例項,也會丟擲一個異常,提示使用者每個執行緒只能有一個Looper
(throw new RuntimeException("Only one Looper may be created per thread");
)。
此外,在Looper
例項化的同時,也建立了對應的MessageQueue
,這也就說明,一個執行緒有且僅有一個Looper
,也僅有一個MessageQueue
。
二、傳送訊息流程分析
1.sendMessage()分析
sendMessage()
流程如下:
// 1.傳送即時訊息
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
// 2.實際上是發射一個延時為0的Message
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 3.將訊息和延時的時間進行入列(訊息佇列)
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);
}
// 4.內部實際上還是執行了MessageQueue的enqueueMessage()方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
注意第四步實際上將Handler
物件最為target,附著在了Message
之上;接下來看MessageQueue
類內部是如何對Message
進行入列的。
2.MessageQueue訊息入列
boolean enqueueMessage(Message msg, long when) {
//... 省略部分程式碼
synchronized (this) {
msg.markInUse();
msg.when = when;
// 獲得連結串列頭的Message
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 若有以下情景之一,將Message置於連結串列頭
// 1.頭部Message為空,連結串列為空
// 2.訊息為即時Message
// 3.頭部Message的時間戳大於最新Message的時間戳
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 反之,將Message插入到連結串列對應的位置
Message prev;
// for迴圈就是找到合適的位置,並將最新的Message插入連結串列
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製程式碼
MessageQueue
的資料結構本身是一個單向連結串列。
三、接收訊息分析
當Handler
建立好後,若在此之前呼叫了Looper.prepare()
初始化Looper
,還需要呼叫Looper.loop()
開始該執行緒內的訊息輪詢。
1.Looper.loop()
public static void loop() {
// ...省略部分程式碼
// 1. 獲取Looper物件
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 2.獲取messageQueue
final MessageQueue queue = me.mQueue;
// 3. 輪詢訊息,這裡是一個死迴圈
for (;;) {
// 4.從訊息佇列中取出訊息,若訊息佇列為空,則阻塞執行緒
Message msg = queue.next();
if (msg == null) {
return;
}
// 5.派發訊息到對應的Handler
msg.target.dispatchMessage(msg);
// ...
}
}
複製程式碼
比較簡單,需要注意的一點是MessageQueue.next()
是一個可能會阻塞執行緒的方法,當有訊息時會輪詢處理訊息,但如果訊息佇列中沒有訊息,則會阻塞執行緒。
2.MessageQueue.next()
private native void nativePollOnce(long ptr, int timeoutMillis);
Message next() {
// ...省略部分程式碼
int nextPollTimeoutMillis = 0;
for (;;) {
// ...
// native方法
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 從訊息佇列中取出訊息
if (msg != null) {
// 當時間小於message的時間戳時,獲取時間差
if (now < msg.when) {
// 該值將會導致在下次迴圈中阻塞對應時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 取出訊息並返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
}
// ...
}
}
複製程式碼
注意程式碼片段最上方的native方法——迴圈體內首先呼叫nativePollOnce(ptr, nextPollTimeoutMillis)
,這是一個native方法,實際作用就是通過Native層的MessageQueue
阻塞nextPollTimeoutMillis
毫秒的時間:
- 1.如果nextPollTimeoutMillis=-1,一直阻塞不會超時。
- 2.如果nextPollTimeoutMillis=0,不會阻塞,立即返回。
- 3.如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時),如果期間有程式喚醒會立即返回。
搞清楚這一點,其它就都好理解了。
3.最終將訊息傳送給Handler
正如上文所說的,msg.target.dispatchMessage(msg)
實際上就是呼叫Handler.dispatchMessage(msg)
,內部最終也是執行了Handler.handleMessage()
回撥:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果訊息沒有定義callBack,或者不是通過
// Handler(Callback)的方式例項化Handler,
// 最終會走到這裡
handleMessage(msg);
}
}
複製程式碼
參考&感謝
- 《Android開發藝術探索》
- 深入理解MessageQueue
- Android Handler:手把手帶你深入分析 Handler機制原始碼
關於我
Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ❤️,也歡迎關注我的部落格或者Github。
如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?