Android ANR

Android ANR

ANR即Application Not Responding扣孟,給使用使用者帶來的感受是程序長時(shí)間無響應(yīng),最終應(yīng)用閃退。

在 Android 里,應(yīng)用程序的響應(yīng)性是由 Activity Manager 和 WindowManager 系統(tǒng)服務(wù)監(jiān)視的辆床。當(dāng)它監(jiān)測到以下情況中的一個(gè)時(shí),Android 就會針對特定的應(yīng)用程序顯示 ANR:

  • InputDispatching Timeout
  • Service Timeout
  • BroadcastQueue Timeout
  • ContentProvider Timeout

觸發(fā)機(jī)制

ANR是一套監(jiān)控Android應(yīng)用響應(yīng)是否及時(shí)的機(jī)制桅狠,可以把發(fā)生ANR比作是引爆炸彈讼载,那么整個(gè)流程包含三部分組成:

  1. 埋定時(shí)炸彈:中控系統(tǒng)(system_server進(jìn)程)啟動倒計(jì)時(shí),在規(guī)定時(shí)間內(nèi)如果目標(biāo)(應(yīng)用進(jìn)程)沒有干完所有的活垂攘,則中控系統(tǒng)會定向炸毀(殺進(jìn)程)目標(biāo)。

  2. 拆炸彈:在規(guī)定的時(shí)間內(nèi)干完工地的所有活淤刃,并及時(shí)向中控系統(tǒng)報(bào)告完成晒他,請求解除定時(shí)炸彈,則幸免于難逸贾。

  3. 引爆炸彈:中控系統(tǒng)立即封裝現(xiàn)場陨仅,抓取快照,搜集目標(biāo)執(zhí)行慢的罪證(traces)铝侵,便于后續(xù)的案件偵破(調(diào)試分析)灼伤,最后是炸毀目標(biāo)。

  • InputDispatching Timeout 超時(shí)機(jī)制(核心代碼位置 InputDispatcher.cpp咪鲜,ActivityManagerService.java)


    ANR機(jī)制-input.png
  • Service Timeout超時(shí)機(jī)制


    ANR機(jī)制-service.png
  • BroadcastQueue Timeout超時(shí)機(jī)制


    ANR機(jī)制-broadcast.png
  • ContentProvider Timeout超時(shí)機(jī)制


    ANR機(jī)制-provider.png

ANR超時(shí)閾值

不同組件的超時(shí)閾值各有不同狐赡,關(guān)于service、broadcast疟丙、contentprovider以及input的超時(shí)閾值如下表:


ANR超時(shí)閾值.png
  • service前后臺判斷

    ActiveServices在startService過程根據(jù)發(fā)起方進(jìn)程callerApp所屬的進(jìn)程調(diào)度組來決定被啟動的服務(wù)是屬于前臺還是后臺颖侄。當(dāng)發(fā)起方進(jìn)程不等于ProcessList.SCHED_GROUP_BACKGROUND(后臺進(jìn)程組)則認(rèn)為是前臺服務(wù),否則為后臺服務(wù)享郊,并標(biāo)記在ServiceRecord的成員變量createdFromFg览祖。

  • broadcoast前后臺判斷

    根據(jù)發(fā)送廣播sendBroadcast(Intent intent)中的intent的flags是否包含F(xiàn)LAG_RECEIVER_FOREGROUND來決定把該廣播是放入前臺廣播隊(duì)列或者后臺廣播隊(duì)列,炊琉,默認(rèn)情況下廣播是放入后臺廣播隊(duì)列展蒂,除非指明加上FLAG_RECEIVER_FOREGROUND標(biāo)識。

    另外苔咪,只有串行處理的廣播才有超時(shí)機(jī)制锰悼,因?yàn)榻邮照呤谴刑幚淼模耙粋€(gè)receiver處理慢团赏,會影響后一個(gè)receiver松捉;并行廣播通過一個(gè)循環(huán)一次性向所有的receiver分發(fā)廣播事件,所以不存在彼此影響的問題馆里,則沒有廣播超時(shí)隘世。

