处理ANR,以及UI不顺畅的问题,无非两种方式:1. 分析Dump文件,2. BlockCanary。
首先应该想到用BlockCanary来发现问题。如果问题好发现的话,先用BlockCanary会省去很多时间和精力。
1. BlockCanary原理
如果你事先了解Android UI主线程的运行机制, 那BlockCanary的原理其实很简单。
Android UI运行都是靠事件驱动的, 所有的UI事件都要事先发到MessageQueue。而Looper类的loop()方法以死循环的方式执行发过来的UI事件。
下面是简化后的代码,有没有注意到系统在执行UI事件的时候,前后都打了Log. 这个Log在你自己运行Debug app的时候,却没有看到log的输出。那是因为系统把log输出默认关掉了。再看下一段代码, 你可以在ActivityThread的main方法里找到。默认是关掉执行UI事件log输出的。而BlockCanary就是打开log开关,然后利用log输出的时间,再分析ANR和UI不流畅的原因。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
//loop()方法以死循环的方式执行发过来的UI事件。
for (;;) {
Message msg = queue.next(); // might block, 等下一个UI事件。
// 执行UI事件前先打个Log
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// 执行UI事件...
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
// 执行UI事件后打个Log
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
public static void main(String[] args) {
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
}
我的一个Google Pixel手机不会在BlockCanary页面里输出。暂时还没有找到原因。我们不去管它,既然知道了BlockCanary的原理如此简单,甚至可以自己写一个符合我们特殊需求的工具。
2. BlockCanary使用
这里不再讲解按BlockCanary典型的方式。在网上可以看到有些开发者也会遇到BlockCanary不输出有用的信息。
BlockCanary有一个StackSampler类, 你双击Shift键可以找到。当事件执行时间超过指定时间后,StackSampler就会去采样,也就是去输出线程堆栈。。
下面这段代码就会去输出线程堆栈,此时你就可以知道问题的源头发生在哪里。
@Override
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
synchronized (sStackMap) {
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
这段堆栈是因为我在MainActivity的onClick事件里面加了一个死锁。
com.nero.anrstudy.MainActivity.onClick(MainActivity.java:70)
java.lang.reflect.Method.invoke(Native Method)
androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:397)
android.view.View.performClick(View.java:7140)
android.view.View.performClickInternal(View.java:7117)
android.view.View.access$3500(View.java:801)
android.view.View$PerformClick.run(View.java:27351)
android.os.Handler.handleCallback(Handler.java:883)
android.os.Handler.dispatchMessage(Handler.java:100)
android.os.Looper.loop(Looper.java:214)
android.app.ActivityThread.main(ActivityThread.java:7356)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
2. 分析Dump文件
分析dump文件,只能解决ANR的问题,包括死锁,或者其他block的点。至于UI运行不顺畅用BlockCanary最好,dump文件恐怕很难分析出来,而且效率也不高。
分析dump文件首先需要拿到dump文件。
Android 9.0以前可以通过, adb pull /data/anr/trace.txt, 拿到dump文件。 但是之后就不行了。
现在可以用: adb bugreport > dump.txt 把dump内容直接输入到dump.txt文件下面, 这个文件在adb目录下。当然你也可以指定路径和文件名,比如, adb bugreport > D:/log/yourdump.txt
由于dump文件size比较大,有好几十M. 可以用Notepad++打开, 很丝滑。
分析,有以下两个方式可以参考:
1. 搜索app包名,找出所有和app相关的dump信息。再寻找可疑点。甚至也可以找到App Crash的点。
2. 然后搜索“waiting to lock”, 这是死锁的关键信息, 看看是不是形成了循环等待。 比如下面这样:
线程24等待线程12释放锁,而线程12又等待线程24释放锁。形成了循环等待。
TID 24:- waiting to lock <0x41a874a0> (a com.android.server.am.ActivityManagerService) held by tid=12 (android.server.ServerThread)
TID 12: - waiting to lock <0x41a7e2e8> (a java.lang.Object) held by tid=24 (Binder_B)
3. 搜索发生ANR的时间点。看看这个时间点的数据。
4. 查看CPU利用率。CPU利用率很高也会导致UI不顺畅或者ANR。
5. 看看主线程main, 是不是在looper类里,执行等待msg.next()返回; 是的话代表UI线程空闲。其他业务线程正忙。
6. BugReport原理和实战
3.Android Studio调试
用线程快照