Android:基于 Handler、Looper 实现 ANR 监控,获取堆栈

在上一篇文章《Android源码剖析:基于 Handler、Looper 实现拦截全局崩溃、监控ANR等》介绍了如何实现简单的ANR监控,判断是否出现了ANR,但是没有介绍如何分析,这篇文章将会详细介绍如何分析解决ANR问题。

触发 ANR
  1. 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等)
  2. BroadcastReceiver在10s内无法结束
  3. Service在特定的时间内无法处理完成
检测是否存在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("主线程执行耗时过长","$duration 毫秒,$it")
                }
            }
        }
    }
}

通过上述代码可以检测是否执行耗时过长,当出现ANR的时候,ANR执行超过5秒,系统会把堆栈打印在/data/anr/traces.txt

获取trace.txt 文件
adb shell cat /data/anr/traces.txt > d:/traces.txt

但是这种方式没有办法做检测,没办法上报到服务端,无法协助我们远程分析问题。

通过代码获取出现 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("主线程执行耗时过长", "$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/主线程执行耗时过长: 1003 毫秒,<<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {5dddc1b} android.view.View$PerformClick@b1d8dda
总结

通过上述代码可以获取ANR,打印堆栈信息,但是并不能获取所有情况的ANR,比如CPU计算资源耗尽导致整个APP所有线程都卡死,这种情况下是解决不了。

推荐阅读更多精彩内容