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è)流程包含三部分組成:
埋定時(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)。
拆炸彈:在規(guī)定的時(shí)間內(nèi)干完工地的所有活淤刃,并及時(shí)向中控系統(tǒng)報(bào)告完成晒他,請求解除定時(shí)炸彈,則幸免于難逸贾。
引爆炸彈:中控系統(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í)閾值如下表:
-
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問題定位
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:onCreate
,onStart
榕栏,onBind
等生命周期在20s
內(nèi)沒有處理完成發(fā)生ANR叠穆。
后臺Service:onCreate
,onStart
臼膏,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文件分析
- 人為的收集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è)對象
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)先級
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)控方案
-
ANR-WatchDog
ANR-WatchDog 是參考 Android WatchDog 機(jī)制(com.android.server.WatchDog.java)起個(gè)單獨(dú)線程向主線程發(fā)送一個(gè)變量 +1 操作,自我休眠自定義 ANR 的閾值败匹,休眠過后判斷變量是否 +1 完成寨昙,如果未完成則告警讥巡。
-
優(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
-
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信息
xCrash倉庫鏈接 https://github.com/iqiyi/xCrash/blob/master/README.zh-CN.md
-
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
-
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
-
SafeLooper(棄用)
SafeLooper通過hook Looper,捕獲dispatchMsg() 產(chǎn)生的異常并通過 Thread.UncaughtExceptionHandler捕獲異常滴须,這種方式與常用的java層crash監(jiān)控方式有沖突舌狗,并且SafeLooper最后維護(hù)時(shí)間為2013年
SafeLooper倉庫鏈接 https://github.com/mmin18/SafeLooper
-
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