導讀
- 移動開發知識體系總章(Java基礎、Android、Flutter)
- Android Handler消息機制
- Android中為什么主線程不會因為Looper.loop里的無限循環ANR?
- ActivityThread工作原理
由于我更新優化本篇文章中的筆誤之處,導致文章莫名被刪除,故此重新發布!
Android Handler消息機制
為了避免ANR,我們通常會把耗時操作放在子線程里面去執行,因為子線程不能更新UI,所以當子線程需要更新UI的時候就需要借助到Andriod的消息機制,也就是Handler機制了。那么其原理是什么呢?下面我們根據平時的使用一步一步來解讀源碼。
以上內容可能會產生的問題:
- 什么是ANR?
在Android上,如果你的應用程序有一段時間無法響應,系統會向用戶顯示一個對話框,這個對話框稱作應用程序無響應(ANR:Application Not Responding)對話框。用戶可以選擇讓程序繼續運行,但是,他們在使用你的應用程序時,并不希望每次都要處理這個對話框。因此,在程序里對響應性能的設計很重要,這樣,系統不會顯示ANR給用戶。
- 耗時操作為什么不能再主線程?
- 為什么子線程不能更新UI?
- Handler機制是什么?
一. Handler的常見用法。
- 創建handler對象并重寫handleMessage方法,以便于處理自己需要的邏輯。
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
...
}
}
- 使用handler 對象發送消息(對象、載體)。
private void sendMsg(){
Mssages msg = handler.obtainMessage();
msg.what=what;
msg.age1=age1;
handler.sendMessageDelayed(msg,1000);
}
...
二. 代碼分析
- 以上代碼可以看出,在主線程創建了一個handler對象,并重構了handleMessage方法,然后通過handler發送消息對象,中間經過一系列處理后,handleMessage方法接收到傳遞的數據,就可以處理具體的邏輯了(如更新UI)。
- 以上代碼可以看出,發送消息、最后處理數據時都使用的是Message對象,這是在源碼設計層面時約定的媒介(載體、中間件、快遞),這里先不進行展開,只做一個簡單的介紹:
what字段作為標識,
arg1/arg2字段作為簡單類型的參數,
obj字段作為復雜對象,以及Bundle常見參數
target字段作為handler對象標識
- 再來看發送消息 sendMessageDelayed,通過源碼可以發現所有的sendMessage方法執行后,最終都會走sendMessageAtTime()方法(這里就不貼上具體源碼了)。
public boolean sendMessageAtTime(@NonNull 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);
}
在這個sendMessageAtTime方法里會拿到一個MessageQueue的實例對象,并觸發enqueueMessage(queue, msg, uptimeMillis)方法。那我們就先看看enqueueMessage方法,然后在去看mQueue怎么來的。
boolean enqueueMessage(Message msg, long when) {
...
Message p = mMessages;
synchronized (this) {
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
...
Message p = mMessages;
...
msg.next = p; // invariant: p == prev.next
...
}
}
return true;
}
首先這是一個被synchronized修飾的同步代碼塊,確保了并發問題;核心邏輯是對Message的實例對象p進行非空判斷,最終兩個兩個邏輯里都會把msg放到Message實例對象p的next方法中進行排隊。
4.現在回來研究這個mQueue對象是怎么來的呢,通過command+f(ctrl+f)搜索,最終發現是Handler構造方法中獲取的(通過代碼可以看出mQueue的獲取是通過looper.mQueue,而我們前面在創建handler的時候使用的是無參構造的,那么這個Looper對象的實例額mLooper是怎么來的?)
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
4.1 觀察Handler的其他重載的構造方法發現,我們的無參構造最后會走如下構造,而這個mLooper實例對象是通過Looper.myLooper()方法獲取的而且還不能為空!由此可以看出Looper、MessageQueue都是非常重要的對象。
public Handler(@Nullable 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;
}
4.2 繼續跟蹤源碼,進入Looper類發現myLooper()的內部是通過sThreadLocal.get()獲取的,那么有get()自然是有set()的,所以我們command+f(ctrl+f)搜索找到了prepare()方法,而這個方法會被prepareMainLooper的方法執行,prepare翻譯過來是準備的意思,prepareMainLooper就是準備主Looper的意思了,這里要注意有synchronized修飾噢。
...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
...
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));
}
...
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
4.3 到這里,我們知道了Handler無參構造的時候其實是使用了一個叫MainLooper及MessageQueue的對象,那這個MainLooper具體又是在哪里創建的呢,那就查查這個prepareMainLooper()方法在哪里被調用的使用command+shift+f進行全局搜索:
搜索出來4個結果,除了Looper.java外,SystemServer.java是系統級別的這里不做深入,后續新增該類的解讀; Bridge.java是橋梁類,注釋中寫著“Main entry point of the LayoutLib Bridge.”,這里也不做深入;最后是ActivityThread.java類,也是這里需要重點關注的類:
public final class ActivityThread extends ClientTransactionHandler {
...
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
...
}
...
}
有兩個重大發現:
1、Looper.prepareMainLooper()是在一個叫ActivityThread 的 final 類型的Java類,的main(String[] args) 方法中執行的。
2、還執行了Looper.loop();
main是Java的入口方法,說明什么呢,在整個(應用)代碼里Looper.prepareMainLooper()是最先執行的代碼之一,也就是說Handler所使用的Looper早就初始化好了,而且是同步唯一的(如果忘了請回翻 )。
接下來看看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;
}
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
這里可以看出幾個重要消息
1、Looper、MessageQueue是不可缺少的對象
2、使用到了for (;;) ,就是說無限循環,一直去MessageQueue的next()去查有沒有Message對象,
3、有Message對象時會執行msg.target.dispatchMessage(msg);方法
當然也會有2個困惑的問題
3.1、for (;;) 這個無限循環不會讓APP卡死嗎?
3.2、 有一行代碼if (msg == null) return 其注釋是的意思是是阻塞,return之后不是繼續for循環嗎?
4.4 繼續跟蹤 msg.target.dispatchMessage(msg)方法,前面有提到target其實就是Handler(為什么呢,大家有興趣可以看看源碼)也就是說dispatchMessage方法是Handler類下的
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
發現這里調用了一個很眼熟的方法名handleMessage 沒錯,這就是一開始我們重寫的的handleMessage方法,至此,腦海中是否已經有了一個Handle整體輪廓圖?是否還有一個疑問,Handler啥時候把線程給切換了?
總結
1.APP在啟動的時候,最先啟動ActivityThread類的main方法,其中Looper的初始化工作和準備工作就是這時候執行的(獲得Looper對象、MessageQueue)
2.Looper開啟loop()方法(永動機)去死循環遍歷MessageQueue的消息隊列(至于為什么沒有卡死前面有提到?有提到嗎,為什么我修改的時候發現沒有,捂臉)
3.在通常用法中創建Handler對象時,構造方法會拿到looper、mQueue對象
4.使用Handler對象發送消息(sendXxx())
5.把消息加入隊列(mQueue.enqueueMessage)
6.第2步的永動機讀取MessageQueue的消息
7.執行dispatchMessage方法
8.執行代碼中創建Handler方法時重寫的handleMessage方法完成Handler機制的整個過程。
最后,讓我們帶著文章中的問題,進行繼續深入:Android中為什么主線程不會因為Looper.loop里的無限循環ANR?