導(dǎo)致ANR的幾種情況
- KeyDispatchTimeout(5s): 按鍵或觸摸事件在特定時間內(nèi)無法處理完成
- BroadcastTimeout(前臺10s,后臺60s): 廣播在特定時間內(nèi)無法處理完成
- ServiceTimeout(前臺20s,后臺200s): Service在特定的時間無法處理完成 另外還有ProviderTimeout和WatchDog等導(dǎo)致的ANR
常見的原因
A.耗時操作,如復(fù)雜的layout,龐大的for循環(huán),IO等。
B.被Binder 對端block
C.被子線程同步鎖block
D.Binder被占滿導(dǎo)致主線程無法和SystemServer通信
E.得不到系統(tǒng)資源(CPU/RAM/IO)
其中ABCD比較好分析,而E比較困難。
應(yīng)用ANR產(chǎn)生的時候,ActivityManagerService的appNotResponding方法就會被調(diào)用,然后在/data/anr/traces.txt文件中寫入ANR相關(guān)信息.
ANR就不作介紹了,下面只介紹如何分析ANR異常.
通常發(fā)生了ANR,ActivityManager會打印報錯信息:
E/ActivityManager: ANR in com.rui.android.poc // ANR出現(xiàn)的進(jìn)程包名
PID: 1322 // ANR進(jìn)程ID
Reason: Broadcast of Intent { act=android.intent.action.SCREEN_ON flg=0x50000010 }//導(dǎo)致ANR的原因
Load: 4.13 / 2.52 / 1.05
CPU usage from 0ms to 12843ms later://CPU在ANR發(fā)生后的使用情況
98% 1322/com.rui.android.poc: 0.5% user + 98% kernel / faults: 1044 minor 104 major
5.6% 567/system_server: 3.4% user + 2.1% kernel / faults: 1940 minor 217 major
2.3% 766/com.android.systemui: 1.2% user + 1% kernel / faults: 251 minor 165 major
1.4% 268/kworker/0:2: 0% user + 1.4% kernel
1.2% 1690/adbd: 0.1% user + 1% kernel / faults: 2 minor 3 major
1% 756/com.android.phone: 0.6% user + 0.3% kernel / faults: 347 minor 61 major
0.9% 139/surfaceflinger: 0.3% user + 0.5% kernel / faults: 1 minor 5 major
0.2% 929/com.sprd.opm: 0.2% user + 0% kernel / faults: 279 minor 115 major
0.2% 23/kworker/u4:1: 0% user + 0.2% kernel
0.2% 368/kworker/u5:1: 0% user + 0.2% kernel
0.1% 914/com.android.modemassert: 0% user + 0% kernel / faults: 182 minor 66 major
0.1% 44/ksmd: 0% user + 0.1% kernel
0% 909/com.spreadst.validator: 0% user + 0% kernel / faults: 164 minor 39 major
0% 8/rcu_preempt: 0% user + 0% kernel
0% 43/kswapd0: 0% user + 0% kernel
0% 117/mmcqd/0: 0% user + 0% kernel
+0% 1770/kworker/0:3: 0% user + 0% kernel
+0% 1774/debuggerd: 0% user + 0% kernel
97% TOTAL: 3.7% user + 55% kernel + 38% iowait // CUP占用情況
CPU usage from 12301ms to 12824ms later:
100% 1322/com.rui.android.poc: 0% user + 100% kernel
100% 1322/rui.android.poc: 0% user + 100% kernel
1.9% 1333/Binder_2: 0% user + 1.9% kernel
3.8% 567/system_server: 1.9% user + 1.9% kernel
3.8% 581/ActivityManager: 1.9% user + 1.9% kernel
1.3% 139/surfaceflinger: 1.3% user + 0% kernel
1.3% 249/DispSync: 0% user + 1.3% kernel
1.5% 766/com.android.systemui: 1.5% user + 0% kernel / faults: 2 minor
1.5% 766/ndroid.systemui: 0% user + 1.5% kernel
1.6% 1770/kworker/0:3: 0% user + 1.6% kernel
100% TOTAL: 0% user + 54% kernel + 45% iowait
Log分析:
log打印了ANR的基本信息,我們可以分析CPU使用率得知ANR的簡單情況;如果CPU使用率很高,接近100%,可能是在進(jìn)行大規(guī)模的計算更可能是陷入死循環(huán);如果CUP使用率很低,說明主線程被阻塞了,并且當(dāng)IOwait很高,可能是主線程在等待I/O操作的完成.
對于ANR只是分析Log很難知道問題所在,我們還需要通過Trace文件分析stack調(diào)用情況.
traces.txt是如何生成的
當(dāng)APP(包括系統(tǒng)APP和用戶APP)進(jìn)程出現(xiàn)ANR、應(yīng)用響應(yīng)慢或WatchDog的監(jiān)視沒有得到回饋時,系統(tǒng)會dump此時的top進(jìn)程,進(jìn)程中Thread的運(yùn)行狀態(tài)就都dump到這個Trace文件中了.每次發(fā)生ANR, 這個文件都會被清空,寫入新的內(nèi)容. 如果想查看以前發(fā)生ANR的信息, 可以去查看DB文件.
DropBox中的log
traces.txt只保留最后一次發(fā)生ANR時的信息, android 2.2開始增加了DropBox功能, 保留歷史上發(fā)生的所有ANR的log.
“/data/system/dropbox”是DB指定的文件存放位置.
日志保存的最長時間, 默認(rèn)是3天.
shell@sp9820a_Rui8503:/data # cd system/dropbox/
shell@sp9820a_Rui8503:/data/system/dropbox # ls
SYSTEM_BOOT@1325376052965.txt
system_app_crash@1325376052598.txt
system_app_crash@1325376054905.txt
system_app_strictmode@1325376048453.txt.gz
system_app_strictmode@1325376053442.txt.gz
system_app_strictmode@1325376058904.txt.gz
system_server_wtf@1325376043066.txt
system_server_wtf@1325376052708.txt
system_server_wtf@1325376052842.txt
shell@sp9820a_Rui8503:/data/system/dropbox #
SystemServer在啟動時, 會創(chuàng)建并添加DROPBOX_SERVICE.
//SystemServer.java
private void startOtherServices() {
... ...
try {
Slog.i(TAG, "DropBox Service");
ServiceManager.addService(Context.DROPBOX_SERVICE,
new DropBoxManagerService(context, new File("/data/system/dropbox")));
} catch (Throwable e) {
reportWtf("starting DropBoxManagerService", e);
}
... ...
}
在traces.txt中可以看到如下信息:
//說明了發(fā)生ANR的進(jìn)程id、時間和進(jìn)程名稱。
----- pid 2023 at 2018-07-24 19:33:17 -----
Cmd line: com.rui.android.poc
//下面三行是線程的基本信息
JNI: CheckJNI is off; workarounds are off; pins=0; globals=348
DALVIK THREADS://以下是各個線程的函數(shù)堆棧信息
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)
//下面一行說明了線程名稱、daemon表示守護(hù)進(jìn)程,線程的優(yōu)先級(默認(rèn)5)、線程鎖id和線程狀態(tài),
//線程名稱是啟動線程的時候手動指明的,這里的main標(biāo)識是主線程,是Android自動設(shè)定的
//一個線程名稱,如果是自己手動創(chuàng)建的線程,一般會被命名成“Thread-xx”的格式,其中xx是
//線程id,它只增不減不會被復(fù)用;注意這其中的tid不是線程的id,它是一個在Java虛擬機(jī)中用
//來實(shí)現(xiàn)線程鎖的變量,隨著線程的增減,這個變量的值是可能被復(fù)用的;
"main" prio=5 tid=1 NATIVE
//group是線程組名稱。sCount是此線程被掛起的次數(shù),dsCount是線程被調(diào)試器掛起的次數(shù),
//當(dāng)一個進(jìn)程被調(diào)試后,sCount會重置為0,調(diào)試完畢后sCount會根據(jù)是否被正常掛起增長,
//但是dsCount不會被重置為0,所以dsCount也可以用來判斷這個線程是否被調(diào)試過。obj表示
//這個線程的Java對象的地址,self表示這個線程N(yùn)ative的地址。
| group="main" sCount=1 dsCount=0 obj=0x4164dcf0 self=0x41565628
//此后是線程的調(diào)度信息,sysTid是Linux下的內(nèi)核線程id,nice是線程的調(diào)度優(yōu)先級,
//sched分別標(biāo)志了線程的調(diào)度策略和優(yōu)先級,cgrp是調(diào)度屬組,handle是線程的處理函數(shù)地址。
| sysTid=2023 nice=-1 sched=0/0 cgrp=apps handle=1074626900
//線程當(dāng)前上下文信息,state是調(diào)度狀態(tài);schedstat從 /proc/[pid]/task/[tid]/schedstat讀出,
//三個值分別表示線程在cpu上執(zhí)行的時間、線程的等待時間和線程執(zhí)行的時間片長度,有的
//android內(nèi)核版本不支持這項信息,得到的三個值都是0;utm是線程用戶態(tài)下使用的時間
//值(單位是jiffies);stm是內(nèi)核態(tài)下的調(diào)度時間值;core是最后執(zhí)行這個線程的cpu核的序號。
| state=S schedstat=( 0 0 0 ) utm=49 stm=21 core=0
//線程的調(diào)用棧信息(這里可查看導(dǎo)致ANR的代碼調(diào)用流程,分析ANR最重要的信息)
(native backtrace unavailable)
at libcore.io.Posix.open(Native Method)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:110)
at libcore.io.IoBridge.open(IoBridge.java:430)
at java.io.FileInputStream.<init>(FileInputStream.java:78)
at com.ruiven.android.Peripheral.util.kingFiles.write(kingFiles.java:58)
at com.ruiven.android.Peripheral.util.kingFiles.write(kingFiles.java:51)
at com.ruiven.android.Peripheral.util.kingLog.writeToFiles(kingLog.java:71)
at com.ruiven.android.Peripheral.util.kingLog.f(kingLog.java:47)
at com.ruiven.android.poc.receiver.ReceiverBatteryChanged.onReceive(ReceiverBatteryChanged.java:114)
at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:767)
at android.os.Handler.handleCallback(Handler.java:769)
at android.os.Handler.dispatchMessage(Handler.java:97)
at android.os.Looper.loop(Looper.java:136)
at com.ruiven.android.Peripheral.util.Cockroach$1.run(Cockroach.java:38)
at android.os.Handler.handleCallback(Handler.java:769)
at android.os.Handler.dispatchMessage(Handler.java:97)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5375)
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:976)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:792)
at dalvik.system.NativeStart.main(Native Method)
//Binder線程是進(jìn)程的線程池中用來處理binder請求的線程
"Binder_3" prio=5 tid=23 NATIVE
| group="main" sCount=1 dsCount=0 obj=0x41b765f8 self=0x4f213f18
| sysTid=1347 nice=0 sched=0/0 cgrp=apps handle=1327603952
| state=S schedstat=( 0 0 0 ) utm=0 stm=3 core=1
#00 pc 000205d0 /system/lib/libc.so (__ioctl+8)
#01 pc 0002d01f /system/lib/libc.so (ioctl+14)
#02 pc 0001e519 /system/lib/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+140)
#03 pc 0001ec67 /system/lib/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+6)
#04 pc 0001ecfd /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+48)
#05 pc 000236cd /system/lib/libbinder.so
#06 pc 0000ea19 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+216)
#07 pc 0004e769 /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+68)
#08 pc 0000e54b /system/lib/libutils.so
#09 pc 0000d240 /system/lib/libc.so (__thread_entry+72)
#10 pc 0000d3dc /system/lib/libc.so (pthread_create+240)
at dalvik.system.NativeStart.run(Native Method)
//JDWP線程是支持虛擬機(jī)調(diào)試的線程,daemon表示守護(hù)進(jìn)程,不需要關(guān)心
"JDWP" daemon prio=5 tid=4 VMWAIT
| group="system" sCount=1 dsCount=0 obj=0x41a21868 self=0x4cc821b0
| sysTid=785 nice=0 sched=0/0 cgrp=apps handle=1257589656
| state=S schedstat=( 0 0 0 ) utm=0 stm=0 core=1
#00 pc 00021420 /system/lib/libc.so (recvmsg+8)
#01 pc 000668ab /system/lib/libdvm.so
#02 pc 00066adf /system/lib/libdvm.so
#03 pc 000697af /system/lib/libdvm.so
#04 pc 00059fdd /system/lib/libdvm.so
#05 pc 0000d240 /system/lib/libc.so (__thread_entry+72)
#06 pc 0000d3dc /system/lib/libc.so (pthread_create+240)
at dalvik.system.NativeStart.run(Native Method)
//“Signal Catcher”負(fù)責(zé)接收和處理kernel發(fā)送的各種信號,例如SIGNAL_QUIT、SIGNAL_USR1等就是被該線程
//接收到并處理的,traces.txt 文件中的內(nèi)容就是由該線程負(fù)責(zé)輸出的,可以看到它的狀態(tài)是RUNNABLE.
"Signal Catcher" daemon prio=5 tid=3 RUNNABLE
| group="system" sCount=0 dsCount=0 obj=0x41a21770 self=0x4af3e5e0
| sysTid=784 nice=0 sched=0/0 cgrp=apps handle=1257677168
| state=R schedstat=( 0 0 0 ) utm=2 stm=2 core=1
at dalvik.system.NativeStart.run(Native Method)
----- end 2023 -----
如果ANR在native層,那么堆棧中就不會有相關(guān)調(diào)用的路徑,這種情況只能在native層添加更多的Log,一步步來查找了
NATIVE表示當(dāng)前線程的狀態(tài):
Thread狀態(tài)及其對應(yīng)的值
ThreadState (defined at “dalvik/vm/thread.h “)
THREAD_UNDEFINED = -1, /* makes enum compatible with int32_t */
THREAD_ZOMBIE = 0, /* TERMINATED */( 線程死亡,終止運(yùn)行)
THREAD_RUNNING = 1, /* RUNNABLE or running now */(線程可運(yùn)行或正在運(yùn)行)
THREAD_TIMED_WAIT = 2, /* TIMED_WAITING in Object.wait() */(執(zhí)行了帶有超時參數(shù)的wait、sleep或join函數(shù))
THREAD_MONITOR = 3, /* BLOCKED on a monitor */(線程阻塞,等待獲取對象鎖)
THREAD_WAIT = 4, /* WAITING in Object.wait() */( 執(zhí)行了無超時參數(shù)的wait函數(shù))
THREAD_INITIALIZING= 5, /* allocated, not yet running */(新建,正在初始化,為其分配資源)
THREAD_STARTING = 6, /* started, not yet on thread list */(新建,正在啟動)
THREAD_NATIVE = 7, /* off in a JNI native method */(正在執(zhí)行JNI本地函數(shù))
THREAD_VMWAIT = 8, /* waiting on a VM resource */(正在等待VM資源)
THREAD_SUSPENDED = 9, /* suspended, usually by GC or debugger */(線程暫停,通常是由于GC或debug被暫停)
**特別說明一下MONITOR狀態(tài)和SUSPEND狀態(tài),MONITOR狀態(tài)一般是類的同步塊或者同步方法造成的,SUSPENDED狀態(tài)在debugger的時候會出現(xiàn),可以用來區(qū)別是不是真的是用戶正常操作跑出了ANR。
mutexes(互斥)的選項簡寫對應(yīng)含義:
tll--thread list lock
tsl-- thread suspend lock
tscl-- thread suspend count lock
ghl--gc heap lock
hwl--heap worker lock
hwll--heap worker list lock
- 通過Cmd line: packagename可以找到要分析的進(jìn)程
- 并不是trace文件包含的應(yīng)用就一定是造成ANR的幫兇,應(yīng)用出現(xiàn)在trace文件中,只能說明出現(xiàn)ANR的時候這個應(yīng)用進(jìn)程還活著,trace文件的頂部則是觸發(fā)ANR的應(yīng)用信息。因此,如果你的應(yīng)用出現(xiàn)在了trace文件的頂部,那么很有可能是因為你的應(yīng)用造成了ANR,否則是你的應(yīng)用造成ANR的可能性不大,但是具體是不是還需要進(jìn)一步分析
參考:https://blog.csdn.net/yxz329130952/article/details/50087731