39.ANR專題

ANR

Application Not Responding

ANR類型

Service Timeout:比如前臺服務在20s內未執行完成,後臺服務Timeout時間是前臺服務的10倍,200s;
BroadcastQueue Timeout:比如前臺廣播在10s內未執行完成,後臺60s
ContentProvider Timeout:內容提供者,在publish過超時10s;
InputDispatching Timeout: 輸入事件分發超時5s,包括按鍵和觸摸事件。
對於input來說即便某次事件執行時間超過Timeout時長,只要用戶後續沒有再生成輸入事件,則不會觸發ANR

ANR文件獲取方式

1.低於Android10系統的手機

adb pull data/anr

2.大於等於Android10系統的手機
如果在Android10以上系統,使用adb pull的方式拉取trace文件,會得到permission denied。需要使用新的命令完成

adb bugreport

導致ANR的原因

應用層導致ANR:

1.函數阻塞:如死循環、主線程IO、處理大數據。
2.鎖出錯:主線程等待子線程的鎖。
3.內存緊張:系統分配給一個應用的內存是有上限的,長期處於內存緊張,會導致頻繁內存交換,進而導致應用的一些操作超時。

系統導致ANR:

1.CPU被搶佔:一般來說,前臺在玩遊戲,可能會導致你的後臺廣播被搶佔。
2.系統服務無法及時響應:系統的服務都是Binder機制,服務能力也是有限的,有可能系統服務長時間不響應導致ANR。
3.其他應用佔用大量內存。

系統ANR檢測機制

ANR由消息處理機制保證,核心原理是消息調度和超時處理。
雖然系統觸發anr的地方有好幾種,但是檢測機制和處理機制其實是差不多的,都是在操作前記錄時間,然後向消息隊列中丟向一個觸發Anr的消息(基於消息機制),再執行完響應的操作的時候將消息移除,如果超過指定時間沒有移除,那麼則會觸發anr操作。

ps發生anr的時候是主線程已經被阻塞了,anr也是基於消息機制,是一個延時消息,那爲什麼它還能被執行呢?
發生anr時,被阻塞的是app進程的主線程,而anr延時消息並不是在app進程中處理的。它是通過AMS所在進程中的子線程消息隊列中執行的(ams內部除了有主線程的消息隊列外,還持有了一個異步的消息隊列),所以時間到了之後還是可以被looper取出; 然後將顯示anr彈窗的消息發送到AMS主線程的消息隊列(mUiHandler),去展示彈窗。

service: 當Service的生命週期開始時,bumpServiceExecutingLocked()會被調用,緊接着會調用scheduleServiceTimeoutLocked(),向消息隊列發送一個延遲消息(20s(後臺服務200s)),當Service的生命週期結束時,在serviceDoneExecutingLocked方法中移除這個消息,如果時間到了還沒有移除,就提示anr。由AMS調度,利用Handler和Looper,設計了一個TIMEOUT消息交由AMS線程來處理,整個超時機制的實現都是在Java層。

broadcast:Broadcast ANR,前臺的“串行廣播消息”必須在10秒內處理完畢,後臺的“串行廣播消息”必須在60秒處理完畢, 每派發串行廣播消息到一個接收器時,都會拋出一個定時消息BROADCAST_TIMEOUT_MSG,如果定時消息響應,則判斷是否廣播消息處理超時,超時就說明發生了ANR。由AMS調度,利用Handler和Looper,設計了一個TIMEOUT消息交由AMS線程來處理,整個超時機制的實現都是在Java層。

input: 在InputDispatcher派發輸入事件時,會尋找接收事件的窗口,如果無法正常派發,則可能會導致當前需要派發的事件超時(默認是5秒)。 Native層發現超時了,會通知Java層,Java層經過一些處理後,會反饋給Native層,是繼續等待還是丟棄當前派發的事件。 InputEvent由InputDispatcher調度,待處理的輸入事件都會進入隊列中等待,設計了一個等待超時的判斷,超時機制的實現在Native層。

