Android:基于 Handler、Looper 实现 ANR
2019-10-19 本文已影响0人
ImWiki
在上一篇文章《Android源码剖析:基于 Handler、Looper 实现拦截全局崩溃、监控ANR等》介绍了如何实现简单的ANR监控,判断是否出现了ANR,但是没有介绍如何分析,这篇文章将会详细介绍如何分析解决ANR问题。
触发 ANR
- 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等)
- BroadcastReceiver在10s内无法结束
- 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所有线程都卡死,这种情况下是解决不了。