記得不少年前的一次面試中,面試官問了這麼一個問題,你在項目中通常如何實現線程切換?
他的本意應該是考察 RxJava 的使用,只是個人答案是 Handler
,他也就沒有再追問下去了。在早期 Android 開發的荒蕪時代,Handler 的確承擔了項目中大部分的線程切換工做,一般包括子線程更新 UI 和消息傳遞。不光在咱們本身的應用中,在整個 Android 體系中,Handler 消息機制也是極其重要的,不亞於 Binder 的地位。 ActivityThread.java
中的內部類 H
就是一個 Handler,它內部定義了幾十種消息類型來處理一些系統事件。java
Handler 的重要性毋庸置疑,今天就經過 AOSP 源碼來深刻學習 Handler。相關類的源碼包含註釋均已上傳到個人 Github 倉庫 android_9.0.0_r45 :android
Handler.javagit
Looper.javagithub
Handler
用來發送和處理線程對應的消息隊列 MessageQueue
中存儲的 Message
。每一個 Handler 實例對應一個線程以及該線程的消息隊列。當你建立一個新的 Handler,它會綁定建立它的線程和消息隊列,而後它會向消息隊列發送 Message
或者 Runnable
,而且在它們離開消息隊列時執行。bash
Handler 有兩個主要用途:微信
以上翻譯自官方註釋。說白了,Handler 只是安卓提供給開發者用來發送和處理事件的,而消息如何存儲,消息如何循環取出,這些邏輯則交給 MessageQueue
和 Looper
來處理,使用者並不須要關心。但要真正瞭解 Handler 消息機制,認真讀一遍源碼就必不可少了。app
Handler 的構造函數大體上能夠分爲兩類,先來看第一類:less
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {
// 若是是匿名類、內部類、本地類,且沒有使用 static 修飾符,提示可能致使內存泄漏
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());
}
}
// 從當前線程的 ThreadLocal獲取 Looper
mLooper = Looper.myLooper();
if (mLooper == null) { // 建立 Handler 以前必定要先建立 Looper。主線程已經自動爲咱們建立。
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // Looper 持有一個 MessageQueue
mCallback = callback; // handleMessage 回調
mAsynchronous = async; // 是否異步處理
}
複製代碼
這一類構造函數最終調用的都是兩個參數的方法,參數中不傳遞 Looper
,因此要顯式檢查是否已經建立 Looper。建立 Handler 以前必定要先建立 Looper,不然會直接拋出異常。在主線程中 Looper 已經自動建立好,無需咱們手動建立,在 ActivityThread.java
的 main()
方法中能夠看到。Looper 持有一個消息隊列 MessageQueue
,並賦值給 Handler 中的 mQueue
變量。Callback
是一個接口,定義以下:
public interface Callback {
public boolean handleMessage(Message msg);
}
複製代碼
經過構造器參數傳入 CallBack 也是 Handler 處理消息的一種實現方式。
再回頭看一下在上面的構造函數中是如何獲取當前線程的 Looper 的?
mLooper = Looper.myLooper(); // 獲取當前線程的 Looper
複製代碼
這裏先記着,回頭看到 Looper 源碼時再詳細解析。
看過 Handler 的第一類構造函數,第二類其實就很簡單了,只是多了 Looper
參數而已:
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
複製代碼
直接賦值便可。
除此以外還有幾個標記爲 @hide
的構造函數就不做說明了。
發送消息你們最熟悉的方法就是 sendMessage(Message msg)
了,可能有人不知道其實還有 post(Runnable r)
方法。雖然方法名稱不同,但最後調用的都是同一個方法。
sendMessage(Message msg)
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long delayMillis)
sendEmptyMessageAtTime(int what, long uptimeMillis)
sendMessageAtTime(Message msg, long uptimeMillis)
複製代碼
幾乎全部的 sendXXX()
最後調用的都是 sendMessageAtTime()
方法。
post(Runnable r)
postAtTime(Runnable r, long uptimeMillis)
postAtTime(Runnable r, Object token, long uptimeMillis)
postDelayed(Runnable r, long delayMillis)
postDelayed(Runnable r, Object token, long delayMillis)
複製代碼
全部的 postXXX()
方法都是調用 getPostMessage()
將 參數中的 Runnable 包裝成 Message,再調用對應的 sendXXX()
方法。看一下 getPostMessage()
的代碼:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
複製代碼
主要是把參數中的 Runnable 賦給 Message 的 callback
屬性。
異曲同工,發送消息的重任最後都落在了 sendMessageAtTime()
身上。
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
來處理。
再額外提一點,enqueueMessage()
方法中的參數 uptimeMillis
並非咱們傳統意義上的時間戳,而是調用 SystemClock.updateMillis()
獲取的,它表示自開機以來的毫秒數。
Message 的入隊工做其實是由 MessageQueue 經過 enqueueMessage()
函數來完成的。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { // msg 必須有 target
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { // msg 不能正在被使用
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;
}
複製代碼
從源碼中能夠看出來,MessageQueue 是用鏈表結構來存儲消息的,消息是按觸發時間的順序來插入的。
enqueueMessage() 方法是用來存消息的,既然存了,確定就得取,這靠的是 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();
}
// 阻塞方法,主要是經過 native 層的 epoll 監聽文件描述符的寫入事件來實現的。
// 若是 nextPollTimeoutMillis = -1,一直阻塞不會超時。
// 若是 nextPollTimeoutMillis = 0,不會阻塞,當即返回。
// 若是 nextPollTimeoutMillis > 0,最長阻塞nextPollTimeoutMillis毫秒(超時),若是期間有程序喚醒會當即返回。
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.
// msg.target == null表示此消息爲消息屏障(經過postSyncBarrier方法發送來的)
// 若是發現了一個消息屏障,會循環找出第一個異步消息(若是有異步消息的話),
// 全部同步消息都將忽略(日常發送的通常都是同步消息)
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.
// 獲得 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(); // 標記 FLAG_IN_USE
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;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
// Idle handle 僅當隊列爲空或者隊列中的第一個消息將要執行時纔會運行
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
// 沒有 idle handler 須要運行,繼續循環
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
// 只有第一次循環時纔會執行下面的代碼塊
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
// 將 pendingIdleHandlerCount 置零保證再也不運行
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
複製代碼
next()
方法是一個死循環,可是當沒有消息的時候會阻塞,避免過分消耗 CPU。nextPollTimeoutMillis
大於 0 時表示等待下一條消息須要阻塞的時間。等於 -1 時表示沒有消息了,一直阻塞到被喚醒。
這裏的阻塞主要靠 native 函數 nativePollOnce()
來完成。其具體原理我並不瞭解,想深刻學習的同窗能夠參考 Gityuan 的相關文 Android消息機制2-Handler(Native層) 。
MessageQueue 提供了消息入隊和出隊的方法,但它本身並非自動取消息。那麼,誰來把消息取出來並執行呢?這就要靠 Looper 了。
建立 Handler 以前必須先建立 Looper,而主線程已經爲咱們自動建立了 Looper,無需再手動建立,見 ActivityThread.java
的 main()
方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper(); // 建立主線程 Looper
...
}
複製代碼
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
複製代碼
sMainLooper
只能被初始化一次,也就是說 prepareMainLooper()
只能調用一次,不然將直接拋出異常。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// 每一個線程只能執行一次 prepare(),不然會直接拋出異常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 將 Looper 存入 ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
主線程中調用的是 prepare(false)
,說明主線程 Looper 是不容許退出的。由於主線程須要源源不斷的處理各類事件,一旦退出,系統也就癱瘓了。而咱們在子線程調用 prepare()
來初始化 Looper時,默認調動的是 prepare(true)
,子線程 Looper 是容許退出的。
每一個線程的 Looper 是經過 ThreadLocal
來存儲的,保證其線程私有。
再回到文章開頭介紹的 Handler 的構造函數中 mLooper
變量的初始化:
mLooper = Looper.myLooper();
複製代碼
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製代碼
也是經過當前線程的 ThreadLocal
來獲取的。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); // 建立 MessageQueue
mThread = Thread.currentThread(); // 當前線程
}
複製代碼
再對照 Handler 的構造函數:
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
複製代碼
其中的關係就很清晰了。
Looper
持有 MessageQueue
對象的引用Handler
持有 Looper
對象的引用以及 Looper
對象的 MessageQueue
的引用看到這裏,消息隊列尚未真正的運轉起來。咱們先來看一個子線程使用 Handler 的標準寫法:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
複製代碼
讓消息隊列轉起來的核心就是 Looper.loop()
。
public static void loop() {
final Looper me = myLooper(); // 從 ThreadLocal 中獲取當前線程的 Looper
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;
}
... // 省略部分代碼
try {
msg.target.dispatchMessage(msg); // 經過 Handler 分發 Message
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
... // 省略部分代碼
msg.recycleUnchecked(); // 將消息放入消息池,以便重複利用
}
}
複製代碼
簡單說就是一個死循環不停的從 MessageQueue 中取消息,取到消息就經過 Handler 來進行分發,分發以後回收消息進入消息池,以便重複利用。
從消息隊列中取消息調用的是 MessageQueue.next()
方法,以前已經分析過。在沒有消息的時候可能會阻塞,避免死循環消耗 CPU。
取出消息以後進行分發調用的是 msg.target.dispatchMessage(msg)
,msg.target
是 Handler 對象,最後再來看看 Handler 是如何分發消息的。
public void dispatchMessage(Message msg) {
if (msg.callback != null) { // callback 是 Runnable 類型,經過 post 方法發送
handleCallback(msg);
} else {
if (mCallback != null) { // Handler 的 mCallback參數 不爲空時,進入此分支
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); // Handler 子類實現的 handleMessage 邏輯
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
複製代碼
postXXX()
發送的,直接執行 Runnable 便可。mCallback.handleMessage(msg)
來處理消息handleMessage()
方法。這好像也是咱們最經常使用的一種形式。之因此把 Message
放在最後說,由於我以爲對整個消息機制有了一個完整的深刻認識以後,再來了解 Message 會更加深入。首先來看一下它有哪些重要屬性:
int what :消息標識
int arg1 : 可攜帶的 int 值
int arg2 : 可攜帶的 int 值
Object obj : 可攜帶內容
long when : 超時時間
Handler target : 處理消息的 Handler
Runnable callback : 經過 post() 發送的消息會有此參數
複製代碼
Message 有 public
修飾的構造函數,可是通常不建議直接經過構造函數來構建 Message,而是經過 Message.obtain()
來獲取消息。
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();
}
複製代碼
sPool
是消息緩存池,鏈表結構,其最大容量 MAX_POOL_SIZE
爲 50。obtain()
方法會直接從消息池中取消息,循環利用,節約資源。當消息池爲空時,再去新建消息。
還記得 Looper.loop()
方法中最後會調用 msg.recycleUnchecked()
方法嗎?這個方法會回收已經分發處理的消息,並放入緩存池中。
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++;
}
}
}
複製代碼
說到這裏,Handler 消息機制就所有分析完了,相信你們也對整個機制瞭然於心了。
Looper.loop()
方法讓消息隊列循環起來。loop()
方法中會調用 MessageQueue 的 next()
方法來不停的取消息。loop()
方法中取出來的消息最後仍是會調用 Handler 的 dispatchMessage()
方法來進行分發和處理。最後,關於 Handler 一直有一個頗有意思的面試題:
Looper.loop()
是死循環爲何不會卡死主線程 ?
看起來問的好像有點道理,實則否則。你仔細思考一下,loop() 方法的死循環和卡死主線程有任何直接關聯嗎?其實並無。
回想一下咱們常常在測試代碼時候寫的 main()
函數:
public static void main(){
System.out.println("Hello World");
}
複製代碼
姑且就把這裏當作主線程,它裏面沒有死循環,執行完就直接結束了,沒有任何卡頓。可是問題是它就直接結束了啊。在一個 Android 應用的主線程上,你但願它直接就結束了嗎?那確定是不行的。因此這個死循環是必要的,保證程序能夠一直運行下去。Android 是基於事件體系的,包括最基本的 Activity 的生命週期都是由事件觸發的。主線程 Handler 必須保持永遠能夠相應消息和事件,程序才能正常運行。
另外一方面,這並非一個時時刻刻都在循環的死循環,當沒有消息的時候,loop() 方法阻塞,並不會消耗大量 CPU 資源。
關於 Handler 就說到這裏了。還記得文章說過線程的 Looper 對象是保存在 ThreadLocal 中的嗎?下一篇文章就來講說 ThreadLocal
是如何保存 線程局部變量 的。
文章首發微信公衆號:
秉心說
, 專一 Java 、 Android 原創知識分享,LeetCode 題解。更多最新原創文章,掃碼關注我吧!