ANR异常

1 ANR定义

ANR即应用无响应(application not responding),一般会弹出无响应提示对话框,某些系统可能会屏蔽掉弹框功能。ANR的日志存放在/data/anr/下,此日志只记录VM层面的日志,不会记录Native的行为日志;

1.1 ANR满足条件与情况

ANR的产生需要同时满足三个条件:

  • 主线程:只有应用程序进程的主线程响应超时才会产生ANR;
  • 超时时间:产生ANR的上下文不同,超时时间也不同,但只要超过这个时间上限没有响应就会产生ANR;
  • 输入事件/特定操作:输入事件是指按键、触屏等设备输入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各个函数调用。

产生ANR的上下文不同,导致ANR原因也不同,主要有以下三种情况:

  • InputDispatchingTimeout
    主线程对5秒内无法响应屏幕触摸事件或键盘输入事件;

  • BroadcastQueueTimeout
    BroadcastReceiver的onReceive()函数运行在主线程中,在特定的时间(10秒)内无法完成处理,后台进程超时时间是60秒。

  • ServiceTimeout
    比较少出现的一种类型,原因是Service的各个生命周期函数在特定时间(20秒)内无法完成处理。

2 ANR原理

2.1 ANR的产生流程

具体代码分析参考链接[3]
ANR 产生的流程如下所示:


ANR异常_第1张图片
ANR产生流程.png
  • 判断窗口是否可以分发事件
    • 窗口处于 paused 状态, 则无法分发事件
    • 如果窗口的连接没有注册, 则无法分发事件
    • 窗口连接失效, 则无法分发事件
    • 窗口的事件待处理队列 outboundQueue 已满, 则无法分发事件
    • 若为 Key Event, 窗口的 outboundQueue 或 waitQueue 有数据, 则无法分发事件
    • 若非 Key Event, 窗口 waitQueue 非空 且 头事件分发超时 500ms, 则无法分发事件
  • 若不能分发, 则安放定时器, 默认 5s 之后重试
    • 在此期间若是分发成功之后会重置定时器
  • 重试的时候, 若仍然分发失败, 则调用 onANRLocked 弹出 ANR 弹窗

2.2 ANR的弹出

具体代码分析参考链接[3]
产生ANR对话框流程:

  1. onANRLocked 从 Native 层回溯到 Java 层的 IMS
  2. IMS 交由 AMS 处理 ANR
  3. AMS 处理 ANR
    • 在 ServiceThread 线程 dump ANR 信息
    • 在 UIThread 线程弹出 AppErrorDialog

2.3 ANR的信息收集

这部分代码位于ActivityManagerService类中:

