1. 程式人生 > >Android為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死

Android為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死

主要有3個疑惑:

1.Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死?

2.沒看見哪裡有相關程式碼為這個死迴圈準備了一個新執行緒去運轉?

3.Activity的生命週期這些方法這些都是在主執行緒裡執行的吧,那這些生命週期方法是怎麼實現在死迴圈體外能夠執行起來的?

(1) Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死?

這裡涉及執行緒,先說說說程序/執行緒,程序:每個app執行時前首先建立一個程序,該程序是由Zygote fork出來的,用於承載App上執行的各種Activity/Service等元件。程序對於上層應用來說是完全透明的,這也是google有意為之,讓App程式都是執行在Android Runtime。大多數情況一個App就執行在一個程序中,除非在AndroidManifest.xml中配置Android:process屬性,或通過native程式碼fork程序。

執行緒:執行緒對應用來說非常常見,比如每次new Thread().start都會建立一個新的執行緒。該執行緒與App所在程序之間資源共享,從Linux角度來說程序與執行緒除了是否共享資源外,並沒有本質的區別,都是一個task_struct結構體,在CPU看來程序或執行緒無非就是一段可執行的程式碼,CPU採用CFS排程演算法,保證每個task都儘可能公平的享有CPU時間片

有了這麼準備,再說說死迴圈問題:

對於執行緒既然是一段可執行的程式碼,當可執行程式碼執行完成後,執行緒生命週期便該終止了,執行緒退出。而對於主執行緒,我們是絕不希望會被執行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行程式碼是能一直執行下去的,死迴圈便能保證不會被退出,

例如,binder執行緒也是採用死迴圈的方法,通過迴圈方式不同與Binder驅動進行讀寫操作,當然並非簡單地死迴圈,無訊息時會休眠。但這裡可能又引發了另一個問題,既然是死迴圈又如何去處理其他事務呢?通過建立新執行緒的方式。

真正會卡死主執行緒的操作是在回撥方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANR,looper.loop本身不會導致應用卡死。


(2) 沒看見哪裡有相關程式碼為這個死迴圈準備了一個新執行緒去運轉?

事實上,會在進入死迴圈之前便建立了新binder執行緒,在程式碼ActivityThread.main()中:
public static void main(String[] args) {
        ....

        //建立Looper和MessageQueue物件,用於處理主執行緒的訊息
        Looper.prepareMainLooper();

        //建立ActivityThread物件
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (建立新執行緒)
        thread.attach(false);

        Looper.loop(); //訊息迴圈執行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

thread.attach(false);便會建立一個Binder執行緒(具體是指ApplicationThread,Binder的服務端,用於接收系統服務AMS傳送來的事件),該Binder執行緒通過Handler將Message傳送給主執行緒,具體過程可檢視 startService流程分析,這裡不展開說,簡單說Binder用於程序間通訊,採用C/S架構。關於binder感興趣的朋友,可檢視我回答的另一個知乎問題:
為什麼Android要採用Binder作為IPC機制? - Gityuan的回答

另外,ActivityThread實際上並非執行緒,不像HandlerThread類,ActivityThread並沒有真正繼承Thread類,只是往往執行在主執行緒,該人以執行緒的感覺,其實承載ActivityThread的主執行緒就是由Zygote fork而建立的程序。

主執行緒的死迴圈一直執行是不是特別消耗CPU資源呢? 其實不然,這裡就涉及到Linux pipe/epoll機制,簡單說就是在主執行緒的MessageQueue沒有訊息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裡,詳情見Android訊息機制1-Handler(Java層),此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生,通過往pipe管道寫端寫入資料來喚醒主執行緒工作。這裡採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主執行緒大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。


(3) Activity的生命週期是怎麼實現在死迴圈體外能夠執行起來的?

ActivityThread的內部類H繼承於Handler,通過handler訊息機制,簡單說Handler機制用於同一個程序的執行緒間通訊。

Activity的生命週期都是依靠主執行緒的Looper.loop,當收到不同Message時則採用相應措施:
在H.handleMessage(msg)方法中,根據接收到不同的msg,執行相應的生命週期。

比如收到msg=H.LAUNCH_ACTIVITY,則呼叫ActivityThread.handleLaunchActivity()方法,最終會通過反射機制,建立Activity例項,然後再執行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,則呼叫ActivityThread.handlePauseActivity()方法,最終會執行Activity.onPause()等方法。 上述過程,我只挑核心邏輯講,真正該過程遠比這複雜。

主執行緒的訊息又是哪來的呢?當然是App程序中的其他執行緒通過Handler傳送給主執行緒,請看接下來的內容:

最後,從程序與執行緒間通訊的角度,通過一張圖加深大家對App執行過程的理解:


system_server程序是系統程序,java framework框架的核心載體,裡面運行了大量的系統服務,比如這裡提供ApplicationThreadProxy(簡稱ATP),ActivityManagerService(簡稱AMS),這個兩個服務都執行在system_server程序的不同執行緒中,由於ATP和AMS都是基於IBinder介面,都是binder執行緒,binder執行緒的建立與銷燬都是由binder驅動來決定的。

App程序則是我們常說的應用程式,主執行緒主要負責Activity/Service等元件的生命週期以及UI相關操作都執行在這個執行緒; 另外,每個App程序中至少會有兩個binder執行緒 ApplicationThread(簡稱AT)和ActivityManagerProxy(簡稱AMP),除了圖中畫的執行緒,其中還有很多執行緒,比如signal catcher執行緒等,這裡就不一一列舉。

Binder用於不同程序之間通訊,由一個程序的Binder客戶端向另一個程序的服務端傳送事務,比如圖中執行緒2向執行緒4傳送事務;而handler用於同一個程序中不同執行緒的通訊,比如圖中執行緒4向主執行緒傳送訊息。

結合圖說說Activity生命週期,比如暫停Activity,流程如下:
  1. 執行緒1的AMS中呼叫執行緒2的ATP;(由於同一個程序的執行緒間資源共享,可以相互直接呼叫,但需要注意多執行緒併發問題)
  2. 執行緒2通過binder傳輸到App程序的執行緒4;
  3. 執行緒4通過handler訊息機制,將暫停Activity的訊息傳送給主執行緒;
  4. 主執行緒在looper.loop()中迴圈遍歷訊息,當收到暫停Activity的訊息時,便將訊息分發給ActivityThread.H.handleMessage()方法,再經過方法的呼叫,最後便會呼叫到Activity.onPause(),當onPause()處理完後,繼續迴圈loop下去。
簡單一句話是:Android應用程式的主執行緒在進入訊息迴圈過程前,會在內部建立一個Linux管道(Pipe),這個管道的作用是使得Android應用程式主執行緒在訊息佇列為空時可以進入空閒等待狀態,並且使得當應用程式的訊息佇列有訊息需要處理時喚醒應用程式的主執行緒。