Android中為什麼主線程不會因為Looper.loop()里的死循環卡死?

app程序入口中為主線程準備好了消息隊列

而根據Looper.loop()源碼可知裡面是一個死循環在遍歷消息隊列取消息

而且並也沒看見哪裡有相關代碼為這個死循環準備了一個新線程去運轉,但是主線程卻並不會因為Looper.loop()中的這個死循環卡死,為什麼呢?

————————————補充————————————————

舉個例子,像Activity的生命周期這些方法這些都是在主線程里執行的吧,那這些生命周期方法是怎麼實現在死循環體外能夠執行起來的?


要完全徹底理解這個問題,需要準備以下4方面的知識:Process/Thread,Android Binder IPC,Handler/Looper/MessageQueue消息機制,Linux pipe/epoll機制。

總結一下樓主主要有3個疑惑:

1.Android中為什麼主線程不會因為Looper.loop()里的死循環卡死?

2.沒看見哪裡有相關代碼為這個死循環準備了一個新線程去運轉?

3.Activity的生命周期這些方法這些都是在主線程里執行的吧,那這些生命周期方法是怎麼實現在死循環體外能夠執行起來的?

--------------------------------------------------------------------------------------------------------------------------------------

針對這些疑惑, @hi大頭鬼hi@Rocko@陳昱全 大家回答都比較精鍊,接下來我再更進一步詳細地一一解答樓主的疑惑:

(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下去。

--------------------------------- 歡迎關注我的微博:Gityuan-----------------------------------------

如果大家覺得我回答得還行,還請大家隨手 點贊、關注、收藏,如果覺得說得不好的,還往評論指正。


簡單一句話是:Android應用程序的主線程在進入消息循環過程前,會在內部創建一個Linux管道(Pipe),這個管道的作用是使得Android應用程序主線程在消息隊列為空時可以進入空閑等待狀態,並且使得當應用程序的消息隊列有消息需要處理時喚醒應用程序的主線程。

---

這一題是需要從消息循環、消息發送和消息處理三個部分理解Android應用程序的消息處理機制了,這裡我對一些要點作一個總結:

A. Android應用程序的消息處理機制由消息循環、消息發送和消息處理三個部分組成的。

B. Android應用程序的主線程在進入消息循環過程前,會在內部創建一個Linux管道(Pipe),這個管道的作用是使得Android應用程序主線程在消息隊列為空時可以進入空閑等待狀態,並且使得當應用程序的消息隊列有消息需要處理時喚醒應用程序的主線程。

C. Android應用程序的主線程進入空閑等待狀態的方式實際上就是在管道的讀端等待管道中有新的內容可讀,具體來說就是是通過Linux系統的Epoll機制中的epoll_wait函數進行的。

D. 當往Android應用程序的消息隊列中加入新的消息時,會同時往管道中的寫端寫入內容,通過這種方式就可以喚醒正在等待消息到來的應用程序主線程。

E. 當應用程序主線程在進入空閑等待前,會認為當前線程處理空閑狀態,於是就會調用那些已經註冊了的IdleHandler介面,使得應用程序有機會在空閑的時候處理一些事情。


了解下linux的epoll你就知道為什麼不會被卡住了,先說結論:阻塞是有的,但是不會卡住

主要原因有2個

1,epoll模型

當沒有消息的時候會epoll.wait,等待句柄寫的時候再喚醒,這個時候其實是阻塞的。

2,所有的ui操作都通過handler來發消息操作。

比如屏幕刷新16ms一個消息,你的各種點擊事件,所以就會有句柄寫操作,喚醒上文的wait操作,所以不會被卡死了。


Handler 機制應該基本都知道了,題主點進去源碼看下 ActivityThread 就能基本搞明白了,為什麼主線程不會因為 Looper.loop() 里的死循環卡死?

首先 ActivityThread 並不是一個 Thread,就只是一個 final 類而已。我們常說的主線程就是從這個類的 main 方法開始,main 方法很簡短,一眼就能看全,我們看到裡面有 Looper 了,那麼接下來就找找 ActivityThread 對應的 Handler 啊,就是內部類 H,其繼承 Handler,貼出 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.arg11) != 0, msg.arg2,
(msg.arg12) != 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.arg11) != 0, msg.arg2,
(msg.arg11) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_SHOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, true, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_HIDE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, false, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SHOW_WINDOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
handleWindowVisibility((IBinder)msg.obj, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case HIDE_WINDOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow");
handleWindowVisibility((IBinder)msg.obj, false);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SEND_RESULT:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
handleSendResult((ResultData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;

...........
}

看完這 Handler 里處理消息的內容應該明白了吧, Activity 的生命周期都有對應的 case 條件了,ActivityThread 有個 getHandler 方法,得到這個 handler 就可以發送消息,然後 loop 里就分發消息,然後就發給 handler, 然後就執行到 H(Handler )里的對應代碼。所以這些代碼就不會卡死~,有消息過來就能執行。舉個例子,在 ActivityThread 里的內部類 ApplicationThread 中就有很多 sendMessage 的方法:

......
public final void schedulePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport) {
sendMessage(
finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
token,
(userLeaving ? 1 : 0) | (dontReport ? 2 : 0),
configChanges);
}

public final void scheduleStopActivity(IBinder token, boolean showWindow,
int configChanges) {
sendMessage(
showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
token, 0, configChanges);
}

public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
sendMessage(
showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
token);
}