final void appNotResponding(ProcessRecord app, ActivityRecord activity,
            ActivityRecord parent, boolean aboveSystem, final String annotation) {
        ArrayList firstPids = new ArrayList(5);
        SparseArray lastPids = new SparseArray(20);
        // mController是IActivityController接口的实例,是为Monkey测试程序预留的,默认为null
        if (mController != null) {
            try {
                // 0 == continue, -1 = kill process immediately
                int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation);
                if (res < 0 && app.pid != MY_PID) {
                    app.kill("anr", true);
                }
            } catch (RemoteException e) {
                mController = null;
                Watchdog.getInstance().setActivityController(null);
            }
        }
        // 更新CPU状态信息
        long anrTime = SystemClock.uptimeMillis();
        if (MONITOR_CPU_USAGE) {
            updateCpuStatsNow();
        }
        synchronized (this) {
            // 某些特定情况下忽略本次ANR,比如系统关机,比如该进程已经处于anr状态或者crash状态
            if (mShuttingDown) {
                Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
                return;
            } else if (app.notResponding) {
                Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
                return;
            } else if (app.crashing) {
                Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
                return;
            }
            // 为了防止多次对相同app的anr执行重复代码,在此处标注记录,属于上面的特定情况种的一种
            app.notResponding = true;
            // 记录ANR信息到Event Log中
            EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
                    app.processName, app.info.flags, annotation);
            // 添加当前app到firstpids列表中
            firstPids.add(app.pid);
            // 如果可能添加父进程到firstpids列表种
            int parentPid = app.pid;
            if (parent != null && parent.app != null && parent.app.pid > 0) parentPid = parent.app.pid;
            if (parentPid != app.pid) firstPids.add(parentPid);
            if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
            // 添加所有进程到firstpids中
            for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
                ProcessRecord r = mLruProcesses.get(i);
                if (r != null && r.thread != null) {
                    int pid = r.pid;
                    if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
                        if (r.persistent) {
                            firstPids.add(pid);
                        } else {
                            lastPids.put(pid, Boolean.TRUE);
                        }
                    }
                }
            }
        }
        // 将ANR信息存在info变量中,后续打印到LOGCAT,这部分的信息会以ActivityManager为Tag打印出来,包含了ANR的进程,出现原因以及当时的CPU状态,这些对分析ANR是非常重要的信息
        StringBuilder info = new StringBuilder();
        info.setLength(0);
        info.append("ANR in ").append(app.processName);
        if (activity != null && activity.shortComponentName != null) {
            info.append(" (").append(activity.shortComponentName).append(")");
        }
        info.append("\n");
        info.append("PID: ").append(app.pid).append("\n");
        if (annotation != null) {
            info.append("Reason: ").append(annotation).append("\n");
        }
        if (parent != null && parent != activity) {
            info.append("Parent: ").append(parent.shortComponentName).append("\n");
        }
        final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
        // 将ANR信息输出到traces文件
        File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
                NATIVE_STACKS_OF_INTEREST);
        String cpuInfo = null;
        if (MONITOR_CPU_USAGE) {
            updateCpuStatsNow();
            synchronized (mProcessCpuTracker) {
                cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);
            }
            info.append(processCpuTracker.printCurrentLoad());
            info.append(cpuInfo);
        }
        info.append(processCpuTracker.printCurrentState(anrTime));
        // 输出到logcat的语句
        Slog.e(TAG, info.toString());
        // 如果traces文件未创建,则只记录当前进程trace并且发送QUIT信号到进程
        if (tracesFile == null) {
            // There is no trace file, so dump (only) the alleged culprit's threads to the log
            Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
        }
        // 将ANR信息处处到DropBox目录下,也就是说除了traces文件还会有一个dropbox文件用于记录ANR
        addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
                cpuInfo, tracesFile, null);
        //...
        synchronized (this) {
            mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
            if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
                app.kill("bg anr", true);
                return;
            }
            // Set the app's notResponding state, and look up the errorReportReceiver
            makeAppNotRespondingLocked(app,
                    activity != null ? activity.shortComponentName : null,
                    annotation != null ? "ANR " + annotation : "ANR",
                    info.toString());
            // 发送SHOW_NOT_RESPONDING_MSG,准备显示ANR对话框
            Message msg = Message.obtain();
            HashMap map = new HashMap();
            msg.what = SHOW_NOT_RESPONDING_MSG;
            msg.obj = map;
            msg.arg1 = aboveSystem ? 1 : 0;
            map.put("app", app);
            if (activity != null) {
                map.put("activity", activity);
            }
            mUiHandler.sendMessage(msg);
        }
    }

当发生ANR时, 会按顺序依次执行:

  1. 输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息;
  2. 收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时;
  3. 输出当前各个进程的CPU使用情况以及CPU负载情况;
  4. 将traces文件和 CPU使用情况信息保存到dropbox,即data/system/dropbox目录;
  5. 根据进程类型,来决定直接后台杀掉,还是弹框告知用户;、

ANR输出重要进程的traces信息,这些进程包含:

  • firstPids队列:第一个是ANR进程,第二个是system_server,剩余是所有persistent进程;
  • Native队列:是指/system/bin/目录的mediaserver,sdcard 以及surfaceflinger进程;
  • lastPids队列:是指mLruProcesses中的不属于firstPids的所有进程;

具体详细过程参看:参考链接[4]

3 产生ANR常见原因及解决方案

  • 主线程阻塞
    解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务,避免主线程调用join(),sleep()或wait()方法;应用程序的UI线程等待子线程释放某个锁,从而无法处理用户的输入。
  • I/O操作
    解决方法:尽量避免在主线程文件读取或数据库操作、不要滥用SharePreferences。
  • 频繁刷新UI
    解决办法:避免频繁实时刷新UI,如下载进度实时更新,可以进行采样方式降低更新频率;
  • 各大组件ANR
    解决办法:BroadCastReceiver不要进行复杂操作的操作,可以在onReceive()方法中启动一个Service来处理;避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

4 ANR分析

ANR可以生成traces.txt以及DropBox目录下的ANR历史记录,因此可以考虑阅读该文件来分析,除此之外我们还有DDMS帮助我们分析ANR,这两种方式实际上是大同小异的,只是应用的场景不同。在讲ANR分析之前,先看看Java应用的分析。

