一、提出問題
面試時常被問到的問題:
- 簡述 Android 消息機制
- Android 中 Handler,Looper,MessageQueue,Message 有什么關系?
這倆問題其實是一個問題,其實只要搞清楚了 Handler,Looper,MessageQueue,Message 的作用和聯(lián)系,就理解了 Android 的 Handler 消息機制。那么再具體一點:
- 為什么在主線程可以直接使用 Handler?
- Looper 對象是如何綁定 MessageQueue 的?
- MessageQueue 里的消息從哪里來?Handler是如何往MessageQueue中插入消息的?
- Message 是如何綁定 Handler 的?
- Handler 如何綁定 MessageQueue?
- 關于 handler,在任何地方 new handler 都是什么線程下?
- Looper 循環(huán)拿到消息后怎么處理?
二、解決問題
那么,我們從主線程的消息機制開始分析:
2.1 主線程 Looper 的創(chuàng)建和循環(huán)
Android 應用程序的入口是 main 函數(shù),主線程 Looper 的創(chuàng)建也是在這里完成的。
ActivityThread --> main() 函數(shù)
public static void main(){
// step1: 創(chuàng)建主線程Looper對象
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
// 綁定應用進程,布爾標記是否為系統(tǒng)進程
thread.attach(false);
// 實例化主線程 Handler
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
// step2: 開始循環(huán)
Loop.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper.prepareMainLooper()
用來創(chuàng)建主線程的 Looper 對象,接下來先看這個方法的實現(xiàn)。
2.1.1 創(chuàng)建主線程 Looper
Looper --> prepareMainLooper()
private static Looper sMainLooper; // guarded by Looper.class
public static void prepareMainLooper(){
// step1: 調用本類 prepare 方法
prepare(false);
// 線程同步,如果變量 sMainLooper 不為空拋出主線程 Looper 已經創(chuàng)建
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// step2: 調用本類 myLooper 方法
sMainLooper = myLooper();
}
}
prepareMainLooper()
方法主要是使用 prepare(false)
創(chuàng)建當前線程的 Looper 對象,再使用 myLooper()
方法來獲取當前線程的 Looper 對象。
step1: Looper --> prepare()
// ThreadLocal 為每個線程保存單獨的變量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// Looper 類的 MessageQueue 變量
final MessageQueue mQueue;
// quitAllowed 是否允許退出,這里是主線程的 Looper 不可退出
private static void prepare(boolean quitAllowed) {
// 首先判定 Looper 是否存在
if(sThreadLocal.get() != null){
throw new RuntimeException("Only one Looper may be created per thread");
}
// 保存線程的副本變量
sThreadLoacal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
prepare()
方法中用 ThreadLocal 來保存主線程的 Looper 對象。ThreadLocal 可以看作是一個用來儲存數(shù)據(jù)的類,類似 HashMap、ArrayList等集合類,它存放著屬于當前線程的變量。ThreadLocal 提供了 get/set 方法分別用來獲取和保存變量。
比如在主線程通過prepare()
方法來創(chuàng)建 Looper 對象,并使用sThreadLoacal.set(new Looper(quitAllowed))
來保存主線程的 Looper 對象,那么在主線程調用myLooper()
(實際調用了sThreadLocal.get()
方法) 就是通過 ThreadLocal 來獲取主線程的 Looper 對象。如果在子線程調用這些方法就是通過 ThreadLocal 保存和獲取屬于子線程的 Looper 對象。
更多關于 ThreadLocal 的原理:
問題1:為什么在主線程可以直接使用 Handler?
因為主線程已經創(chuàng)建了 Looper 對象并開啟了消息循環(huán),通過上文的代碼就可以看出來。
問題2:Looper 對象是如何綁定 MessageQueue 的?或者說 Looper 對象創(chuàng)建 MessageQueue 過程。
很簡單,Looper 有個一成員變量 mQueue,它就是 Looper 對象默認保存的 MessageQueue。上面代碼中 Looper 有一個構造器,新建 Looper 對象時會直接創(chuàng)建 MessageQueue 并賦值給 mQueue。
問題2解決:在 new Looper 時就創(chuàng)建了 MessageQueue 對象并賦值給 Looper 的成員變量 mQueue。
step2: Looper --> myLooper()
// 也就是使用本類的ThreadLocal對象獲取之前創(chuàng)建保存的Looper對象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
這個方法就是通過 sThreadLocal 變量獲取當前線程的 Looper 對象,比較常用的一個方法。上文主線程 Looper 對象創(chuàng)建后使用該方法獲取了 Looper 對象。
2.1.2 開始循環(huán)處理消息
回到最開始的 main()
函數(shù),在創(chuàng)建了 Looper 對象以后就調用了 Looper.loop()
來循環(huán)處理消息,貼一下大致代碼:
public static void main(){
// step1: 創(chuàng)建主線程Looper對象
Looper.prepareMainLooper();
...
// step2: 開始循環(huán)
Loop.loop();
}
Looper --> loop()
public static void loop() {
// step1: 獲取當前線程的 Looper 對象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// step2: 獲取 Looper 保存的 MessageQueue 對象
final MessageQueue queue = me.mQueue;
...
// step3: 循環(huán)讀取消息,如果有則調用消息對象中儲存的 handler 進行發(fā)送
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
// step4: 使用 Message 對象保存的 handler 對象處理消息
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
- step1 :
myLooper()
方法就是通過 ThreadLocal 獲取當前線程的 Looper 對象,注意在哪個線程使用該方法就獲取的該線程的 Looper 對象。 - step2 :
me.mQueue
,這個 mQueue 就是上面問題2所說的在 Looper 對象創(chuàng)建時新建的 MessageQueue 變量。 - step3 :接下來是一個 for 循環(huán),首先通過
queue.next()
來提取下一條消息,具體是怎么提取的可以參考下面文章的 4.2 節(jié):
獲取到下一條消息,如果 MessageQueue 中沒有消息,就會進行阻塞。那么如果存在消息,它又是怎么放入 MessageQueue 的呢?或者說MessageQueue 里的消息從哪里來?Handler是如何往MessageQueue中插入消息的?先不說這個,把這個問題叫作問題3后面分析。
- step4 :
msg.target.dispatchMessage(msg);
這個方法最終會調用 Handler 的handleMessage(msg)
方法。同時這里又產生個問題:msg.target
是何時被賦值的?,也就是說Message 是如何綁定 Handler 的?先稱之為問題4。那么接著看 Handler 的dispatchMessage
方法:
Handler --> dispatchMessage
public void dispatchMessage(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();
}
public void handleMessage(Message msg) {
}
可以看到該方法最后執(zhí)行了 handleMessage()
方法,這是一個空方法也就是需要我們覆寫并實現(xiàn)的。另外 dispatchMessage()
也體現(xiàn)出一個問題:
消息分發(fā)的優(yōu)先級:
- Message 的回調方法:
message.callback.run();
優(yōu)先級最高; - Handler 的回調方法:
mCallback.handleMessage(msg)
優(yōu)先級次于上方; - Handler 的回調方法:
handleMessage()
優(yōu)先級最低。
到這里 Looper 循環(huán)并通過 Handler 發(fā)送消息有一個整體的流程了,接下來分析 Handler 在消息機制中的主要作用以及和 Looper、Message 的關系。
2.2 Handler 的創(chuàng)建和作用
上面說到 loop()
方法在不斷從消息隊列 MessageQueue 中取出消息(queue.next()
方法),如果沒有消息則阻塞,反之交給 Message 綁定的 Handler 處理。回顧一下沒解決的兩個問題:
- 問題3:MessageQueue 里的消息從哪里來?Handler 是如何往 MessageQueue 中插入消息的?
- 問題4:
msg.target
是何時被賦值的?,也就是說Message 是如何綁定 Handler 的?
既然要解決 Handler 插入消息的問題,就要看 Handler 發(fā)送消息的過程。
2.2.1 Handler 發(fā)送消息
Handler --> sendMessage(Message msg);
final MessageQueue mQueue;
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
// 發(fā)送延時消息
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 指定時間發(fā)送消息
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);
}
// 處理消息,賦值 Message 對象的 target,消息隊列插入消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到調用 sendMessage(Message msg)
方法最終會調用到 enqueueMessage()
方法,這個方法主要有兩個作用:賦值 Message 對象的 target、消息隊列插入消息。
- 賦值 msg 的 target:
msg.target = this
把發(fā)送消息的 Handler 賦值給 msg 對象的 target。那么問題 4 就解決了:Handler 執(zhí)行發(fā)送消息的過程中將自己綁定給了 Message 的 target,這樣兩者之間就產生了聯(lián)系; - 消息隊列插入消息:
queue.enqueueMessage(msg, uptimeMillis)
queue 是 MessageQueue 的一個實例,queue.enqueueMessage(msg, uptimeMillis)
是執(zhí)行 MessageQueue 的enqueueMessage
方法來插入消息。這樣問題 3 就找到答案:Handler 在發(fā)送消息的時候執(zhí)行 MessageQueue 的enqueueMessage
方法來插入消息;關于 MessageQueue 是怎么執(zhí)行插入消息的過程,參考下方文章 4.3 節(jié)
- 上面 Handler 發(fā)送消息使用了 MessageQueue 的實例 queue,可以看到這個 queue 是上一個方法
sendMessageAtTime
中由 Handler 的成員變量 mQueue 賦值的,那么 mQueue 是哪來的?問題 5:Handler 如何綁定 MessageQueue?先劇透一下 Handler 綁定的是 Looper 的 MessageQueue 對象,Looper 的 MessageQueue 對象是在 Looper 創(chuàng)建時就 new 的。
要了解 Handler 的 MessageQueue 對象是怎么賦值的就要看 Handler 的構造函數(shù)了,Handler 創(chuàng)建的時候作了一些列操作比如獲取當前線程的 Looper,綁定 MessageQueue 對象等。
2.2.2 Handler 的創(chuàng)建
下面是 Handler 無參構造器和主要的構造器,另外幾個重載的構造器有些是通過傳遞不同參數(shù)調用包含兩個參數(shù)的構造器。兩個參數(shù)構造函數(shù)第一個參數(shù)為 callback 回調,第二個函數(shù)用來標記消息是否異步。
// 無參構造器
public Handler() {
this(null, false);
}
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());
}
}
// step1:獲取當前線程 Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// step2:獲取 Looper 對象綁定的 MessageQueue 對象并賦值給 Handler 的 mQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- step1:調用
myLooper()
方法,該方法是使用 sThreadLocal 對象獲取當前線程的 Looper 對象,回顧一下:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
如果獲取的 Looper 對象為 null,說明沒有執(zhí)行 Looper.prepare()
為當前線程保存 Looper 變量,就會拋出 RuntimeException。這里又說明了Handler 必須在有 Looper 的線程中使用,報錯不說,沒有 Looper 就無法綁定 MessageQueue 對象也就無法進行更多有關消息的操作。
- step2:
mQueue = mLooper.mQueue
說明了 Handler 的 MessageQueue 對象是由當前線程 Looper 的 MessageQueue 對象賦值的。這里問題 5 解決:Handler 在創(chuàng)建時綁定了當前線程 Looper 的 MessageQueue 對象。 - 由于 Handler 和 Looper 可以看作使用的是同一個 MessageQueue 對象,所以 Handler 和 Looper 可以共享消息隊列 MessageQueue。Handler 發(fā)送消息(用 mQueue 往消息對列插入消息),Looper 可以方便的循環(huán)使用 mQueue 查詢消息,如果查詢到消息,就可以用 Message 對象綁定的 Handler 對象 target 去處理消息,反之則阻塞。
既然說到了 Handler 的構造器,就想到一個問題:問題 6:關于 handler,在任何地方 new handler 都是什么線程下?這個問題要分是否傳遞 Looper 對象來看。
- 不傳遞 Looper 創(chuàng)建 Handler:
Handler handler = new Handler();
上文就是 Handler 無參創(chuàng)建的源碼,可以看到是通過Looper.myLooper()
來獲取 Looper 對象,也就是說對于不傳遞 Looper 對象的情況下,在哪個線程創(chuàng)建 Handler 默認獲取的就是該線程的 Looper 對象,那么 Handler 的一系列操作都是在該線程進行的。 - 傳遞 Looper 對象創(chuàng)建 Handler:
Handler handler = new Handler(looper);
那么看看傳入 Looper 的構造函數(shù):
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
// 第一個參數(shù)是 looper 對象,第二個 callback 對象,第三個消息處理方式(是否異步)
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看出來傳遞 Looper 對象 Handler 就直接使用了。所以對于傳遞 Looper 對象創(chuàng)建 Handler 的情況下,傳遞的 Looper 是哪個線程的,Handler 綁定的就是該線程。
到這里 Looper 和 Handler 就有一個大概的流程了,接下來看一個簡單的子線程 Handler 使用例子:
new Thread() {
@Override
public void run() {
// step1
Looper.prepare();
// step2
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 1){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"HandlerTest",Toast.LENGTH_SHORT).show();
}
});
// step5
Looper.myLooper().quit();
}
}
};
// step3
handler.sendEmptyMessage(1);
// step4
Looper.loop();
}
}.start();
- step1: 調用
Looper.prepare();
為當前線程創(chuàng)建 Looper 對象,同時也就創(chuàng)建了 MessageQueue,之后將該線程的 Looper 對象保存在 ThreadLocal 中。注意這里的一切操作都在子線程中,如果不調用Looper.prepare()
就使用 Handler 會報錯。 - step2: 創(chuàng)建 Handler 對象,覆寫 handleMessage 處理消息,等待該 Handler 發(fā)送的消息處理時會調用該方法。
- step3: 使用 handler 發(fā)送消息,這里只是示例,畢竟自己給自己發(fā)送消息沒啥必要。發(fā)送的過程中會將自己賦值給
msg.target
,然后再將消息插入到 Looper 綁定的 MessageQueue 對象中。 - step4: 調用
Looper.loop();
首先獲取當前線程的 Looper 對象,根據(jù) Looper 對象就可以拿到 Looper 保存的 MessageQueue 對象 mQueue。有了 MessageQueue 對象就可以 for 循環(huán)獲取它保存的消息 Message 對象,如果消息不存在就返回 null 阻塞,反之則使用 Message 中保存的 Handler:msg.target
來處理消息,最終調用 handleMessage 也就是之前覆寫的方法來處理消息。 - step5: 邏輯處理完畢以后,應在最后使用 quit 方法來終止消息循環(huán),否則這個子線程就會一直處于等待的狀態(tài),而如果退出Looper以后,這個線程就會立刻終止,因此建議不需要的時候終止Looper。
三、總結和其它
3.1 Handler、Looper、MessageQueue、Message
- Handler 用來發(fā)送消息,創(chuàng)建時先獲取默認或傳遞來的 Looper 對象,并持有 Looper 對象包含的 MessageQueue,發(fā)送消息時使用該 MessageQueue 對象來插入消息并把自己封裝到具體的 Message 中;
- Looper 用來為某個線程作消息循環(huán)。Looper 持有一個 MessageQueue 對象 mQueue,這樣就可以通過循環(huán)來獲取 MessageQueue 所維護的 Message。如果獲取的 MessageQueue 沒有消息時,便阻塞在 loop 的
queue.next()
中的nativePollOnce()
方法里,反之則喚醒主線程繼續(xù)工作,之后便使用 Message 封裝的 handler 對象進行處理。 - MessageQueue 是一個消息隊列,它不直接添加消息,而是通過與 Looper 關聯(lián)的 Handler 對象來添加消息。
- Message 包含了要傳遞的數(shù)據(jù)和信息。
3.2 Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死?
這是知乎上的問題,感覺問的挺有意思。平時可能不太會太深究這些問題,正好有大神回答那就記錄一下吧。
- 為什么不會因為死循環(huán)卡死?
線程可以看作是一段可執(zhí)行代碼,當代碼執(zhí)行完畢線程的生命周期就該終止了。對于主線程來說我們不希望它執(zhí)行一段時間后退出,所以簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會被退出。既然是死循環(huán)那么怎么去處理消息呢,通過創(chuàng)建新線程的方式。 - 為這個死循環(huán)準備了一個新線程
在進入死循環(huán)之前便創(chuàng)建了新binder線程,在代碼ActivityThread.main()中:
public static void main(){
...
Looper.prepareMainLooper();
//創(chuàng)建ActivityThread對象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (創(chuàng)建新線程)
thread.attach(false);
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
// step2: 開始循環(huán)
Loop.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.attach(false);便會創(chuàng)建一個Binder線程(具體是指ApplicationThread,Binder的服務端,用于接收系統(tǒng)服務AMS發(fā)送來的事件),該Binder線程通過Handler將Message發(fā)送給主線程。
- 主線程的死循環(huán)一直運行是不是特別消耗CPU資源呢?
其實不然,這里就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法里,詳情見Android消息機制1-Handler(Java層),此時主線程會釋放CPU資源進入休眠狀態(tài),直到下個消息到達或者有事務發(fā)生,通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作。這里采用的epoll機制,是一種IO多路復用機制,可以同時監(jiān)控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。所以說,主線程大多數(shù)時候都是處于休眠狀態(tài),并不會消耗大量CPU資。
- Activity的生命周期是怎么實現(xiàn)在死循環(huán)體外能夠執(zhí)行起來的?
上文 main 函數(shù)有一部分獲取 sMainThreadHandler 的代碼:
final H mH = new H();
public static void main(){
...
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
...
}
final Handler getHandler() {
return mH;
}
類 H 繼承了 Handler,在主線程創(chuàng)建時就創(chuàng)建了這個 Handler 用于處理 Binder 線程發(fā)送來的消息。
Activity的生命周期都是依靠主線程的Looper.loop,當收到不同Message時則采用相應措施:
在H.handleMessage(msg)方法中,根據(jù)接收到不同的msg,執(zhí)行相應的生命周期。
比如收到msg=H.LAUNCH_ACTIVITY,則調用ActivityThread.handleLaunchActivity()方法,最終會通過反射機制,創(chuàng)建Activity實例,然后再執(zhí)行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,則調用ActivityThread.handlePauseActivity()方法,最終會執(zhí)行Activity.onPause()等方法。 上述過程,我只挑核心邏輯講,真正該過程遠比這復雜。
3.3 Handler 使用造成內存泄露
- 有延時消息,要在Activity銷毀的時候移除Messages
- 匿名內部類導致的泄露改為匿名靜態(tài)內部類,并且對上下文或者Activity使用弱引用。
具體操作可以參考文章:
參考資料:
《Android 開發(fā)藝術探索》
Android消息機制1-Handler(Java層)
Android Handler消息機制實現(xiàn)原理