public final void scheduleSleeping(IBinder token, boolean sleeping) {
sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
}

public final void scheduleResumeActivity(IBinder token, int processState,
boolean isForward, Bundle resumeArgs) {
updateProcessState(processState, false);
sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
}

......


去看下handler機制就明白了,網上一大把。

1.handler機制是使用pipe來實現的

2.主線程沒有消息處理時阻塞在管道的讀端

3.binder線程會往主線程消息隊列里添加消息,然後往管道寫端寫一個位元組,這樣就能喚醒主線程從管道讀端返回,也就是說queue.next()會調用返回

4.dispatchMessage()中調用onCreate, onResume


ActivityThread是應用程序的入口,這裡你可以看到寫Java程序時司空見慣的main方法,而main方法正是整個Java程序的入口。

ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那麼你的程序也就可以退出了。

從消息隊列中取消息可能會阻塞,取到消息會做出相應的處理。如果某個消息處理時間過長,就可能會影響UI線程的刷新速率,造成卡頓的現象。


誰和你說沒卡死,實際上就是卡死了。不信你new一個thread,在裡面建立一個looper,然後looper.prepare,隨後再打個log,你就會發現log死活不會打。

一個looper維護一個消息隊列,隊列里有消息就取出來執行,沒消息就休眠直到有消息進入,就是個典型的生產者消費者模型。實際的消息模型會更複雜一些,定時執行和延遲執行關係到系統中斷。

所以在looper啟動後,主線程上執行的任何代碼都是在一個被looper從消息隊列里取出來執行的runnable內被執行而已,主線程此時就是被阻塞在一個無限循環里了。

activity life cycle callback也是如此,生命周期的回調首先是ams通過binder發送ipc調用給app進程,app進程里的binder stub接收到調用後,給main looper插了條runnable而已(通過和main looper綁定的handler完成)。


大神們寫得都很好,但其實對我們小白來講,還有很多點是需要打通的

APK程序的運行過程

首先,ActivityThread從static main()函數開始,調用prepareMainLooper()為UI線程創建一個消息對列(MessageQueue)

然後,創建一個ActivityThread對象,在其初始化代碼中會創建一個H(Handler)對象和一個AppplicationThread(Binder)對象

Binder負責接遠程AmS的IPC調用,收到消息後,通過Handler將消息發送到消息隊列,UI主線程會非同步的從消息隊列中取出消息並執行操作

接著,UI主線程調用Looper.loop()進入消息循環體。

當ActivityThread接收到AmS發送start某個activit後,就會創建指定的Activity對象。Activity又會創建PhoneWindow類---&>DecroView類---&>相應的View創建完成後,Activity需要把創建好的界面顯示到屏幕上,於是調用WindowManager,他會創建一個ViewRoot對象,創建完ViewRoot後,WM調用WmS提供的遠程介面完成添加一個窗口並顯示到屏幕上。

-------------------------以上摘自柯元旦&

我覺得比較重要的幾個點:

1.基本上一個Binder會對應一個線程,因為他用於IPC通信,肯定是阻塞式的,他又不可能在UI線程中阻塞

2.理解AmS和和客戶端Activity的交互過程,WmS拿到啟動某個Activity的消息後肯定會把這條消息以IPC的方式發送給ActivityThread,負責這一過程的Binder就是ApplicationThread

3.如果不理解Binder請忽略


前幾天剛好在面試的時候被問到了這個問題,我也看過looper源碼,但是確實不清楚,當時沒答出來。後面和同學討論的時候,同學說應該是通過某種方式在消息隊列沒有消息的時候讓它阻塞。


