Android ANR
ANR即Application Not Responding,給使用使用者帶來(lái)的感受是程序長(zhǎng)時(shí)間無(wú)響應(yīng),最終應(yīng)用閃退。
在 Android 里,應(yīng)用程序的響應(yīng)性是由 Activity Manager 和 WindowManager 系統(tǒng)服務(wù)監(jiān)視的。當(dāng)它監(jiān)測(cè)到以下情況中的一個(gè)時(shí),Android 就會(huì)針對(duì)特定的應(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è)流程包含三部分組成:
埋定時(shí)炸彈:中控系統(tǒng)(system_server進(jìn)程)啟動(dòng)倒計(jì)時(shí),在規(guī)定時(shí)間內(nèi)如果目標(biāo)(應(yīng)用進(jìn)程)沒(méi)有干完所有的活,則中控系統(tǒng)會(huì)定向炸毀(殺進(jìn)程)目標(biāo)。
拆炸彈:在規(guī)定的時(shí)間內(nèi)干完工地的所有活,并及時(shí)向中控系統(tǒng)報(bào)告完成,請(qǐng)求解除定時(shí)炸彈,則幸免于難。
引爆炸彈:中控系統(tǒng)立即封裝現(xiàn)場(chǎng),抓取快照,搜集目標(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í)閾值如下表:

-
service前后臺(tái)判斷
ActiveServices在startService過(guò)程根據(jù)發(fā)起方進(jìn)程callerApp所屬的進(jìn)程調(diào)度組來(lái)決定被啟動(dòng)的服務(wù)是屬于前臺(tái)還是后臺(tái)。當(dāng)發(fā)起方進(jìn)程不等于ProcessList.SCHED_GROUP_BACKGROUND(后臺(tái)進(jìn)程組)則認(rèn)為是前臺(tái)服務(wù),否則為后臺(tái)服務(wù),并標(biāo)記在ServiceRecord的成員變量createdFromFg。
-
broadcoast前后臺(tái)判斷
根據(jù)發(fā)送廣播sendBroadcast(Intent intent)中的intent的flags是否包含F(xiàn)LAG_RECEIVER_FOREGROUND來(lái)決定把該廣播是放入前臺(tái)廣播隊(duì)列或者后臺(tái)廣播隊(duì)列,,默認(rèn)情況下廣播是放入后臺(tái)廣播隊(duì)列,除非指明加上FLAG_RECEIVER_FOREGROUND標(biāo)識(shí)。
另外,只有串行處理的廣播才有超時(shí)機(jī)制,因?yàn)榻邮照呤谴刑幚淼?,前一個(gè)receiver處理慢,會(huì)影響后一個(gè)receiver;并行廣播通過(guò)一個(gè)循環(huán)一次性向所有的receiver分發(fā)廣播事件,所以不存在彼此影響的問(wèn)題,則沒(méi)有廣播超時(shí)。
什么進(jìn)程屬于SCHED_GROUP_BACKGROUND調(diào)度組呢?進(jìn)程調(diào)度組大體可分為T(mén)OP、前臺(tái)、后臺(tái),進(jìn)程優(yōu)先級(jí)(Adj)和進(jìn)程調(diào)度組(SCHED_GROUP)算法較為復(fù)雜,其對(duì)應(yīng)關(guān)系可粗略理解為Adj等于0的進(jìn)程屬于Top進(jìn)程組,Adj等于100或者200的進(jìn)程屬于前臺(tái)進(jìn)程組,Adj大于200的進(jìn)程屬于后臺(tái)進(jìn)程組。關(guān)于Adj的含義見(jiàn)下表,簡(jiǎn)單來(lái)說(shuō)就是Adj>200的進(jìn)程對(duì)用戶(hù)來(lái)說(shuō)基本是無(wú)感知,主要是做一些后臺(tái)工作,故后臺(tái)服務(wù)擁有更長(zhǎng)的超時(shí)閾值,同時(shí)后臺(tái)服務(wù)屬于后臺(tái)進(jìn)程調(diào)度組,相比前臺(tái)服務(wù)屬于前臺(tái)進(jìn)程調(diào)度組,分配更少的CPU時(shí)間片。