什么進(jìn)程屬于SCHED_GROUP_BACKGROUND調(diào)度組呢可柿?進(jìn)程調(diào)度組大體可分為TOP、前臺丙者、后臺复斥,進(jìn)程優(yōu)先級(Adj)和進(jìn)程調(diào)度組(SCHED_GROUP)算法較為復(fù)雜,其對應(yīng)關(guān)系可粗略理解為Adj等于0的進(jìn)程屬于Top進(jìn)程組械媒,Adj等于100或者200的進(jìn)程屬于前臺進(jìn)程組目锭,Adj大于200的進(jìn)程屬于后臺進(jìn)程組。關(guān)于Adj的含義見下表纷捞,簡單來說就是Adj>200的進(jìn)程對用戶來說基本是無感知痢虹,主要是做一些后臺工作,故后臺服務(wù)擁有更長的超時(shí)閾值主儡,同時(shí)后臺服務(wù)屬于后臺進(jìn)程調(diào)度組奖唯,相比前臺服務(wù)屬于前臺進(jìn)程調(diào)度組,分配更少的CPU時(shí)間片糜值。


ANR-adj級別.png

ANR問題定位

logcat日志分析

查看mobilelog文件夾下的events_log,從日志中搜索關(guān)鍵字:am_anr丰捷,找到出現(xiàn)ANR的時(shí)間點(diǎn)、進(jìn)程PID寂汇、ANR類型病往。

出現(xiàn)ANR的一般有以下幾種類型:
1:KeyDispatchTimeout(常見)
input事件在5S內(nèi)沒有處理完成發(fā)生了ANR。
logcat日志關(guān)鍵字:ActivityManagerService,inputDispatchingTimedOut,Input event dispatching timed out

2:BroadcastTimeout
前臺Broadcast:onReceiver在10S內(nèi)沒有處理完成發(fā)生ANR骄瓣。
后臺Broadcast:onReceiver在60s內(nèi)沒有處理完成發(fā)生ANR停巷。
logcat日志關(guān)鍵字:Timeout of broadcast BroadcastRecord

3:ServiceTimeout
前臺Service:onCreateonStart榕栏,onBind等生命周期在20s內(nèi)沒有處理完成發(fā)生ANR叠穆。
后臺Service:onCreateonStart臼膏,onBind等生命周期在200s內(nèi)沒有處理完成發(fā)生ANR
logcat日志關(guān)鍵字:Timeout executing service

4:ContentProviderTimeout
ContentProvider 在10S內(nèi)沒有處理完成發(fā)生ANR硼被。 logcat日志關(guān)鍵字:timeout publishing content providers

trace文件分析

  1. 人為的收集trace.txt的命令
    adb shell kill -3 888 //可指定進(jìn)程pid
    執(zhí)行完該命令后traces信息的結(jié)果保存到文件/data/anr/traces.txt
  2. trace文件解讀
----- pid 888 at 2016-11-11 22:22:22 -----
Cmd line: system_server
ABI: arm
Build type: optimized
Zygote loaded classes=4113 post zygote classes=3239
Intern table: 57550 strong; 9315 weak
JNI: CheckJNI is off; globals=2418 (plus 115 weak)
Libraries: /system/lib/libandroid.so /system/lib/libandroid_servers.so /system/lib/libaudioeffect_jni.so /system/lib/libcompiler_rt.so /system/lib/libjavacrypto.so /system/lib/libjnigraphics.so /system/lib/libmedia_jni.so /system/lib/librs_jni.so /system/lib/libsechook.so /system/lib/libshell_jni.so /system/lib/libsoundpool.so /system/lib/libwebviewchromium_loader.so /system/lib/libwifi-service.so /vendor/lib/libalarmservice_jni.so /vendor/lib/liblocationservice.so libjavacore.so (16)
//已分配堆內(nèi)存大小40MB,其中29M已用渗磅,總分配207772個(gè)對象 
Heap: 27% free, 29MB/40MB; 307772 objects
... //省略GC相關(guān)信息

//當(dāng)前進(jìn)程總99個(gè)線程
DALVIK THREADS (99):
//主線程調(diào)用棧
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x75bd9fb0 self=0x5573d4f770
  | sysTid=12078 nice=-2 cgrp=default sched=0/0 handle=0x7fa75fafe8
  | state=S schedstat=( 5907843636 827600677 5112 ) utm=453 stm=137 core=0 HZ=100
  | stack=0x7fd64ef000-0x7fd64f1000 stackSize=8MB
  | held mutexes=
  //內(nèi)核棧
  kernel: __switch_to+0x70/0x7c
  kernel: SyS_epoll_wait+0x2a0/0x324
  kernel: SyS_epoll_pwait+0xa4/0x120
  kernel: cpu_switch_to+0x48/0x4c
  native: #00 pc 0000000000069be4 /system/lib64/libc.so (__epoll_pwait+8)
  native: #01 pc 000000000001cca4 /system/lib64/libc.so (epoll_pwait+32)
  native: #02 pc 000000000001ad74 /system/lib64/libutils.so (_ZN7android6Looper9pollInnerEi+144)
  native: #03 pc 000000000001b154 /system/lib64/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+80)
  native: #04 pc 00000000000d4bc0 /system/lib64/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+48)
  native: #05 pc 000000000000082c /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+144)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:323)
  at android.os.Looper.loop(Looper.java:135)
  at com.android.server.SystemServer.run(SystemServer.java:290)
  at com.android.server.SystemServer.main(SystemServer.java:175)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)
  