4.1 Java线程调用分析方法

为什么要在讲Android的ANR分析方法之前提到Java的分析方法呢,因为需要在解释ANR之前稍微介绍一下线程状态的概念,以便后面做叙述,同时也可以更方便的带入分析的方法。JDK中有一个关键命令可以帮助我们分析和调试Java应用——jstack,命令的使用方法是:

jstack {pid}

其中pid可以通过jps命令获得,jps命令会列出当前系统中运行的所有Java虚拟机进程,比如这样:

> jps
7266 Test
7267 Jps

上面的命令可以发现系统中目前有7266和7267两个Java虚拟机进程,此时如果想知道当前Test进程的情况,就可以通过jstack命令来查看。jstack命令的输出结果很简单,它会打印出该进程中所有线程的状态以及调用关系,甚至会给出一些简单的分析结果:

2016-06-20 14:01:54
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.71-b01 mixed mode):
"Attach Listener" daemon prio=5 tid=0x00007fde7385d800 nid=0x3507 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" prio=5 tid=0x00007fde73873000 nid=0x1303 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Thread-1" prio=5 tid=0x00007fde73872800 nid=0x4a03 waiting for monitor entry [0x000000011cb30000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at Test.rightLeft(Test.java:48)
    - waiting to lock <0x00000007d56540a0> (a Test$LeftObject)
    - locked <0x00000007d5656180> (a Test$RightObject)
    at Test$2.run(Test.java:68)
    at java.lang.Thread.run(Thread.java:745)
"Thread-0" prio=5 tid=0x00007fde73871800 nid=0x4803 waiting for monitor entry [0x000000011ca2d000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at Test.leftRight(Test.java:34)
    - waiting to lock <0x00000007d5656180> (a Test$RightObject)
    - locked <0x00000007d56540a0> (a Test$LeftObject)
    at Test$1.run(Test.java:60)
    at java.lang.Thread.run(Thread.java:745)
"Service Thread" daemon prio=5 tid=0x00007fde73821000 nid=0x4403 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" daemon prio=5 tid=0x00007fde73035000 nid=0x4203 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" daemon prio=5 tid=0x00007fde7381e000 nid=0x4003 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=5 tid=0x00007fde7481d800 nid=0x300f runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=5 tid=0x00007fde73010000 nid=0x2d03 in Object.wait() [0x000000011aacb000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007d55047f8> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    - locked <0x00000007d55047f8> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" daemon prio=5 tid=0x00007fde7300f000 nid=0x2b03 in Object.wait() [0x000000011a9c8000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007d5504410> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:503)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
    - locked <0x00000007d5504410> (a java.lang.ref.Reference$Lock)
"VM Thread" prio=5 tid=0x00007fde7300c800 nid=0x2903 runnable
"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fde74000800 nid=0x2103 runnable
"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fde7400c000 nid=0x2303 runnable
"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fde7400c800 nid=0x2503 runnable
"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fde7400d000 nid=0x2703 runnable
"VM Periodic Task Thread" prio=5 tid=0x00007fde7481e000 nid=0x4603 waiting on condition
JNI global references: 110
 
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007fde73818ab8 (object 0x00000007d56540a0, a Test$LeftObject),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fde73819f58 (object 0x00000007d5656180, a Test$RightObject),
  which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
    at Test.rightLeft(Test.java:48)
    - waiting to lock <0x00000007d56540a0> (a Test$LeftObject)
    - locked <0x00000007d5656180> (a Test$RightObject)
    at Test$2.run(Test.java:68)
    at java.lang.Thread.run(Thread.java:745)
"Thread-0":
    at Test.leftRight(Test.java:34)
    - waiting to lock <0x00000007d5656180> (a Test$RightObject)
    - locked <0x00000007d56540a0> (a Test$LeftObject)
    at Test$1.run(Test.java:60)
    at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
4.1.1 Thread基础信息

输出种包含所有的线程,取其中的一条:

"Thread-1" prio=5 tid=0x00007fde73872800 nid=0x4a03 waiting for monitor entry [0x000000011cb30000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at Test.rightLeft(Test.java:48)
    - waiting to lock <0x00000007d56540a0> (a Test$LeftObject)
    - locked <0x00000007d5656180> (a Test$RightObject)
    at Test$2.run(Test.java:68)
    at java.lang.Thread.run(Thread.java:745)
  1. "Thread-1" prio=5 tid=0x00007fde73872800 nid=0x4a03 waiting for monitor entry [0x000000011cb30000]
    首先描述了线程名是『Thread-1』,然后prio=5表示优先级,tid表示的是线程id,nid表示native层的线程id,他们的值实际都是一个地址,后续给出了对于线程状态的描述,waiting for monitor entry [0x000000011cb30000]这里表示该线程目前处于一个等待进入临界区状态,该临界区的地址是[0x000000011cb30000]
    这里对线程的描述多种多样,简单解释下上面出现的几种状态
  • waiting on condition(等待某个事件出现)
  • waiting for monitor entry(等待进入临界区)
  • runnable(正在运行)
  • in Object.wait(处于等待状态)
  1. java.lang.Thread.State: BLOCKED (on object monitor)
    这段是描述线程状态,我们知道Java的6种线程状态定义在Thread.java中:
//Thread.java
public class Thread implements Runnable {
    ...
    public enum State {
        /**
         * The thread has been created, but has never been started.
         */
        NEW,
        /**
         * The thread may be run.
         */
        RUNNABLE,
        /**
         * The thread is blocked and waiting for a lock.
         */
        BLOCKED,
        /**
         * The thread is waiting.
         */
        WAITING,
        /**
         * The thread is waiting for a specified amount of time.
         */
        TIMED_WAITING,
        /**
         * The thread has been terminated.
         */
        TERMINATED
    }
    ...
}
  1. at xxx 调用栈
at Test.rightLeft(Test.java:48)
- waiting to lock <0x00000007d56540a0> (a Test$LeftObject)
- locked <0x00000007d5656180> (a Test$RightObject)
at Test$2.run(Test.java:68)
at java.lang.Thread.run(Thread.java:745)

这段线程的调用栈,可以看到线程在我们执行jstack命令的时候运行到Test.java的48行,而在68行到48行之间,线程对一个TestLeftObject锁。

4.1.2 jstack分析信息
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007fde73818ab8 (object 0x00000007d56540a0, a Test$LeftObject),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fde73819f58 (object 0x00000007d5656180, a Test$RightObject),
  which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
    at Test.rightLeft(Test.java:48)
    - waiting to lock <0x00000007d56540a0> (a Test$LeftObject)
    - locked <0x00000007d5656180> (a Test$RightObject)
    at Test$2.run(Test.java:68)
    at java.lang.Thread.run(Thread.java:745)
"Thread-0":
    at Test.leftRight(Test.java:34)
    - waiting to lock <0x00000007d5656180> (a Test$RightObject)
    - locked <0x00000007d56540a0> (a Test$LeftObject)
    at Test$1.run(Test.java:60)
    at java.lang.Thread.run(Thread.java:745)

说明中的信息很详细,它认为我们的应用出现了一个Java层的死锁,即Thread-1等待一个被Thread-0持有的锁,Thread-0等待一个被Thread-1持有的锁,实际上的确也是这样,最后再来看看源代码是不是这么回事。

public class Test {
    public static class LeftObject {     
    }
     
    public static class RightObject {
    }
    private Object leftLock = new LeftObject();
    private Object rightLock = new RightObject();
     
    public void leftRight() {
        synchronized (leftLock) {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (rightLock) {
                System.out.println("leftRight");
            }
        }
    }
    public void rightLeft() {
        synchronized (rightLock) {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (leftLock) {
                System.out.println("leftRight");
            }
        }
    }
    public static void main(String[] args) {
        final Test test = new Test();
         
        new Thread(new Runnable() {          
            @Override
            public void run() {
                test.leftRight();
            }
        }).start();
         
        new Thread(new Runnable() {   
            @Override
            public void run() {
                test.rightLeft();
            }
        }).start();
    }
}

4.2 DDMS分析ANR问题

有了上面的基础,再来看看Android的ANR如何分析,Android的DDMS工具其实已经给我们提供了一个类似于jstack命令的玩意,可以很好的让我们调试的时候实时查看Android虚拟机的线程状况。

4.2.1 使用DDMS—Update Threads工具

使用DDMS的Update Threads工具可以分为如下几步:
1)选择需要查看的进程;
2)点击Update Threads按钮;
3)在Threads视图查看该进程的所有线程状态;


ANR异常_第2张图片
选择需要查看的进程并点击更新线程按钮.png

ANR异常_第3张图片
查看线程状态.png
4.2.2 阅读Update Threads的输出

Update Threads工具可以输出当前进程的所有线程的状态,上半部分是线程列表,选中其中一条下半部分将展现出该线程当前的调用栈。
1. 线程列表
上半部分种的线程列表分为好几列,其中ID栏表示的序号,其中带有『*』标志的是守护线程,Tid是线程号,Status表示线程状态,utime表示执行用户代码的累计时间,stime表示执行系统代码的累计时间,Name表示的是线程名字。实际上utime还有stime他们具体的含义我也不是太清楚,不过这不影响我们分析问题,这里需要特别注意的是main线程啦,还有线程状态。

2. main线程
main线程就是应用主线程啦,点击上半部分线程列表选中main线程,我们可以发现,绝大多数不操作应用的情况下,调用栈应该是如下样式的:

ANR异常_第4张图片
main线程状态.png

这是一个空闲等待状态,等待其他线程或者进程发送消息到主线程,再由主线程处理相应事件。如果主线程在执行过程中出现了问题,就会出现ANR,结合下面关于线程状态的分析可以知道如果主线程的状态是MONITOR一般肯定就是出现了ANR了。

3. 线程状态
我们刚刚在分心Java线程状态时明明只有6个状态,但是现在Android虚拟机给出的线程状态超出了这6个的限制,这也是需要在源码中寻找答案的,VMThread.java类中有这么一段代码:

/**
 * Holds a mapping from native Thread statuses to Java one. Required for
 * translating back the result of getStatus().
 */
static final Thread.State[] STATE_MAP = new Thread.State[] {
    Thread.State.TERMINATED,     // ZOMBIE
    Thread.State.RUNNABLE,       // RUNNING
    Thread.State.TIMED_WAITING,  // TIMED_WAIT
    Thread.State.BLOCKED,        // MONITOR
    Thread.State.WAITING,        // WAIT
    Thread.State.NEW,            // INITIALIZING
    Thread.State.NEW,            // STARTING
    Thread.State.RUNNABLE,       // NATIVE
    Thread.State.WAITING,        // VMWAIT
    Thread.State.RUNNABLE        // SUSPENDED
};

而且,native层的Thread.cpp中还有一段代码:

const char* dvmGetThreadStatusStr(ThreadStatus status) {
    switch (status) {
        case THREAD_ZOMBIE:         return "ZOMBIE";
        case THREAD_RUNNING:        return "RUNNABLE";
        case THREAD_TIMED_WAIT:     return "TIMED_WAIT";
        case THREAD_MONITOR:        return "MONITOR";
        case THREAD_WAIT:           return "WAIT";
        case THREAD_INITIALIZING:   return "INITIALIZING";
        case THREAD_STARTING:       return "STARTING";
        case THREAD_NATIVE:         return "NATIVE";
        case THREAD_VMWAIT:         return "VMWAIT";
        case THREAD_SUSPENDED:      return "SUSPENDED";
        default:                    return "UNKNOWN";
    }
}

由此我们可以看到Android虚拟机中有10种线程状态,其对应关系如下:

Thread.java中定义的状态 Thread.cpp中定义的状态 说明
TERMINATED ZOMBIE 线程死亡,终止运行
RUNNABLE RUNNING/RUNNABLE 线程可运行或正在运行
TIMED_WAITING TIMED_WAIT 执行了带有超时参数的wait、sleep或join函数
BLOCKED MONITOR 线程阻塞,等待获取对象锁
WAITING WAIT 执行了无超时参数的wait函数
NEW INITIALIZING 新建,正在初始化,为其分配资源
NEW STARTING 新建,正在启动
RUNNABLE NATIVE 正在执行JNI本地函数
WAITING VMWAIT 正在等待VM资源
RUNNABLE SUSPENDED 线程暂停,通常是由于GC或debug被暂停
UNKNOWN 未知状态
4.2.3 举个例子
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button mBtn = (Button) findViewById(R.id.button);
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                print();
            }
        });
    }
    public void print() {
        BufferedReader bufferedReader = null;
        String tmp = null;
        try {
            bufferedReader = new BufferedReader(new FileReader(new File(Environment.getExternalStorageDirectory() + "/test")));
            while ((tmp = bufferedReader.readLine()) != null) {
                Log.i("wangchen", tmp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

简单的一个Activity,点击按钮时将读取文件内容并进行打印到logcat,本身没有什么大问题,但是在该Activity的按钮被点击时却出现了未响应的情况。
通过DDMS,我们查看到当前未响应时主线程一直处于如下调用状态:

at android.util.Log.println_native(Native Method) 
at android.util.Log.i(Log.java:173) 
at com.example.wangchen.androitest.MainActivity.print(MainActivity.java:37) 
at com.example.wangchen.androitest.MainActivity$1.onClick(MainActivity.java:26) 
at android.view.View.performClick(View.java:4446) 
at android.view.View$PerformClick.run(View.java:18480) 
at android.os.Handler.handleCallback(Handler.java:733) 
at android.os.Handler.dispatchMessage(Handler.java:95) 
at android.os.Looper.loop(Looper.java:136) 
at android.app.ActivityThread.main(ActivityThread.java:5314) 
at java.lang.reflect.Method.invokeNative(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:515) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:864) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:680) 
at dalvik.system.NativeStart.main(Native Method)

由上面对主线程的分析可以知道,正常情况下主线程应当是处于空闲等待状态,如果长时间处于处理某一个任务时就会导致其他被发送到主线程的事件无法被及时处理,导致ANR,实际上这里的test文件有30M,完全打印是非常耗时的,导致ANR也就理所当然了,所以对于文件读写操作还是建议在非主线程操作。

4.3 trace文件分析

在开发调试过程中遇到ANR问题大多是可以通过DDMS方法来分析问题原因的,但是所有的ANR问题不一定会在开发阶段出现,如果在测试或者发版之后出现了ANR问题,那么就需要通过traces文件来分析。根据之前的分析我们知道,traces文件位于/data/anr目录下,即便是没有root的手机也是可以通过adb命令将该文件pull出来,一个traces文件中包含了出现ANR时当前系统的所有活动进程的情况,其中每一个进程会包含所有线程的情况,因此文件的内容量往往比较大。但是一般造成ANR的进程会被记录在头一段,因此尽可能详细的分析头一段进程是解析traces文件的重要方法。

4.3.1 trace文件内容
adb pull /data/anr/traces.txt ./mytraces.txt

通过命令获得trace文件,内容如下:

// 进程头部信息:进程的pid号,当前时间,进程名称
----- pid 4280 at 2016-05-30 00:17:13 -----
Cmd line: com.quicinc.cne.CNEService

// 进程资源状态信息
Build fingerprint: 'Xiaomi/virgo/virgo:6.0.1/MMB29M/6.3.21:user/release-keys'
ABI: 'arm'
Build type: optimized
Zygote loaded classes=4124 post zygote classes=18
Intern table: 51434 strong; 17 weak
JNI: CheckJNI is off; globals=286 (plus 277 weak)
Libraries: /system/lib/libandroid.so /system/lib/libcompiler_rt.so /system/lib/libjavacrypto.so /system/lib/libjnigraphics.so /system/lib/libmedia_jni.so /system/lib/libmiuinative.so /system/lib/libsechook.so /system/lib/libwebviewchromium_loader.so libjavacore.so (9)
Heap: 50% free, 16MB/33MB; 33690 objects
Dumping cumulative Gc timings
Total number of allocations 33690
Total bytes allocated 16MB
Total bytes freed 0B
Free memory 16MB
Free memory until GC 16MB
Free memory until OOME 111MB
Total memory 33MB
Max memory 128MB
Zygote space size 1624KB
Total mutator paused time: 0
Total time waiting for GC to complete: 0
Total GC count: 0
Total GC time: 0
Total blocking GC count: 0
Total blocking GC time: 0
suspend all histogram:  Sum: 102us 99% C.I. 3us-25us Avg: 8.500us Max: 25us

// 每条线程的信息
DALVIK THREADS (10):
"Signal Catcher" daemon prio=5 tid=2 Runnable
  | group="system" sCount=0 dsCount=0 obj=0x12c470a0 self=0xaeb8b000
  | sysTid=4319 nice=0 cgrp=default sched=0/0 handle=0xb424f930
  | state=R schedstat=( 111053493 34114006 33 ) utm=6 stm=5 core=0 HZ=100
  | stack=0xb4153000-0xb4155000 stackSize=1014KB
  | held mutexes= "mutator lock"(shared held)
  native: #00 pc 00370e89  /system/lib/libart.so (art::DumpNativeStack(std::__1::basic_ostream >&, int, char const*, art::ArtMethod*, void*)+160)
  native: #01 pc 003504f7  /system/lib/libart.so (art::Thread::Dump(std::__1::basic_ostream >&) const+150)
  native: #02 pc 0035a3fb  /system/lib/libart.so (art::DumpCheckpoint::Run(art::Thread*)+442)
  native: #03 pc 0035afb9  /system/lib/libart.so (art::ThreadList::RunCheckpoint(art::Closure*)+212)
  native: #04 pc 0035b4e7  /system/lib/libart.so (art::ThreadList::Dump(std::__1::basic_ostream >&)+142)
  native: #05 pc 0035bbf7  /system/lib/libart.so (art::ThreadList::DumpForSigQuit(std::__1::basic_ostream >&)+334)
  native: #06 pc 00333d3f  /system/lib/libart.so (art::Runtime::DumpForSigQuit(std::__1::basic_ostream >&)+74)
  native: #07 pc 0033b0a5  /system/lib/libart.so (art::SignalCatcher::HandleSigQuit()+928)
  native: #08 pc 0033b989  /system/lib/libart.so (art::SignalCatcher::Run(void*)+340)
  native: #09 pc 0003f54f  /system/lib/libc.so (__pthread_start(void*)+30)
  native: #10 pc 00019c2f  /system/lib/libc.so (__start_thread+6)
  (no managed stack frames)
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x7541b3c0 self=0xb4cf6500
  | sysTid=4280 nice=-1 cgrp=default sched=0/0 handle=0xb6f5cb34
  | state=S schedstat=( 52155108 81807757 159 ) utm=2 stm=3 core=0 HZ=100
  | stack=0xbe121000-0xbe123000 stackSize=8MB
  | held mutexes=
  native: #00 pc 00040984  /system/lib/libc.so (__epoll_pwait+20)
  native: #01 pc 00019f5b  /system/lib/libc.so (epoll_pwait+26)
  native: #02 pc 00019f69  /system/lib/libc.so (epoll_wait+6)
  native: #03 pc 00012c57  /system/lib/libutils.so (android::Looper::pollInner(int)+102)
  native: #04 pc 00012ed3  /system/lib/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+130)
  native: #05 pc 00082bed  /system/lib/libandroid_runtime.so (android::NativeMessageQueue::pollOnce(_JNIEnv*, _jobject*, int)+22)
  native: #06 pc 0000055d  /data/dalvik-cache/arm/system@[email protected] (Java_android_os_MessageQueue_nativePollOnce__JI+96)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:323)
  at android.os.Looper.loop(Looper.java:135)
  at android.app.ActivityThread.main(ActivityThread.java:5435)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:735)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625)