ANR問(wèn)題定位
logcat日志分析
查看mobilelog文件夾下的events_log,從日志中搜索關(guān)鍵字:am_anr,找到出現(xiàn)ANR的時(shí)間點(diǎn)、進(jìn)程PID、ANR類(lèi)型。
出現(xiàn)ANR的一般有以下幾種類(lèi)型:
1:KeyDispatchTimeout(常見(jiàn))
input事件在5S內(nèi)沒(méi)有處理完成發(fā)生了ANR。
logcat日志關(guān)鍵字:ActivityManagerService,inputDispatchingTimedOut,Input event dispatching timed out等
2:BroadcastTimeout
前臺(tái)Broadcast:onReceiver在10S內(nèi)沒(méi)有處理完成發(fā)生ANR。
后臺(tái)Broadcast:onReceiver在60s內(nèi)沒(méi)有處理完成發(fā)生ANR。
logcat日志關(guān)鍵字:Timeout of broadcast BroadcastRecord
3:ServiceTimeout
前臺(tái)Service:onCreate,onStart,onBind等生命周期在20s內(nèi)沒(méi)有處理完成發(fā)生ANR。
后臺(tái)Service:onCreate,onStart,onBind等生命周期在200s內(nèi)沒(méi)有處理完成發(fā)生ANR
logcat日志關(guān)鍵字:Timeout executing service
4:ContentProviderTimeout
ContentProvider 在10S內(nèi)沒(méi)有處理完成發(fā)生ANR。 logcat日志關(guān)鍵字:timeout publishing content providers
trace文件分析
- 人為的收集trace.txt的命令
adb shell kill -3 888 //可指定進(jìn)程pid
執(zhí)行完該命令后traces信息的結(jié)果保存到文件/data/anr/traces.txt - 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è)對(duì)象
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è)線程.
- 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)先級(jí)
tid: 線程內(nèi)部id
-
線程狀態(tài): NATIVE
ANR線程狀態(tài)對(duì)照表.png
- 第1行:
- group: 線程所屬的線程組
- sCount: 線程掛起次數(shù)
- dsCount: 用于調(diào)試的線程掛起次數(shù)
- obj: 當(dāng)前線程關(guān)聯(lián)的java線程對(duì)象
- self: 當(dāng)前線程地址
- 第2行:
- sysTid:線程真正意義上的tid
- nice: 調(diào)度有優(yōu)先級(jí)
- 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: 用戶(hù)態(tài)/內(nèi)核態(tài)的CPU時(shí)間(單位是jiffies)
- core: 該線程的最后運(yùn)行所在核
- HZ: 時(shí)鐘頻率
- 第4行:
- stack:線程棧的地址區(qū)間
- stackSize:棧的大小
- 第5行:
- mutex: 所持有mutex類(lèi)型,有獨(dú)占鎖exclusive和共享鎖shared兩類(lèi)
- schedstat含義說(shuō)明:
nice值越小則優(yōu)先級(jí)越高。此處nice=-2, 可見(jiàn)優(yōu)先級(jí)還是比較高的;
-
schedstat括號(hào)中的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: 該線程在用戶(hù)態(tài)所執(zhí)行的時(shí)間,單位是jiffies,jiffies定義為sysconf(_SC_CLK_TCK),默認(rèn)等于10ms
- stm: 該線程在內(nèi)核態(tài)所執(zhí)行的時(shí)間,單位是jiffies,默認(rèn)等于10ms
- 可見(jiàn),該線程Running=186667489018ns,也約等于186667ms。在CPU運(yùn)行時(shí)間包括用戶(hù)態(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ù)庫(kù)讀寫(xiě)。
"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ù)量過(guò)大
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)控方案
-
ANR-WatchDog
ANR-WatchDog 是參考 Android WatchDog 機(jī)制(com.android.server.WatchDog.java)起個(gè)單獨(dú)線程向主線程發(fā)送一個(gè)變量 +1 操作,自我休眠自定義 ANR 的閾值,休眠過(guò)后判斷變量是否 +1 完成,如果未完成則告警。