"Binder_1" prio=5 tid=8 Native
  | group="main" sCount=1 dsCount=0 obj=0x12c610a0 self=0x5573e5c750
  | sysTid=12092 nice=0 cgrp=default sched=0/0 handle=0x7fa2743450
  | state=S schedstat=( 796240075 863170759 3586 ) utm=50 stm=29 core=1 HZ=100
  | stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB
  | held mutexes=
  kernel: __switch_to+0x70/0x7c
  kernel: binder_thread_read+0xd78/0xeb0
  kernel: binder_ioctl_write_read+0x178/0x24c
  kernel: binder_ioctl+0x2b0/0x5e0
  kernel: do_vfs_ioctl+0x4a4/0x578
  kernel: SyS_ioctl+0x5c/0x88
  kernel: cpu_switch_to+0x48/0x4c
  native: #00 pc 0000000000069cd0 /system/lib64/libc.so (__ioctl+4)
  native: #01 pc 0000000000073cf4 /system/lib64/libc.so (ioctl+100)
  native: #02 pc 000000000002d6e8 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb+164)
  native: #03 pc 000000000002df3c /system/lib64/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+24)
  native: #04 pc 000000000002e114 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+124)
  native: #05 pc 0000000000036c38 /system/lib64/libbinder.so (???)
  native: #06 pc 000000000001579c /system/lib64/libutils.so (_ZN7android6Thread11_threadLoopEPv+208)
  native: #07 pc 0000000000090598 /system/lib64/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+96)
  native: #08 pc 0000000000014fec /system/lib64/libutils.so (???)
  native: #09 pc 0000000000067754 /system/lib64/libc.so (_ZL15__pthread_startPv+52)
  native: #10 pc 000000000001c644 /system/lib64/libc.so (__start_thread+16)
  (no managed stack frames)
... //此處省略剩余的N個(gè)線程.
  1. trace參數(shù)解讀
"Binder_1" prio=5 tid=8 Native
  | group="main" sCount=1 dsCount=0 obj=0x12c610a0 self=0x5573e5c750
  | sysTid=12092 nice=0 cgrp=default sched=0/0 handle=0x7fa2743450
  | state=S schedstat=( 796240075 863170759 3586 ) utm=50 stm=29 core=1 HZ=100
  | stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB
  | held mutexes=
  • 第0行:
    • 線程名: Binder_1(如有daemon則代表守護(hù)線程)

    • prio: 線程優(yōu)先級

    • tid: 線程內(nèi)部id

    • 線程狀態(tài): NATIVE


      ANR線程狀態(tài)對照表.png
  • 第1行:
    • group: 線程所屬的線程組
    • sCount: 線程掛起次數(shù)
    • dsCount: 用于調(diào)試的線程掛起次數(shù)
    • obj: 當(dāng)前線程關(guān)聯(lián)的java線程對象
    • self: 當(dāng)前線程地址
  • 第2行:
    • sysTid:線程真正意義上的tid
    • nice: 調(diào)度有優(yōu)先級
    • cgrp: 進(jìn)程所屬的進(jìn)程調(diào)度組
    • sched: 調(diào)度策略
    • handle: 函數(shù)處理地址
  • 第3行:
    • state: 線程狀態(tài)
    • schedstat: CPU調(diào)度時(shí)間統(tǒng)計(jì)
    • utm/stm: 用戶態(tài)/內(nèi)核態(tài)的CPU時(shí)間(單位是jiffies)
    • core: 該線程的最后運(yùn)行所在核
    • HZ: 時(shí)鐘頻率
  • 第4行:
    • stack:線程棧的地址區(qū)間
    • stackSize:棧的大小
  • 第5行:
    • mutex: 所持有mutex類型嚷硫,有獨(dú)占鎖exclusive和共享鎖shared兩類
  • schedstat含義說明:
    • nice值越小則優(yōu)先級越高。此處nice=-2, 可見優(yōu)先級還是比較高的;

    • schedstat括號中的3個(gè)數(shù)字依次是Running始鱼、Runable仔掸、Switch,緊接著的是utm和stm

      • Running時(shí)間:CPU運(yùn)行的時(shí)間医清,單位ns
      • Runable時(shí)間:RQ隊(duì)列的等待時(shí)間起暮,單位ns
      • Switch次數(shù):CPU調(diào)度切換次數(shù)
      • utm: 該線程在用戶態(tài)所執(zhí)行的時(shí)間,單位是jiffies会烙,jiffies定義為sysconf(_SC_CLK_TCK)负懦,默認(rèn)等于10ms
      • stm: 該線程在內(nèi)核態(tài)所執(zhí)行的時(shí)間筒捺,單位是jiffies,默認(rèn)等于10ms
  • 可見纸厉,該線程Running=186667489018ns,也約等于186667ms系吭。在CPU運(yùn)行時(shí)間包括用戶態(tài)(utm)和內(nèi)核態(tài)(stm)。 utm + stm = (12112 + 6554) ×10 ms = 186666ms颗品。
  • 結(jié)論:utm + stm = schedstat第一個(gè)參數(shù)值肯尺。