// ...
----- end 4280 -----
4.3.2 举个例子

一般traces.txt日志输出格式如下,本实例是在主线程中强行Sleep导致的ANR日志:

DALVIKTHREADS :
(mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)  
"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x73f11000 self=0xf3c25800
  | sysTid=2957 nice=0 cgrp=default sched=0/0 handle=0xf7770ea0
  | state=S schedstat=( 107710942 40533261 131 ) utm=4 stm=6 core=2 HZ=100
  | stack=0xff49d000-0xff49f000 stackSize=8MB
  | heldmutexes=
  atjava.lang.Thread.sleep!(Native method)
  - sleepingon <0x31fd6f5d> (a java.lang.Object)
  atjava.lang.Thread.sleep(Thread.java:1031)
  - locked <0x31fd6f5d> (a java.lang.Object)
  atjava.lang.Thread.sleep(Thread.java:985)
  atcom.sunny.demo.MainActivity.startMethod(MainActivity.java:21)
  atjava.lang.reflect.Method.invoke!(Native method)
  atjava.lang.reflect.Method.invoke(Method.java:372)
  atandroid.view.View$1.onClick(View.java:4015)
  atandroid.view.View.performClick(View.java:4780)
  atandroid.view.View$PerformClick.run(View.java:19866)
  atandroid.os.Handler.handleCallback(Handler.java:739)
  atandroid.os.Handler.dispatchMessage(Handler.java:95)
  atandroid.os.Looper.loop(Looper.java:135)
  atandroid.app.ActivityThread.main(ActivityThread.java:5254)
  atjava.lang.reflect.Method.invoke!(Native method)
  atjava.lang.reflect.Method.invoke(Method.java:372)
  atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
  • 第1行:固定头,指明下面都是当前运行的dvm thread:“DALVIK THREADS”;
  • 第2行:输出的是该进程中各线程互斥量的值,有些手机上面可能没有这一行日志信息;
  • 第3行:输出的是线程名字(“main”),线程优先级(“prio=5”),线程id(“tid=1”),线程状态(Sleeping),比较常见的状态还有Native、Waiting;
  • 第4行:分别是线程所处的线程组 (“main”),线程被正常挂起的次处(“sCount=1”),线程因调试而挂起次数(”dsCount=0“),当前线程所关联的java线程对象(”obj=0x73f11000“)以及该线程本身的地址(“0xf3c25800”);
  • 第5行:显示线程调度信息,分别是该线程在linux系统下得本地线程id (“ sysTid=2957”),线程的调度有优先级(“nice=0”),调度策略(sched=0/0),优先组属(“cgrp=default”)以及 处理函数地址(“handle=0xf7770ea0”);
  • 第6行:显示更多该线程当前上下文,分别是调度状态(从 /proc/[pid]/task/[tid]/schedstat读出)(“schedstat=( 107710942 40533261 131 )”),以及该线程运行信息 ,它们是线程用户态下使用的时间值(单位是jiffies)(“utm=4”), 内核态下得调度时间值(“stm=6”),以及最后运行该线程的cpu标识(“core=2”);
  • 第7行:表示线程栈的地址(“stack=0xff49d000-0xff49f000”)以及栈大小(“stackSize=8MB”);
  • 后面是线程的调用栈信息,也是分析ANR的核心所在。
    分析调试栈发现:at com.sunny.demo.MainActivity.startMethod(MainActivity.java:21) 很容易就可以定位到我们的问题所在。