-
優(yōu)點(diǎn):
(1) 兼容性好,無(wú)需適配機(jī)型。
(2) 無(wú)需改動(dòng)APP邏輯代碼,非侵入性。
(3) 性能影響不大。
-
缺點(diǎn):
- (1) 無(wú)法保證能捕獲所有ANR,對(duì)閾值設(shè)置影響捕獲概率。如時(shí)間過(guò)長(zhǎng),中間發(fā)生的ANR則可能被遺漏掉。
ANR-WatchDog倉(cāng)庫(kù)鏈接 https://github.com/SalomonBrys/ANR-WatchDog#how-it-works
-
xCrash
Android 應(yīng)用在收到異常終止信號(hào)(SIGQUIT)時(shí),沒(méi)有遵循傳統(tǒng) UNIX信號(hào)模型的默認(rèn)行為 (終止 + core )。而是打印出trace 文件來(lái),以利于記錄應(yīng)用異常終止的原因。
核心代碼位于xc_trace.c ,初始化時(shí)開(kāi)啟了一個(gè)常駐線程,當(dāng)捕捉SIGQUIT信號(hào)時(shí),既沒(méi)有nativecrash也沒(méi)有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信息

xCrash倉(cāng)庫(kù)鏈接 https://github.com/iqiyi/xCrash/blob/master/README.zh-CN.md
-
BlockCanary
利用 Looper.setMessageLogging(Printer printer);
優(yōu)點(diǎn):靈活配置可監(jiān)控常見(jiàn)APP應(yīng)用性能也可作為一部分場(chǎng)景的ANR監(jiān)測(cè),并且可以準(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對(duì)象是可以被更改的,已經(jīng)有開(kāi)發(fā)者遇到在使用WebView時(shí)logger被set為Null導(dǎo)致BlockCanary失效,只只能讓BlockCanary在WebView初始化之后調(diào)用start。
(2) 如果dispatchMessage執(zhí)行的非常久是無(wú)法觸發(fā)BlockCanary的邏輯。
(3) 在Printer輸出之前,有一段代碼queue.next()也會(huì)可能發(fā)生ANR,從而造成無(wú)法監(jiān)控到ANR。
(4) 無(wú)法監(jiān)控CPU資源緊張?jiān)斐上到y(tǒng)卡頓無(wú)法響應(yīng)的ANR。
BlockCanary 倉(cāng)庫(kù)鏈接 https://github.com/seiginonakama/BlockCanaryEx
-
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倉(cāng)庫(kù)鏈接 https://github.com/mmin18/SafeLooper
-
SafeLooper(棄用)
SafeLooper通過(guò)hook Looper,捕獲dispatchMsg() 產(chǎn)生的異常并通過(guò) Thread.UncaughtExceptionHandler捕獲異常,這種方式與常用的java層crash監(jiān)控方式有沖突,并且SafeLooper最后維護(hù)時(shí)間為2013年

SafeLooper倉(cāng)庫(kù)鏈接 https://github.com/mmin18/SafeLooper
-
FileObserver(不建議)
FileObserver監(jiān)聽(tīng)/data/anr目錄下文件是否有新增".tarce"結(jié)尾的文件,如果有則認(rèn)為發(fā)生ANR,并導(dǎo)出trace文件,注意如果當(dāng)多個(gè)APP同時(shí)發(fā)生ANR,里面會(huì)有多個(gè)trace文件,需要對(duì)包名時(shí)間等進(jìn)行過(guò)濾。
優(yōu)點(diǎn)
1、基于原生接口調(diào)用,時(shí)機(jī)和內(nèi)容準(zhǔn)確。
2、無(wú)性能問(wèn)題實(shí)現(xiàn)簡(jiǎn)單-
缺點(diǎn)
最大的困難是兼容性問(wèn)題,這個(gè)方案受限于 Android 系統(tǒng)的 SELinux 機(jī)制,5.0 以后基本已經(jīng)使低權(quán)限應(yīng)用無(wú)法監(jiān)聽(tīng)到 trace 文件了,但是可以在開(kāi)發(fā)內(nèi)測(cè)階段通過(guò) root 手機(jī)修改 app 對(duì)應(yīng)的 te 文件提權(quán)進(jìn)行監(jiān)控。SEAndroid及SELinux 參考鏈接 https://blog.csdn.net/qq_19923217/article/details/81240027