ANR 案例整理

一、主線程被其他線程lock躯枢,導(dǎo)致死鎖

waiting on <0x1cd570> (a android.os.MessageQueue)
DALVIK THREADS:
"main" prio=5 tid=3 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 s=0 obj=0x400143a8
  | sysTid=691 nice=0 sched=0/0 handle=-1091117924
  at java.lang.Object.wait(Native Method)
  - waiting on <0x1cd570> (a android.os.MessageQueue)
  at java.lang.Object.wait(Object.java:195)
  at android.os.MessageQueue.next(MessageQueue.java:144)
  at android.os.Looper.loop(Looper.java:110)
  at android.app.ActivityThread.main(ActivityThread.java:3742)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
  at dalvik.system.NativeStart.main(Native Method)

"Binder Thread #3" prio=5 tid=15 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x434e7758
  | sysTid=734 nice=0 sched=0/0 handle=1733632
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #2" prio=5 tid=13 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x1cd570
  | sysTid=696 nice=0 sched=0/0 handle=1369840
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #1" prio=5 tid=11 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433aca10
  | sysTid=695 nice=0 sched=0/0 handle=1367448
  at dalvik.system.NativeStart.run(Native Method)

----- end 691 -----

二则吟、主線程做耗時(shí)的操作:比如數(shù)據(jù)庫讀寫。

"main" prio=5 tid=1 Native
held mutexes=
kernel: (couldn't read /proc/self/task/11003/stack)
native: #00 pc 000492a4 /system/lib/libc.so (nanosleep+12)
native: #01 pc 0002dc21 /system/lib/libc.so (usleep+52)
native: #02 pc 00009cab /system/lib/libsqlite.so (???)
native: #03 pc 00011119 /system/lib/libsqlite.so (???)
native: #04 pc 00016455 /system/lib/libsqlite.so (???)
native: #16 pc 0000fa29 /system/lib/libsqlite.so (???)
native: #17 pc 0000fad7 /system/lib/libsqlite.so (sqlite3_prepare16_v2+14)
native: #18 pc 0007f671 /system/lib/libandroid_runtime.so (???)
native: #19 pc 002b4721 /system/framework/arm/boot-framework.oat (Java_android_database_sqlite_SQLiteConnection_nativePrepareStatement__JLjava_lang_String_2+116)
at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:294)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:215)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:808)
locked <0x0db193bf> (a java.lang.Object)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:793)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:696)
at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:690)
at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:299)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:223)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)
locked <0x045a4a8c> (a com.xxxx.video.common.data.DataBaseHelper)
at com.xxxx.video.common.data.DataBaseORM.<init>(DataBaseORM.java:46)
at com.xxxx.video.common.data.DataBaseORM.getInstance(DataBaseORM.java:53)
locked <0x017095d5> (a java.lang.Class<com.xxxx.video.common.data.DataBaseORM>)

三锄蹂、binder數(shù)據(jù)量過大