service、broadcast、provider 的ANR原理都是埋定時炸彈和拆炸彈原理,

但是input的超時檢測機制稍微有點不同,需要等收到下一次input事件,纔會去檢測上一次input事件是否超時,input事件裏埋的炸彈是普通炸彈,需要通過掃雷來排查。

SNR(System Not Respoding)

SNR由Watchdog機制保證。Watchdog定時(30s)檢查一些重要的系統服務,舉報長時間阻塞的事件,甚至殺掉system_server進程,讓Android系統重啓。

Monitor Checker(addMonitor),用於檢查是Monitor對象可能發生的死鎖, AMS, PKMS, WMS等核心的系統服務都是Monitor對象。

Looper Checker(addThread),用於檢查線程的消息隊列是否長時間處於工作狀態。一些重要的線程的消息隊列,也會加入到Looper Checker中,譬如AMS, PKMS,這些是在對應的對象初始化時加入的。

Monitor Checker預警我們不能長時間持有核心繫統服務的對象鎖,否則會阻塞很多函數的運行; Looper Checker預警我們不能長時間的霸佔消息隊列,否則其他消息將得不到處理。這兩類都會導致系統卡住(System Not Responding)。

ANR主要流程

所有ANR,最終都會調用AppErrors的appNotResponding方法

1.系統監控到app發生ANR後,收集了一些相關進程pid(包括髮生ANR的進程),準備讓這些進程dump堆棧,從而生成ANR Trace文件。
2.系統開始向這些進程發送SIGQUIT信號(通過監聽這個信號可以實現對anr的監控,如matrix的實現),進程收到SIGQUIT信號之後開始dump堆棧。
AMS開始按照firstPids、nativePids、extraPids的順序dump這些進程的堆棧。並且最長dump時間限制爲20秒。

dump的調用過程:

→ dumpStackTraces
→ dumpJavaTracesTombstoned
→ ActivityManagerService#dumpJavaTracesTombstoned()
→ Debug#dumpJavaBacktraceToFileTimeout()
→ android_os_Debug#android_os_Debug_dumpJavaBacktraceToFileTimeout()
→ android_os_Debug#dumpTraces()
→ debuggerd_client#dump_backtrace_to_file_timeout()
→ debuggerd_client#debuggerd_trigger_dump()。

1.AMS進程間接給需要dump堆棧那個進程發送了一個SIGQUIT信號
2.除Zygote進程外,每個進程都會創建一個SignalCatcher守護線程,用於捕獲SIGQUIT,然後開始dump
3.這個過程從收到SIGQUIT信號開始到使用socket寫Trace結束。

ANR監控

一. WatchDog

ANRWatchDog 是一個自動檢測ANR的開源庫,往主線程發送消息,並設置超時檢測,如果超時還沒執行相應消息,則判定爲可能發生ANR。需要進一步從系統服務獲取相關數據(可通過ActivityManagerService.getProcessesInErrorState()方法獲取進程的ANR信息),進一步判定是否真的發生了ANR。

缺點
1.存在5s的誤差,假設當ANRWatchDog post消息的時候,已經卡了2s,然後再過3s卡完了恢復了,那麼ANRWatchDog是監測不到的。
2.和BlockCanary一樣不支持touch事件的監測

優化ANRWatchDog存在5s的誤差的缺點(AnrMonitor)。每隔1s就會檢查一下,並提示可能的風險,累計5s就觸發ANR流程,這是一個實現思路,代碼只提供了一個demo,還需要完善。此方案可以結合ProcessLifecycleOwner,應用在前臺纔開啓檢測,進入後臺則停止檢測。

二. SIGQUIT信號

該方案通過去監聽SIGQUIT信號,從而感知當前進程可能發生了ANR,需配合當前進程是否處於NOT_RESPONDING狀態以及主線程是否卡頓來進行甄別,以免誤判。這種方案纔是真正的監控ANR,matrix、xCrash都在使用這種方案。已經在微信等app上檢驗過,穩定性和可靠性都能得到保證。

