Android中為什么主線程不會因為Looper.loop()方法造成阻塞


很多人都對Handler的機制有所了解,如果不是很熟悉的可以看看我 
如果看過源碼的人都知道,在處理消息的時候使用了Looper.loop()方法,並且在該方法中進入了一個死循環,同時Looper.loop()方法是在主線程中調用的,那么為什么沒有造成阻塞呢? 
首先我們需要從Android程序啟動的入口開始來看: 
Android程序的運行入口 
如果不清楚Android的應用啟動詳細流程的可以看看這個 
然后再看看Looper.loop()方法:

    public static final void loop() {
        Looper me = myLooper();  
        MessageQueue queue = me.mQueue;  
        // 開始了死循環
        while (true) {  
            Message msg = queue.next(); // might block  
            if (msg != null) {
                if (msg.target == null) {  
                    return;  
                }  
                if (me.mLogging!= null){
               me.mLogging.println(   ">>>>> Dispatching to " + msg.target + " "    + msg.callback + ": " + msg.what   );  
            }
                msg.target.dispatchMessage(msg);  
                if (me.mLogging!= null) {
               me.mLogging.println(    "<<<<< Finished to    " + msg.target + " "   + msg.callback);  
            }
                msg.recycle();  
            }
        }
    }

 

這么一看,似乎真的是在主線程中有一個死循環,而且沒有造成阻塞?

那么我們先從入口ActivityThread 類開始看:首先 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.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; 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 的方法。 
簡單的來說:ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那么你的程序也就可以退出了。 
從消息隊列中取消息可能會阻塞,取到消息會做出相應的處理。如果某個消息處理時間過長,就可能會影響UI線程的刷新速率,造成卡頓的現象。

如果你了解下linux的epoll你就知道為什么不會被卡住了,先說結論:阻塞是有的,但是不會卡住 
主要原因有2個

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

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

這里涉及線程,先說說說進程/線程: 
進程:每個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時間片。

作者:Gityuan
鏈接:https://www.zhihu.com/question/34652589/answer/90344494
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM