1. 程式人生 > >主執行緒中的Looper.loop()一直無限迴圈為什麼不會造成ANR?

主執行緒中的Looper.loop()一直無限迴圈為什麼不會造成ANR?

引子:

正如我們所知,在android中如果主執行緒中進行耗時操作會引發ANR(Application Not Responding)異常。

造成ANR的原因一般有兩種:

  1. 當前的事件沒有機會得到處理(即主執行緒正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了)
  2. 當前的事件正在處理,但沒有及時完成

為了避免ANR異常,android使用了Handler訊息處理機制。讓耗時操作在子執行緒執行。

因此產生了一個問題,主執行緒中的Looper.loop()一直無限迴圈檢測訊息佇列中是否有新訊息為什麼不會造成ANR?

本人面試網易的時候就被問到了T_T

原始碼分析

ActivityThread.java 是主執行緒入口的類,這裡你可以看到寫Java程式中司空見慣的main方法,而main方法正是整個Java程式的入口。

ActivityThread原始碼

    public static final void main(String[] args) {
        ...
        //建立Looper和MessageQueue
        Looper.prepareMainLooper();
        ...
        //輪詢器開始輪詢
        Looper.loop();
        ...
    }

Looper.loop()方法

   while (true) {
       //取出訊息佇列的訊息,可能會阻塞
       Message msg = queue
.next(); // might block ... //解析訊息,分發訊息 msg.target.dispatchMessage(msg); ... }

顯而易見的,如果main方法中沒有looper進行迴圈,那麼主執行緒一執行完畢就會退出。這還玩個蛋啊!

總結:ActivityThread的main方法主要就是做訊息迴圈,一旦退出訊息迴圈,那麼你的應用也就退出了。

我們知道了訊息迴圈的必要性,那為什麼這個死迴圈不會造成ANR異常呢?

因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點選觸控或者說Activity的生命週期都是執行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。只能是某一個訊息或者說對訊息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。

也就說我們的程式碼其實就是在這個迴圈裡面去執行的,當然不會阻塞了。

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。

讓我們再看一遍造成ANR的原因,你可能就懂了。

造成ANR的原因一般有兩種:

  1. 當前的事件沒有機會得到處理(即主執行緒正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了)
  2. 當前的事件正在處理,但沒有及時完成

而且主執行緒Looper從訊息佇列讀取訊息,當讀完所有訊息時,主執行緒阻塞。子執行緒往訊息佇列傳送訊息,並且往管道檔案寫資料,主執行緒即被喚醒,從管道檔案讀取資料,主執行緒被喚醒只是為了讀取訊息,當訊息讀取完畢,再次睡眠。因此loop的迴圈並不會對CPU效能有過多的消耗。

總結:Looer.loop()方法可能會引起主執行緒的阻塞,但只要它的訊息迴圈沒有被阻塞,能一直處理事件就不會產生ANR異常。