1.監聽SIGQUIT信號。

系統監控到app發生ANR後,收集了一些相關進程pid,並向這些進程發送SIGQUIT信號,進程收到SIGQUIT信號之後開始dump堆棧,通過對這個信號的監聽,實現anr的監聽,實現方式見matrix框架(https://github.com/Tencent/matrix/tree/master/matrix/matrix-android/matrix-trace-canary/src/main)。

2.監聽NOT_RESPONDING標記。

發生ANR的進程一定會收到SIGQUIT信號;但是收到SIGQUIT信號的進程並不一定發生了ANR。爲了解決這個問題,回到源碼中,我們會發現執行makeAppNotRespondingLocked方法時,會給發生ANR的進程標記一個NOT_RESPONDING的flag,這個flag可以通過ActivityManager來獲取:

private static boolean checkErrorState() {
    try {
        Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
        ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
        if (procs == null) return false;
        for (ActivityManager.ProcessErrorStateInfo proc : procs) {
            if (proc.pid != android.os.Process.myPid()) continue;
            if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
            return true;
        }
        return false;
    } catch (Throwable t){
        MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
    }
    return false;
}

監控到SIGQUIT後,我們在20秒內(20秒是ANR dump的timeout時間)不斷輪詢自己是否有NOT_RESPONDING的flag,一旦發現有這個flag,那麼馬上就可以認定發生了一次ANR。

3.結合線程是否處於卡頓狀態判斷

發生ANR的進程並不一定會被設置爲NOT_RESPONDING狀態,如後臺ANR會直接被殺死,另外有些機型修改了anr的邏輯,發生anr後直接殺死了進程,所以需要一種機制,在收到SIGQUIT信號後,需要非常快速的偵查出自己是否已經處於ANR的狀態,進行快速的dump和上報,可以通過主線程釋放處於卡頓狀態來判斷,可以通過Looper的mMessage對象,該對象的when變量,表示的是當前正在處理的消息入隊的時間,我們可以通過when變量減去當前時間,得到的就是等待時間,如果等待時間過長,就說明主線程是處於卡住的狀態。這時候收到SIGQUIT信號基本上就可以認爲的確發生了一次ANR。

ANR Trace.txt文件獲取

低版本可以直接拿到,但是高版本無法拿到,怎麼解決這個問題?從上邊的分析我們知道,除了Zygote進程之外,每個進程都有一個SignalCatcher線程來監聽SIGQUIT信號,後邊通過socket將dump的內容寫入(write)文件中,可以直接hook這裏的write,就能直接拿到系統dump的ANR Trace內容。

Trace.txt文件分析

"Signal Catcher" daemon prio=5 tid=4 Runnable
  | group="system" sCount=0 dsCount=0 flags=0 obj=0x18c84570 self=0x7252417800
  | sysTid=7772 nice=0 cgrp=default sched=0/0 handle=0x725354ad50
  | state=R schedstat=( 16273959 1085938 5 ) utm=0 stm=1 core=4 HZ=100
  | stack=0x7253454000-0x7253456000 stackSize=991KB
  | held mutexes= "mutator lock"(shared held)

-"Signal Catcher" daemon :線程名,有daemon表示守護線程
-prio:線程優先級
-tid:線程內部id
-線程狀態:Runnable
-group:線程所屬的線程組
-sCount:線程掛起次數
-dsCount:用於調試的線程掛起次數
-obj:當前線程關聯的Java線程對象
-self:當前線程地址
-sysTid:線程真正意義上的tid
-nice:調度優先級,值越小則優先級越高
-cgrp:進程所屬的進程調度組
-sched:調度策略
-handle:函數處理地址
-state:線程狀態
-schedstat:CPU調度時間統計(schedstat括號中的3個數字依次是Running、Runable、Switch,Running時間:CPU運行的時間,單位ns,Runable時間:RQ隊列的等待時間,單位ns,Switch次數:CPU調度切換次數)
-utm/stm:用戶態/內核態的CPU時間
-core:該線程的最後運行所在覈
-HZ:時鐘頻率
-stack:線程棧的地址區間
-stackSize:棧的大小
-mutex:所持有mutex類型,有獨佔鎖exclusive和共享鎖shared兩類

ANR案例分析

1.主線程無卡頓,處於正常狀態堆棧

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x74b38080 self=0x7ad9014c00
  | sysTid=23081 nice=0 cgrp=default sched=0/0 handle=0x7b5fdc5548
  | state=S schedstat=( 284838633 166738594 505 ) utm=21 stm=7 core=1 HZ=100
  | stack=0x7fc95da000-0x7fc95dc000 stackSize=8MB
  | held mutexes=
  kernel: __switch_to+0xb0/0xbc
  kernel: SyS_epoll_wait+0x288/0x364
  kernel: SyS_epoll_pwait+0xb0/0x124
  kernel: cpu_switch_to+0x38c/0x2258
  native: #00 pc 000000000007cd8c  /system/lib64/libc.so (__epoll_pwait+8)
  native: #01 pc 0000000000014d48  /system/lib64/libutils.so (android::Looper::pollInner(int)+148)
  native: #02 pc 0000000000014c18  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  native: #03 pc 00000000001275f4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:330)
  at android.os.Looper.loop(Looper.java:169)
  at android.app.ActivityThread.main(ActivityThread.java:7073)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:536)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)

