Android ANR和UI不顺畅的处理方式

处理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调试

用线程快照

 

你可能感兴趣的:(Android ANR和UI不顺畅的处理方式)