安卓進階(3)之Handler/Looper/MessageQueue原始碼分析以及原理理解
前言
安卓系統是訊息驅動的,所以深刻了解Handler整個訊息分發機制,對於我們瞭解安卓系統,是一個必不可少的知識點。整個過程中,我們需要重點關注的類是:
1. Handler
2. Looper
3. MessageQueue
4. Meesage
5. ThreadLocal
6. Thread
這幾個類之間的聯絡:Handler傳送訊息和接收訊息都是通過Message,而基於連結串列的Message是由MessageQueue管理的,Looper用於阻塞和喚醒執行緒以便處理Message,為確保每個執行緒中頂多只有一個Looper物件,Android使用ThreadLocal來管理Looper,ThreadLocal並不是Thread的子類,它只是用ThreadLocal.ThreadLocalMap來儲存Looper,最終Looper物件被儲存在Thread物件中的threadLocals變數中。
本部落格所分析的handler訊息分發機制是基於andorid-28這個sdk來的,之所以要強調,是因為我發現ThreadLocal這個類,在android-23和android-28中不太一樣,android-23中用來儲存資料的類是ThreadLocal.Value,而android-28中是ThreadLocal.ThreadLocalMap,看了兩者程式碼,其實差不多。不知道其他類有沒有其他的區別,這裡特此強調一下。
Handler原理
基本使用
先來看看我們使用handler最常用的用法,一般在子執行緒做耗時操作,然後用handler來通知主執行緒進行UI更新啥的:
Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: Log.d("test","接收到訊息了。。"); break; } } }; private void operate(){ new Thread(new Runnable() { @Override public void run() { try { Log.d("test", "模擬耗時操作..."); Thread.sleep(5000); Log.d("test", "子執行緒耗時操作結束,準備通知主執行緒..."); handler.sendEmptyMessageDelayed(1, 5 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
列印的log如下:
09-16 13:55:14.923 24522-24756/com.rxjavatest D/test: 模擬耗時操作...
09-16 13:55:19.923 24522-24756/com.rxjavatest D/test: 子執行緒耗時操作結束,準備通知主執行緒...
09-16 13:55:24.929 24522-24522/com.rxjavatest D/test: 接收到訊息了。。
訊息傳送機制
接下來,我們分析handler.sendEmptyMessageDelayed()
到底幹了什麼:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
可以看到,雖然我們沒傳Message物件進去,但是sendEmptyMessageDelayed()方法裡面會通過Message.obtain()獲取一個複用的Message物件,然後將延時時間賦值到此msg物件中。剛開始我也不理解這裡為什麼要有一個Message物件,後面看了MessageQueue才得知,handler傳送訊息是基於Message的,每個訊息事件,必須得由Message物件來傳遞。 然後,一路順藤摸瓜跟蹤後面的幾個方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到,關於傳送訊息的延時的時間計算,它是由我們傳遞進去的delayMillis,加上SystemClock.uptimeMillis(),SystemClock.uptimeMillis()獲取的時間是從開機到當前所佔用的時間,如果手機進入深度睡眠(比如關閉螢幕有可能進入深度睡眠),SystemClock.uptimeMillis()獲取的時間又是從0開始的了。大家可以試一下,用SystemClock.uptimeMillis()獲取的時間,一般是小於手機設定中看到的開機時間的。 然後,繼續往下看:
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);
}
這裡就將訊息加入到了MessageQueue的enqueueMessage()裡面。handler傳送訊息暫告一段落,後面分析MessageQueue再往下細說。
訊息回撥機制
咱回過頭來看看handler中的handleMessage()是如何回撥的。handler的回撥支援兩種匿名內部類的寫法,第一種是繼承Handler的,第二種是實現Handler.Callback介面的,很顯然,第一種簡潔多了:
new Handler(){
@Override
public void handleMessage(Message msg) {
}
};
new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
兩者都是在dispatchMessage()中處理的: :
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
這裡的msg.callback只有使用handler.post(Runnable r)時,才會對smg.callback賦值,目前只能是null,雖然Message中有一個setCallback(),但它是一個隱藏的方法,估計是還沒設計好吧。else裡面邏輯也很簡單,如果callback物件不為空,走callback的回撥,否則就走繼承handler的回撥。那麼dispatchMessage()又是在哪裡呼叫的呢:
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
上面這段程式碼是Looper.loop()
中死迴圈裡的,在Looper.loop()裡,通過MessageQueue.next()
阻塞,一有訊息就獲取到,並通過msg.target.dispatchMessage(msg)
將訊息回撥給Handler所線上程的handleMessage()
,這就是Handler的訊息回撥機制。
訊息回收機制
我們接下來看看handler.removeCallbacksAndMessages()
做了什麼:
/**
* Remove any pending posts of callbacks and sent messages whose
* <var>obj</var> is <var>token</var>. If <var>token</var> is null,
* all callbacks and messages will be removed.
*/
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
handler中刪除所有回撥和訊息,最終還是刪除MessageQueue中的回撥和訊息,當token為null時,表示刪除所有。我們接著看MessageQueue中刪除回撥和訊息是怎麼做的:
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
**Message是基於連結串列結構的,所以通過Message.next()不斷迴圈回收掉message物件。**看到這裡各種例項物件的命名,n/nn/h/p,google工程師也偷懶了?。接下來再看下Message.recycleUnchecked()
做了什麼事情:
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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物件中各種屬性都置空,就表示清除了此Message物件~
handler.sendMessage()和handler.post()的區別
相同點:兩者都是在當前執行緒事件處理完後,回撥到new了Handler的那個執行緒裡面,再去做邏輯處理。 不同點:handler.sendMessage()會將處理的這個message物件傳過去,算是一個優化吧。
MessageQueue原理
訊息佇列中我們重點看兩個方法,訊息加入佇列enqueueMessage()和取訊息佇列next()。
訊息加入佇列
boolean enqueueMessage(Message msg, long when) {
......
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;
}
大家重點看一下for (; ; ){}這個死迴圈。Message是一個連結串列資料的結構,當有一個新的msg物件到來,這個msg插入到原始msg裡面會有兩種可能,一種是插到最後面,一種是插到中間,而不管是哪一種,都是根據msg.when
這個時間引數來的,也就是我們在handler中提到的SystemColock.upTimeMillis()+delaysTime
這兩個時間。
取出訊息
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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 && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
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;
}
......
}
}
可以看到,還是在for(; ; ){}這個死迴圈裡,會根據msg.when去取mMessages
這個公共變數值中時間剛好到了的msg物件。
Looper原理
Looper初現
Looper最開始出現的地方,是我們在new一個handler物件時:
public Handler(Callback callback, boolean async) {
......
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;
}
在handler的構造方法中,會去獲取handler所線上程的looper例項,以及looper例項中的訊息佇列例項。這裡我們也能看到,如果mLooper==null
,會拋異常。所以,我們在new一個handler時,需要先在當前執行緒呼叫Looper.prepare()
初始化Looper。
為什麼在主執行緒不需要我們呼叫Looper.prepare()初始化?
那有人就會問了,為啥我們在主執行緒不初始化Looper例項,就能隨意new Handler呢?這是因為,在ActivityThread.java
這個類中,已經呼叫了Looper.prepareMainLooper()
了:
public static void main(String[] args) {
.......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
.......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
ActivityThread
中的main()方法,是應用程式的主入口。從這裡我們可以看到,深刻理解handler
和looper
機制,對於理解以訊息驅動的安卓系統來說,太特麼重要了。首先初始化了主執行緒的Looper例項,然後通過Looper.loop()讓主執行緒阻塞,最後一個拋異常的語句也說明了,主執行緒必須是阻塞的,否則就會拋異常。
所以,如果在主執行緒呼叫以下方法,妥妥地讓你crash:
Looper.getMainLooper().quitSafely();
子執行緒使用Handler和Looper
有時候,我們想在子執行緒使用Handler進行訊息分發,使用情況如下:
new Thread(
new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
}
}
).start();
當然了,如果我們在子執行緒中使用了handler,當退出子執行緒時,我們必須手動呼叫Looper.myLooper().quitSafely();
或者Looper.myLooper().quit();
否則子執行緒就會由於Looper一直處於阻塞狀態,而導致記憶體洩漏問題。
Looper阻塞和喚醒原理
上面一直在說阻塞,和handler接收訊息,那阻塞和喚醒的原理又是什麼樣的呢?我們來看下Looper.loop():
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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;
}
......
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked();
}
}
在for(; ; ){}
這個死迴圈裡,其實導致執行緒阻塞的,是MessageQueue
物件中的next()
,當取出了訊息,就會通過msg.target.dispatchMessage(msg)
將訊息分發出去,最終走到handleMessage(msg)
。接下來再看看MessageQueue
中的next()
方法:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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 && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
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;
}
......
}
在for(; ; ){}
這個死迴圈當中,主要有兩點會進入阻塞狀態,一是當訊息佇列中沒有訊息時,它會使執行緒進入等待狀態;二是訊息佇列中有訊息,但是訊息指定了執行的時間,而現在還沒有到這個時間,執行緒也會進入等待狀態。而nativePollOnce()
是一個native方法,它跟nativeWake()
相對應使用,一個用於阻塞執行緒,一個用於喚醒執行緒,而它們底層的通訊機制,其實是屬於linux通訊中的管道通訊機制。而管道通訊的特點是:程序試圖讀一個空管道時,在資料寫入管道前,程序將一直阻塞,程序再試圖寫管道,在其它程序從管道中讀走資料之前,寫程序將一直阻塞。MessageQueue的插入訊息和取出訊息,分別對應管道通訊的寫資料和讀資料。
ThreadLocal原理以及它在Looper中的應用
ThreadLocal翻譯過來叫“本地執行緒”,其實它並不繼承於Thread,它的作用,是為了保證一個執行緒,最多隻能有一個類的例項物件。使用場景的話,就是在多執行緒的環境中,我們為了保證每一個執行緒最多隻能有一個類的例項物件,這時候用ThreadLocal來儲存此物件,是再適合不過的了。在安卓中,handler訊息驅動機制規定每個執行緒只能有一個Looper來管理訊息的阻塞和喚醒,所以,google工程師們就用了ThreadLocal來管理Looper。
那ThreadLocal儲存的原理又是怎樣的呢?首先,我們來看看Looper類中的的一個重要的物件:
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
由此可以看到,sThreadLocal物件不屬於Looper類,而屬於靜態靜態方法區。在例項化Looper物件前,會對當前執行緒中是否有Looper做一個異常判斷:
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));
}
sThreadLocal.get()
獲取的就是當前執行緒的looper物件,如果我們在當前執行緒呼叫過Looper.prepare()方法了,再次呼叫時,sThreadLocal.get()
不為空,就會拋異常。這裡的sThreadLocal.get()
和sThreadLocal.set()
,就是ThreadLocal最重要的兩個方法了。
ThreadLocal設定Looper
我們首先看看sThreadLocal.set()
幹了什麼:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我們的Looper
物件,是儲存在ThreadLocalMap
中,而Thread
類中有一個threadLocals變數來放置ThreadLocalMap
物件,所以,我們的Looper物件,以當前執行緒物件t
為key
,以當前執行緒的looper
物件為value
。
從ThreadLocal取出Looper
我們在呼叫Looper.myLooper()時,會去取當前執行緒的Looper物件:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那又是如何獲取的呢:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
根據當前執行緒,去獲取當前執行緒中的ThreadLocalMap
物件,那麼問題來了,作為儲存資料的ThreadLocalMap
類,它的資料結構又是什麼樣的呢?
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
從這裡看到,threadLocalMap
是仿HashMap
以鍵值對的方式儲存資料的,key
被儲存在弱引用中,value
被儲存在Object物件中。這裡我查資料查了蠻久,不清楚為什麼要將ThreadLocal
物件以弱引用的方式儲存,而不是直接用強引用?有了解的朋友麻煩多多留言,謝謝了!