5 ANR 检测

5.1 StrictMode

严格模式StrictMode是Android SDK提供的一个用来检测代码中是否存在违规操作的工具类,StrictMode主要检测两大类问题:
1. 线程策略ThreadPolicy

  • detectCustomSlowCalls: 检测自定义耗时操作。
  • detectDiskReads: 检测是否存在磁盘读取操作。
  • detectDiskWrites: 检测是否存在磁盘写入操作。
  • detectNetWork: 检测是否存在网络操作。
    2. 虚拟机策略VmPolicy
  • detectActivityLeaks: 检测是否存在Activity泄漏。
  • detectLeakedClosableObjects: 检测是否存在未关闭的Closable对象泄漏。
  • detectLeakedSqlLiteObjects: 检测是否存在Sqlite对象泄漏。
  • setClassInstanceLimit: 检测类实例个数是否超过限制。

可以看到,其中ThreadPolicy可以用来检测可能存在的主线程耗时操作,解决这些检测到问题能搞减少应用发生ANR的概率。注意:只能在Debug模式下使用它。在应用初始化的地方,例如Application或者MainActivity类的onCreate方法中执行如下代码即可:

@Override
protected void onCreate(Bundle savedInstanceState) {
  if (BuildConfig.DEBUG) {
    //开启线程模式所有检测策略
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
    //开启虚拟机模式所有检测策略
    StrictMode.setVmPolicy(new VmPolicy.Builder().detectAll().penaltyLog().build());
  }
  super.onCreate(savedInstanceState);
}