看起來很正常,主線程是空閒的,因爲它正處於nativePollOnce,正在等待新消息。處於這個狀態,那還發生了ANR,可能有2個原因:
1.dump堆棧時機太晚了,ANR已經發生過了,纔去dump堆棧,此時主線程已經恢復正常了。
2.CPU搶佔或者內存緊張等其他因素引起。

2.主線程執行耗時操作

//模擬主線程耗時操作,View點擊的時候調用這個函數
fun makeAnr(view: View) {
    var s = 0L
    for (i in 0..99999999999) {
        s += i
    }
    Log.d("xxx", "s=$s")
}

suspend all histogram:    Sum: 206us 99% C.I. 0.098us-46us Avg: 7.629us Max: 46us
DALVIK THREADS (16):
"main" prio=5 tid=1 Runnable
  | group="main" sCount=0 dsCount=0 flags=0 obj=0x73907540 self=0x725f010800
  | sysTid=32298 nice=-10 cgrp=default sched=1073741825/2 handle=0x72e60080d0
  | state=R schedstat=( 6746757297 5887495 256 ) utm=670 stm=4 core=6 HZ=100
  | stack=0x7fca180000-0x7fca182000 stackSize=8192KB
  | held mutexes= "mutator lock"(shared held)
  at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:58)
  at java.lang.reflect.Method.invoke(Native method)
  at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
  at android.view.View.performClick(View.java:7317)
  at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
  at android.view.View.performClickInternal(View.java:7291)
  at android.view.View.access$3600(View.java:838)
  at android.view.View$PerformClick.run(View.java:28247)
  at android.os.Handler.handleCallback(Handler.java:900)
  at android.os.Handler.dispatchMessage(Handler.java:103)
  at android.os.Looper.loop(Looper.java:219)
  at android.app.ActivityThread.main(ActivityThread.java:8668)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)

從日誌上看,主線程處於執行狀態,不是空閒狀態,導致ANR了,說明com.xfhy.watchsignaldemo.MainActivity.makeAnr這裏有耗時操作。

3.主線程被鎖阻塞

fun makeAnr(view: View) {

    val obj1 = Any()
    val obj2 = Any()

    //搞個死鎖,相互等待

    thread(name = "臥槽") {
        synchronized(obj1) {
            SystemClock.sleep(100)
            synchronized(obj2) {
            }
        }
    }

    synchronized(obj2) {
        SystemClock.sleep(100)
        synchronized(obj1) {
        }
    }
}

