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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章