07-21 04:43:21.573  1000  1488 12756 E Binder  : Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 388568 (data: 1, 32, 7274595)
07-21 04:43:21.573  1000  1488 12756 E Binder  : android.util.Log$TerribleFailure: Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 388568 (data: 1, 32, 7274595)
07-21 04:43:21.607  1000  1488  2951 E Binder  : Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 211848 (data: 1, 23, 7274595)
07-21 04:43:21.607  1000  1488  2951 E Binder  : android.util.Log$TerribleFailure: Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 211848 (data: 1, 23, 7274595)
07-21 04:43:21.662  1000  1488  6258 E Binder  : Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 259300 (data: 1, 33, 7274595)

四氓仲、binder 通信失敗

07-21 06:04:35.580 <6>[32837.690321] binder: 1698:2362 transaction failed 29189/-3, size 100-0 line 3042
07-21 06:04:35.594 <6>[32837.704042] binder: 1765:4071 transaction failed 29189/-3, size 76-0 line 3042
07-21 06:04:35.899 <6>[32838.009132] binder: 1765:4067 transaction failed 29189/-3, size 224-8 line 3042
07-21 06:04:36.018 <6>[32838.128903] binder: 1765:2397 transaction failed 29189/-22, size 348-0 line 2916

ANR監(jiān)控方案

  1. ANR-WatchDog

    ANR-WatchDog 是參考 Android WatchDog 機(jī)制(com.android.server.WatchDog.java)起個(gè)單獨(dú)線程向主線程發(fā)送一個(gè)變量 +1 操作,自我休眠自定義 ANR 的閾值败匹,休眠過后判斷變量是否 +1 完成寨昙,如果未完成則告警讥巡。

ANR-watchDog.png
  • 優(yōu)點(diǎn):

    • (1) 兼容性好掀亩,無需適配機(jī)型。

    • (2) 無需改動APP邏輯代碼欢顷,非侵入性槽棍。

    • (3) 性能影響不大。

    • 缺點(diǎn):

      • (1) 無法保證能捕獲所有ANR抬驴,對閾值設(shè)置影響捕獲概率炼七。如時(shí)間過長,中間發(fā)生的ANR則可能被遺漏掉布持。

    ANR-WatchDog倉庫鏈接 https://github.com/SalomonBrys/ANR-WatchDog#how-it-works

  1. xCrash

    Android 應(yīng)用在收到異常終止信號(SIGQUIT)時(shí)豌拙,沒有遵循傳統(tǒng) UNIX信號模型的默認(rèn)行為 (終止 + core )。而是打印出trace 文件來题暖,以利于記錄應(yīng)用異常終止的原因按傅。

    核心代碼位于xc_trace.c ,初始化時(shí)開啟了一個(gè)常駐線程胧卤,當(dāng)捕捉SIGQUIT信號時(shí)唯绍,既沒有nativecrash也沒有javacrash,就認(rèn)為是ANR了枝誊。


    while(1)
    {
        //block here, waiting for sigquit
        XCC_UTIL_TEMP_FAILURE_RETRY(read(xc_trace_notifier, &data, sizeof(data)));
        
        //check if process already crashed
        if(xc_common_native_crashed || xc_common_java_crashed) break;
        
        ......
        //記錄ANR信息
ANR-Xcrash原理.png

xCrash倉庫鏈接 https://github.com/iqiyi/xCrash/blob/master/README.zh-CN.md

  1. BlockCanary

    利用 Looper.setMessageLogging(Printer printer);

    • 優(yōu)點(diǎn):靈活配置可監(jiān)控常見APP應(yīng)用性能也可作為一部分場景的ANR監(jiān)測况芒,并且可以準(zhǔn)確定位ANR和耗時(shí)調(diào)用棧。

    • 缺點(diǎn):

      (1) 谷歌已經(jīng)明確標(biāo)注This must be in a local variable, in case a UI event sets the logger這個(gè)looger對象是可以被更改的叶撒,已經(jīng)有開發(fā)者遇到在使用WebView時(shí)logger被set為Null導(dǎo)致BlockCanary失效绝骚,只只能讓BlockCanary在WebView初始化之后調(diào)用start耐版。
      (2) 如果dispatchMessage執(zhí)行的非常久是無法觸發(fā)BlockCanary的邏輯。
      (3) 在Printer輸出之前皮壁,有一段代碼queue.next()也會可能發(fā)生ANR椭更,從而造成無法監(jiān)控到ANR。
      (4) 無法監(jiān)控CPU資源緊張?jiān)斐上到y(tǒng)卡頓無法響應(yīng)的ANR蛾魄。

    BlockCanary 倉庫鏈接 https://github.com/seiginonakama/BlockCanaryEx

  2. matrix-TraceCanary模塊

    利用 Looper.setMessageLogging(Printer printer);

    利用IdleHandler 閑時(shí)機(jī)制循環(huán)設(shè)置Printer虑瀑,避免Looper中的Printer為空

    核心代碼位于com.tencent.matrix.trace.core.LooperMonitor的構(gòu)造方法中

        private synchronized void resetPrinter() {
            Printer originPrinter = null;
            try {
                if (!isReflectLoggingError) {
                    originPrinter = ReflectUtils.get(looper.getClass(), "mLogging", looper);
                    if (originPrinter == printer && null != printer) {
                        return;
                    }
                }
            } catch (Exception e) {
                isReflectLoggingError = true;
                Log.e(TAG, "[resetPrinter] %s", e);
            }
    
            if (null != printer) {
                MatrixLog.w(TAG, "maybe thread:%s printer[%s] was replace other[%s]!",
                        looper.getThread().getName(), printer, originPrinter);
            }
            looper.setMessageLogging(printer = new LooperPrinter(originPrinter));
            if (null != originPrinter) {
                MatrixLog.i(TAG, "reset printer, originPrinter[%s] in %s", originPrinter, looper.getThread().getName());
            }
        }
    

    matrix倉庫鏈接 https://github.com/mmin18/SafeLooper

  3. SafeLooper(棄用)

    SafeLooper通過hook Looper,捕獲dispatchMsg() 產(chǎn)生的異常并通過 Thread.UncaughtExceptionHandler捕獲異常滴须,這種方式與常用的java層crash監(jiān)控方式有沖突舌狗,并且SafeLooper最后維護(hù)時(shí)間為2013年