"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73907540 self=0x725f010800
  | sysTid=19900 nice=-10 cgrp=default sched=0/0 handle=0x72e60080d0
  | state=S schedstat=( 542745832 9516666 182 ) utm=48 stm=5 core=4 HZ=100
  | stack=0x7fca180000-0x7fca182000 stackSize=8192KB
  | held mutexes=
  at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:59)
  - waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22   //註釋1
  - locked <0x01abeb23> (a java.lang.Object)
  at java.lang.reflect.Method.invoke(Native method)
  at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
  at android.view.View.performClick(View.java:7317)
  at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
  at android.view.View.performClickInternal(View.java:7291)
  at android.view.View.access$3600(View.java:838)
  at android.view.View$PerformClick.run(View.java:28247)
  at android.os.Handler.handleCallback(Handler.java:900)
  at android.os.Handler.dispatchMessage(Handler.java:103)
  at android.os.Looper.loop(Looper.java:219)
  at android.app.ActivityThread.main(ActivityThread.java:8668)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)

"臥槽" prio=5 tid=22 Blocked  //註釋2
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x12c8a118 self=0x71d625f800
  | sysTid=20611 nice=0 cgrp=default sched=0/0 handle=0x71d4513d50
  | state=S schedstat=( 486459 0 3 ) utm=0 stm=0 core=4 HZ=100
  | stack=0x71d4411000-0x71d4413000 stackSize=1039KB
  | held mutexes=
  at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:52)
  - waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
  - locked <0x0c6f8c52> (a java.lang.Object)  
  at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:49)
  at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)

......

注意看,下面幾行:

"main" prio=5 tid=1 Blocked
  - waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22
  - locked <0x01abeb23> (a java.lang.Object)

"臥槽" prio=5 tid=22 Blocked
  - waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
  - locked <0x0c6f8c52> (a java.lang.Object)  

主線程的tid是1,線程狀態是Blocked,正在等待0x0c6f8c52這個Object,而這個Object被thread 22這個線程所持有,主線程當前持有的是0x01abeb23的鎖。而臥槽的tid是22,也是Blocked狀態,它想請求的和已有的鎖剛好與主線程相反。這樣的話,ANR原因也就找到了:線程22持有了一把鎖,並且一直不釋放,主線程等待這把鎖發生超時。在線上環境,常見因鎖而ANR的場景是SharePreference寫入。

4. CPU被搶佔

CPU usage from 0ms to 10625ms later (2020-03-09 14:38:31.633 to 2020-03-09 14:38:42.257):
  543% 2045/com.test.demo: 54% user + 89% kernel / faults: 4608 minor 1 major //注意看這裏
  99% 674/[email protected]: 81% user + 18% kernel / faults: 403 minor
  24% 32589/com.wang.test: 22% user + 1.4% kernel / faults: 7432 minor 1 major
  ......

可以看到,該進程佔據CPU高達543%,搶佔了大部分CPU資源,因爲導致發生ANR,這種ANR與我們的app無關。

5.內存緊張導致ANR

如果一份ANR日誌的CPU和堆棧都很正常,可以考慮是內存緊張。看一下ANR日誌裏面的內存相關部分。還可以去日誌裏面搜一下onTrimMemory,如果dump ANR日誌的時間附近有相關日誌,可能是內存比較緊張了。

10-31 22:37:19.749 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:37:33.458 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:38:00.153 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:38:58.731 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
10-31 22:39:02.816 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0

6.系統服務超時導致ANR

