在上一篇文章《Android源碼剖析:基于 Handler掘宪、Looper 實現(xiàn)攔截全局崩潰攘烛、監(jiān)控ANR等》介紹了如何實現(xiàn)簡單的ANR監(jiān)控鼠次,判斷是否出現(xiàn)了ANR觅捆,但是沒有介紹如何分析栅炒,這篇文章將會詳細介紹如何分析解決ANR問題职辅。
觸發(fā) ANR
- 5s內(nèi)無法響應用戶輸入事件(例如鍵盤輸入, 觸摸屏幕等)
- BroadcastReceiver在10s內(nèi)無法結束
- Service在特定的時間內(nèi)無法處理完成
檢測是否存在ANR
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
var startWorkTimeMillis = 0L
Looper.getMainLooper().setMessageLogging {
if (it.startsWith(">>>>> Dispatching to Handler")) {
startWorkTimeMillis = System.currentTimeMillis()
} else if (it.startsWith("<<<<< Finished to Handler")) {
val duration = System.currentTimeMillis() - startWorkTimeMillis
if (duration > 500) {
Log.e("主線程執(zhí)行耗時過長","$duration 毫秒簇秒,$it")
}
}
}
}
}
通過上述代碼可以檢測是否執(zhí)行耗時過長趋观,當出現(xiàn)ANR的時候皱坛,ANR執(zhí)行超過5秒,系統(tǒng)會把堆棧打印在/data/anr/traces.txt
。
獲取trace.txt 文件
adb shell cat /data/anr/traces.txt > d:/traces.txt
但是這種方式?jīng)]有辦法做檢測吭服,沒辦法上報到服務端艇棕,無法協(xié)助我們遠程分析問題北苟。
通過代碼獲取出現(xiàn) ANR 堆棧
class MyApplication : Application() {
private val TAG = "MyApplication"
private var startWorkTimeMillis = 0L
private val mRunnable = Runnable {
// 獲取主線程
val thread = Looper.getMainLooper().thread
val stringBuilder = StringBuilder()
// 打印主線程的堆棧
for (stack in thread.stackTrace) {
stringBuilder.append(stack).append('\n')
}
Log.e("耗時過長", stringBuilder.toString())
}
override fun onCreate() {
super.onCreate()
val handlerThread = HandlerThread("anr")
handlerThread.start()
val stackHandler = Handler(handlerThread.looper)
Looper.getMainLooper().setMessageLogging {
if (it.startsWith(">>>>> Dispatching to Handler")) {
startWorkTimeMillis = System.currentTimeMillis()
stackHandler.removeCallbacks(mRunnable)
stackHandler.postDelayed(mRunnable, 500)
} else if (it.startsWith("<<<<< Finished to Handler")) {
stackHandler.removeCallbacks(mRunnable)
val duration = System.currentTimeMillis() - startWorkTimeMillis
if (duration > 500) {
Log.e("主線程執(zhí)行耗時過長", "$duration 毫秒桃移,$it")
}
}
}
}
}
模擬測試
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button1.setOnClickListener {
Thread.sleep(1000)
}
button2.setOnClickListener {
Thread.sleep(5000)
Thread(Runnable {
throw RuntimeException()
}).start()
}
}
}
測試結果
E/耗時過長: java.lang.Thread.sleep(Native Method)
java.lang.Thread.sleep(Thread.java:373)
java.lang.Thread.sleep(Thread.java:314)
com.taoweiji.handleranalyze.MainActivity$onCreate$1.onClick(MainActivity.kt:18)
android.view.View.performClick(View.java:6597)
android.view.View.performClickInternal(View.java:6574)
android.view.View.access$3100(View.java:778)
android.view.View$PerformClick.run(View.java:25885)
android.os.Handler.handleCallback(Handler.java:873)
android.os.Handler.dispatchMessage(Handler.java:99)
android.os.Looper.loop(Looper.java:193)
android.app.ActivityThread.main(ActivityThread.java:6669)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
E/主線程執(zhí)行耗時過長: 1003 毫秒进泼,<<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {5dddc1b} android.view.View$PerformClick@b1d8dda
總結
通過上述代碼可以獲取ANR绞惦,打印堆棧信息济蝉,但是并不能獲取所有情況的ANR王滤,比如CPU計算資源耗盡導致整個APP所有線程都卡死雁乡,這種情況下是解決不了。