上面的初始化代码调用penaltyLog表示在Logcat中打印日志,调用detectAll方法表示启动所有的检测策略,可以根据需求只开启部分检测功能。

5.2 BlockCanary

BlockCanary是一个非入侵式的性能监控函数库,它的用法和LeakCanary类似,用来监控应用主线程卡顿。它的基本原理是利用主线程的消息队列处理机制,通过对比消息分发开始和结束时间点来判断是否超过设定时间。
在build.gradle中添加依赖

dependencies {
  compile 'com.github.moduth:blockcanary-android:1.2.1'

  //仅在debug包启用BlockCanary进行卡顿监控和提示
  debugCompile 'com.github.moduth:blockcanary-android:1.2.1'
  releaseCompile 'com.github.moduth:blockcanary-no-op:1.2.1'
}
public class DemoApplication extends Application {
  @Override
  public void onCreate() {
    //在主进程初始化调用
    BlockCanary.install(this, new AppBlockCanaryContext()).start();
  }
}

public class AppBlockCanaryContext extends BlockCanaryContext {
  //实现各种上下文,包括应用标识符,用户uid,网络类型,卡慢判断阈值,Log保存位置等
}

参考链接:

[1] 安卓应用无响应,你真的了解吗?
[2] Android ANR的产生与分析 ★
[3] Android系统架构 —— IMS的ANR 产生流程 ★★
[4] 理解Android ANR的信息收集过程 ★
[5] Android ANR:原理分析及解决办法
[6] Android应用ANR分析 ★
[7] ANR产生的原因及其定位分析 ★
[8] ANR产生的原因及定位分析 √

你可能感兴趣的:(ANR异常)