系統服務超時一般會包含BinderProxy.transactNative關鍵字,來看一段日誌:

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x727851e8 self=0x78d7060e00
  | sysTid=4894 nice=0 cgrp=default sched=0/0 handle=0x795cc1e9a8
  | state=S schedstat=( 8292806752 1621087524 7167 ) utm=707 stm=122 core=5 HZ=100
  | stack=0x7febb64000-0x7febb66000 stackSize=8MB
  | held mutexes=
  kernel: __switch_to+0x90/0xc4
  kernel: binder_thread_read+0xbd8/0x144c
  kernel: binder_ioctl_write_read.constprop.58+0x20c/0x348
  kernel: binder_ioctl+0x5d4/0x88c
  kernel: do_vfs_ioctl+0xb8/0xb1c
  kernel: SyS_ioctl+0x84/0x98
  kernel: cpu_switch_to+0x34c/0x22c0
  native: #00 pc 000000000007a2ac  /system/lib64/libc.so (__ioctl+4)
  native: #01 pc 00000000000276ec  /system/lib64/libc.so (ioctl+132)
  native: #02 pc 00000000000557d4  /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+252)
  native: #03 pc 0000000000056494  /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+60)
  native: #04 pc 00000000000562d0  /system/lib64/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+216)
  native: #05 pc 000000000004ce1c  /system/lib64/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+72)
  native: #06 pc 00000000001281c8  /system/lib64/libandroid_runtime.so (???)
  native: #07 pc 0000000000947ed4  /system/framework/arm64/boot-framework.oat (Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+196)
  at android.os.BinderProxy.transactNative(Native method) ————————————————關鍵行!!!
  at android.os.BinderProxy.transact(Binder.java:804)
  at android.net.IConnectivityManager$Stub$Proxy.getActiveNetworkInfo(IConnectivityManager.java:1204)—關鍵行!
  at android.net.ConnectivityManager.getActiveNetworkInfo(ConnectivityManager.java:800)
  at com.xiaomi.NetworkUtils.getNetworkInfo(NetworkUtils.java:2)
  at com.xiaomi.frameworkbase.utils.NetworkUtils.getNetWorkType(NetworkUtils.java:1)
  at com.xiaomi.frameworkbase.utils.NetworkUtils.isWifiConnected(NetworkUtils.java:1)

從日誌堆棧中可以看到是獲取網絡信息發生了ANR:getActiveNetworkInfo。系統的服務都是Binder機制(16個線程),服務能力也是有限的,有可能系統服務長時間不響應導致ANR。如果其他應用佔用了所有Binder線程,那麼當前應用只能等待。可進一步搜索:blockUntilThreadAvailable關鍵字:
at android.os.Binder.blockUntilThreadAvailable(Native method)
如果有發現某個線程的堆棧,包含此字樣,可進一步看其堆棧,確定是調用了什麼系統服務。此類ANR也是屬於系統環境的問題,如果某類型手機上頻繁發生此問題,應用層可以考慮規避策略。

還有不足

當前Trace堆棧所在業務耗時嚴重。
當前Trace堆棧所在業務耗時並不嚴重,但歷史調度有一個嚴重耗時。
當前Trace堆棧所在業務耗時並不嚴重,但歷史調度有多個消息耗時。
當前Trace堆棧所在業務耗時並不嚴重,但是歷史調度存在巨量重複消息(業務頻繁發送消息)。
當前Trace堆棧業務邏輯並不耗時,但是其他線程存在嚴重資源搶佔,如IO、Mem、CPU。
當前Trace堆棧業務邏輯並不耗時,但是其他進程存在嚴重資源搶佔,如IO、Mem、CPU。
這裏的6個影響因素中,除了第一個以外,其他的根據ANR Trace有可能無法進行判別。這就會導致很多時候看到的ANR Trace裏面主線程堆棧對應的業務其實並不耗時(因爲可能是前面的消息導致的耗時,但它已經執行完了),如何解決這個問題?

字節跳動內部有一個監控工具:Raster,大致原理:該工具主要是在主線程消息調度過程進行監控。比較耗時的消息會抓取主線從堆棧,這樣可以知道那個耗時的消息具體是在幹什麼,從而針對性優化。同時對應用四大組件消息執行過程進行監控,便於對這類消息的調度及耗時情況進行跟蹤和記錄。另外對當前正在調度的消息及消息隊列中待調度消息進行統計,從而在發生問題時,可以回放主線程的整體調度情況。此外,該庫將系統服務的CheckTime機制遷移到應用側,應用爲線程CheckTime機制,以便於系統信息不足時,從線程調度及時性推測過去一段時間系統負載和調度情況。因此該工具用一句話來概括就是:由點到面,回放過去,現在和將來。

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