這裡是 6.0 的 ActivityThread main 方法的源代碼:

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

Looper.loop() 確實是一個死循環,但是 Looper.loop() 後面就沒有東西了啊(輪到後面那一句執行就是程序異常退出了)。

Looper.loop() 就這樣一直在主線程守著,我們的 App 才有機會等待用戶來操作,才不會執行完 main() 方法就結束了。

Looper.loop() 這個方法在主線程循環著,當有消息的時候就進行消息處理,可以調用我們在 Activity 里寫的生命周期方法,也就說我們的代碼其實就是在這個循環裡面去執行的,當然不會阻塞了。


卡死是指不響應消息,而那個循環就是一個消息處理循環啊。


對於 epoll 相關的知識題主可以稍後了解,這裡主要說下為什麼不會「卡死」,我覺得題主認為的卡死就是一個方法長時間沒有返回導致 ANR,然後 main 函數中又調用了 loop 方法,這個方法也不會返回,是一個死循環。但是 UI 是事件循環來驅動的,也就是說每一幀的繪製都會通過一次事件循環(Android 後來出現的 Render Thread 或者 macOS、iOS 的 WindowServer 機制不嚴格是這樣),每次觸摸操作也要經過一次事件循環,假如某個方法執行時間過長,那麼新的觸摸或重繪事件就不能得以處理,用戶就會覺得卡或死機了。然而事件循環總得有個驅動器,這就需要用到循環,loop 方法中實際上有一個很重要的調用來獲取下一個可用的事件,假設沒有可用事件就會通過 epoll 來等待,拿到事件後就會通過 Handler 進行派發,派發的最終結果就是你的 Activity 或其他 components 里的一個生命周期方法或回調方法被調用,你就可以在裡面做自己的事情了。


這個東西,如果故意這麼寫的,遊戲界稱為叫永真循環。如果是某程序員亂寫的,叫死循環。。。。


epoll+pipe,有消息就依次執行,沒消息就block住,讓出CPU,等有消息了,epoll會往pipe中寫一個字元,把主線程從block狀態喚起,主線程就繼續依次執行消息,怎麼會死循環呢…


ActivityThread.java 的 main 函數是應用的入口,所以,直接看 ActiivtyThread.java 的 main 方法的最後。looper.loop() 是這樣被調用的:

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

如題主所說 Looper.loop() 的內部有一個無限的循環,只要應用一直在跑,Looper.loop() 的循環就不會跳出來。由上面的代碼可知,如果從 Looper.loop() 的循環跳出來,會拋出「主線程循環異常退出」("Main thread loop unexpectedly exited")的異常。

Android 的應用是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個應用都是運行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。只能是某一個應用阻塞 Looper.loop(),而不是 Looper.loop() 阻塞應用。


大概的意思是,OnCreate等activity的生命周期函數都是由AMS(ActivityManagerService)通過往ApplicationThread這個binder上發消息,然後往MainLooper的messagequeue上發消息,主線程取出消息來執行這些生命周期的。

當MainLooper的messagequeue為空時,也可以註冊idlehandler去執行空轉,也就是沒有消息是執行一種空閑時的動作。

如果涉及到UI的更新,會使用非同步消息,平時我們handler發送得消息都是同步消息。非同步消息的大概意思就是messagequeue查找的時候會跳過前面所有的同步消息,直接去取非同步的來執行,這樣就保證了UI更新的即時性,優先被主線程處理,


loop里,主線程會不停的去消息隊列里取消息,如果隊列為空就阻塞線程,thread.sleep,交出cpu佔用,當別的線程添加消息時,會喚醒主線程,這時候,主線程會去消息隊列把新加的消息取出來執行,執行完畢就繼續沉睡


只要處理message的各種操作不是耗時操作就不會卡死。

至於oncreat,onstart主線程運行, 本質也是系統級別service, Ams, 通過binder發消息到activitythread的消息隊列實現的。


就像一輛車在圓形賽車道上跑,一邊跑一邊執行任務,比如開到市中心買瓶水,任務完成又回到剛剛離開的地方,繼續各種執行買水的任務,直到沒有任務了,行了,跑一圈回到起點。睡覺,有任務叫醒你,你又開始跑一圈。邊跑邊接單。


你說的卡死,是OOM,stackoverflow,還是ANR?


推薦閱讀:

拿到 Android 項目源碼後,如何才能以最高效的速度看懂?

TAG:Android開發 | Android開發諮詢 | Android系統源碼 |