ANR-safelooper.png

SafeLooper倉庫鏈接 https://github.com/mmin18/SafeLooper

  1. FileObserver(不建議)

    FileObserver監(jiān)聽/data/anr目錄下文件是否有新增".tarce"結(jié)尾的文件,如果有則認(rèn)為發(fā)生ANR扔水,并導(dǎo)出trace文件痛侍,注意如果當(dāng)多個(gè)APP同時(shí)發(fā)生ANR,里面會有多個(gè)trace文件魔市,需要對包名時(shí)間等進(jìn)行過濾主届。

    • 優(yōu)點(diǎn)
      1、基于原生接口調(diào)用待德,時(shí)機(jī)和內(nèi)容準(zhǔn)確君丁。
      2、無性能問題實(shí)現(xiàn)簡單

    • 缺點(diǎn)
      最大的困難是兼容性問題将宪,這個(gè)方案受限于 Android 系統(tǒng)的 SELinux 機(jī)制绘闷,5.0 以后基本已經(jīng)使低權(quán)限應(yīng)用無法監(jiān)聽到 trace 文件了,但是可以在開發(fā)內(nèi)測階段通過 root 手機(jī)修改 app 對應(yīng)的 te 文件提權(quán)進(jìn)行監(jiān)控较坛。

      SEAndroid及SELinux 參考鏈接 https://blog.csdn.net/qq_19923217/article/details/81240027

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末印蔗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丑勤,更是在濱河造成了極大的恐慌华嘹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件法竞,死亡現(xiàn)場離奇詭異耙厚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)爪喘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門颜曾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秉剑,你說我怎么就攤上這事泛豪。” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵诡曙,是天一觀的道長臀叙。 經(jīng)常有香客問我,道長价卤,這世上最難降的妖魔是什么劝萤? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮慎璧,結(jié)果婚禮上床嫌,老公的妹妹穿的比我還像新娘。我一直安慰自己胸私,他們只是感情好厌处,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岁疼,像睡著了一般阔涉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捷绒,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天瑰排,我揣著相機(jī)與錄音,去河邊找鬼暖侨。 笑死椭住,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的它碎。 我是一名探鬼主播函荣,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼显押,長吁一口氣:“原來是場噩夢啊……” “哼扳肛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乘碑,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤挖息,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后兽肤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體套腹,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年资铡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了电禀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笤休,死狀恐怖尖飞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤政基,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布贞铣,位于F島的核電站,受9級特大地震影響沮明,放射性物質(zhì)發(fā)生泄漏辕坝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一荐健、第九天 我趴在偏房一處隱蔽的房頂上張望酱畅。 院中可真熱鬧,春花似錦江场、人聲如沸圣贸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吁峻。三九已至,卻和暖如春在张,著一層夾襖步出監(jiān)牢的瞬間用含,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工帮匾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啄骇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓缸夹,卻偏偏與公主長得像螺句,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子蛇尚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容