Android中為什么主線程不會因為Looper.loop里的無限循環ANR?

通過前面的知識Android Handler消息機制我們知道了Handler的工作原理,同時也產生了一些問題,比如Looper.loop()開啟了死循環,不會阻塞線程么?不會被卡死么?

導讀

Android中為什么主線程不會因為Looper.loop里的無限循環ANR?

先看看進程和線程:

進程:每個app運行前會先創建一個進程,該進程是由Zygote fork出來的,用于承載App上運行的各種Activity/Service等組件。進程對于上層應用來說是完全透明的,App程序都是運行在Android Runtime。大多數情況一個App就運行在一個進程中(在AndroidManifest.xml中配置Android:process屬性,或通過native代碼fork進程)。并且至少有一個線程。

線程:依附于進程,是分配和調度CPU的最小單元,由于依附關系,線程之間是可以資源共享的,但進程不行,從Linux角度來說進程與線程除了是否共享資源外,并沒有本質的區別,都是一個task_struct結構體,在CPU看來進程、線程無非就是一段可執行的代碼,CPU采用CFS調度算法,保證每個task都盡可能公平的享有CPU時間片。

對于進程和線程的區別,我看到過這樣一個比喻:把系統比作一個公司,公司會分配一個辦公場所(擁有獨立的用戶空間)給某個部門的部門(部門或部門經理相當于進程了),會有核心項目需要執行(可執行程序代碼),部門至少有一個或多個員工(即線程),而員工歸屬于部門(或部門經理)(線程依附于進程),在這個部門下員工之間非常好溝通(零食啥的隨便分享),但部門與部門間有啥事就得通過溝通了(不能直接共享,需要通信)

對于主線程,若應用運行一下就自己就結束了,肯定不行的,那么如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,無限循環便能保證不會被退出,而ActivityThread的main方法主要就是做消息循環,主角就是Looper。

再看看程序無響應(ANR)和線程阻塞、結束

  • 程序無響應:主線程執行事件的時間過長,導致其他需要立刻在主線程處理的事件無法得到處理。
  • 線程阻塞:線程處于等待狀態。
  • 線程結束:一般線程執行完run方法之后,線程就正常結束了。

1.阻塞和程序無響應沒有必然的關系,雖然主線程在沒有消息可處理的時候是阻塞的,但是只要保證有消息的時候能夠立即處理,程序是不會無響應的。

2.阻塞和線程退出也沒有必然的關系,線程完全可以在不阻塞的情況下死循環,同樣達到不退出的效果。阻塞考慮到節約系統而做的處理,和線程退出沒有關系。

3.ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那么應用就退出了,Looper.loop方法可能會引起主線程阻塞,但只要消息循環沒有被阻塞,就一直能處理事件就不會產生ANR異常。

4.消息隊列是一個無限循環,為什么無限循環不會ANR?因為應用的整個生命周期就是運行在這個消息循環中的,Android是由事件驅動的,Looper.loop方法不斷的接受處理事件,每個點擊觸摸或者Activity每個生命周期都是在Looper.loop方法的控制之下,Looper.loop方法一旦結束,應用程序的生命周期也就結束了。

5.什么情況下會發生ANR?
第一,事件沒有得到處理;
第二,事件正在處理,但是沒有及時完成。

結論:
所以只能說事件的處理如果阻塞導致ANR,而不能說是Looper.loop的無限循環會ANR。

這就是說為什么在Activity生命周期onCreate/onStart/onResume等方法進行耗時的操作會ANR了。
這也是以下情況容易ANR的根本原因
Activity響應時間超過5s無響應
Broadcast在處理時間超過10s無響應
Service處理時間超過20s無響應

筆者新建了一個demo,創建了頁面一(有一個跳轉頁面二的按鈕)、頁面二(在子線程中開啟了一個無限循環),運行后,頁面二果然沒有ANR。(代碼很簡單就不貼了)。

頁面一 頁面二(子線程無限循環的)

頁面一、二CPU\內存對比結果:
1、頁面一無CPU消耗,頁面二CPU平均消耗25%。
2、頁面一內存消耗約10M,頁面二內存消耗約12M。
3、在頁面一直接新增無限循環,直接ANR。
4、在頁面二無限循環新增Thread.sleep(200000);進行休眠,發現程序并沒有ANR,而且CPU沒有消耗,內存多消耗約1M。
(說明:只有主線程中執行任務事件過長,導致其他需要立刻在主線程處理的事件無法得到處理,才有可能會ANR).
5、在頁面二無限循環新增簡單處理邏輯,CPU和內存消耗會小幅波動。
6、在頁面二無限循環新增了讀取drawable為Bitmap并用list一直add,此時的CPU波動非常平穩保持22%,而內存消耗則逐步提升,很快又會被降低,在約23分鐘后后由于OutOfMemory而停止運行。

Looper.loop和MessageQueue.next分析

先查看loop()的源碼:

public static void loop() {
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        }
    }

在for循環里,queue.next(); 寫著注釋might block,說可能會阻塞!查看next關鍵代碼:


    Message next() {
        ...
        for (;;) {
            ...
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                       ...
                       if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                            ...
                            return msg;
                        }
                } 
           }
        }
    }

1、MessageQueue.next內部也是一個for (;;)無限循環。
2、正常情況下,有消息,直接return msg,繼續執行loop()中的方法。
2、msg為空或者msg指向的Handler為空(target)時,,則會執行dowhile,去取隊列中的下一個消息,值得取出來為止,反之會無限循環著!
3、Looper中有無限循環,MessageQueue中也有無限循環,我們都知道新建一個項目跑起來是沒有任何問題的,也就是說,Looper、MessageQueue中的無限循環不會ANR,
(還不知道msg.target為什么是Handler?Hanlder源碼中明確寫著Message.target == this)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375