Android面試:主執行緒中的Looper.loop()一直無限迴圈為什麼不會造成ANR?(轉)
引子:
正如我們所知,在android中如果主執行緒中進行耗時操作會引發ANR(Application Not Responding)異常。
造成ANR的原因一般有兩種:
只有當應用程式的UI執行緒響應超時才會引起ANR,超時產生原因一般有兩種
1. 當前的事件沒有機會得到處理,例如UI執行緒正在響應另一個事件,當前事件由於某種原因被阻塞了。
3. 當前的事件正在處理,但是由於耗時太長沒能及時完成。
一般造成ANR的場景:
根據ANR產生的原因不同,從本質上講,產生ANR的原因有三種: 1. KeyDispatchTimeout:原因就是View的點選事件或者觸控事件在5s內無法得到響應。 3. BroadcastTimeout:原因是BroadcastReceiver的onReceive()函式執行在主執行緒中,在10s內無法完成處理。 4. ServiceTimeout:原因是Service的各個生命週期函式在20s內無法完成處理。
為了避免ANR異常,android使用了Handler訊息處理機制。讓耗時操作在子執行緒執行。
因此產生了一個問題,主執行緒中的Looper.loop()一直無限迴圈檢測訊息佇列中是否有新訊息為什麼不會造成ANR?
原始碼分析
ActivityThread.java 是主執行緒入口的類,這裡你可以看到寫Java程式中司空見慣的main方法,而main方法正是整個Java程式的入口。
ActivityThread原始碼
public static final void main(String[] args) { ... //建立Looper和MessageQueue Looper.prepareMainLooper(); ... //輪詢器開始輪詢 Looper.loop(); ... }
顯而易見的,如果main方法中沒有looper進行迴圈,那麼主執行緒一執行完畢就會退出,此時app就退出了,無法繼續與使用者互動。
Looper.loop()方法
public static void loop() { //拿到looper物件 final Looper me = myLooper(); ... //獲取message queue final MessageQueue queue = me.mQueue; ... //迴圈取出message for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ... //獲取到message後dispatch進行處理,接著執行for迴圈處理其他message msg.target.dispatchMessage(msg); ... msg.recycleUnchecked(); } }
總結:因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點選觸控或者說Activity的生命週期都是執行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。只能是某一個訊息或者說對訊息的處理阻塞了 Looper.loop()。
再來看一下ANR產生的本質原因:當前的事件沒有機會得到處理;也就是說只要事件在特定時間內得到執行就不會造成ANR。
handleMessage方法部分原始碼
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord) msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...........
}
}
可以看見Activity的生命週期都是依靠主執行緒的Looper.loop,當收到不同Message時採用相應措施。
如果某個訊息處理時間過長,比如你在onCreate(),onResume()裡面處理耗時操作,那麼下一次的訊息比如使用者的點選事件不能處理了,整個迴圈就會產生卡頓,時間一長就成了ANR。
而且主執行緒Looper從訊息佇列讀取訊息,當讀完所有訊息時,主執行緒阻塞。子執行緒往訊息佇列傳送訊息,並且往管道檔案寫資料,主執行緒即被喚醒,從管道檔案讀取資料,主執行緒被喚醒只是為了讀取訊息,當訊息讀取完畢,再次睡眠。因此loop的迴圈並不會對CPU效能有過多的消耗(參考:https://blog.csdn.net/broadview2006/article/details/8552148)。
總結:Looer.loop()方法可能會引起主執行緒的阻塞,但只要它的訊息迴圈沒有被阻塞,能一直處理事件就不會產生ANR異常。
參考:
https://www.zhihu.com/question/34652589
http://www.jianshu.com/p/cfe50b8b0a41
https://juejin.im/entry/597026806fb9a06bcb7fc660