ANR 和 ANR watchdog 源码笔记

ANR 和 ANR watchdog 源码笔记

03 March 2018 Android

ANR,全称 Application Not Responding,指应用程序不能响应用户的操作。
Android UI Thread 中,如果被阻塞时间过长,则无法响应用户的操作,系统会弹窗提示抛出 ANR。
ANR 的话,可能在开发中遇到的频率不如 crash 多...




ANR 和 systemserver 进程


ANR 的检测是由 systemserver 进程上的 AMS「ActivityManager」和 WMS「WindowManager」等系统服务监视的。

一般分为三种情况:

  • Activity 在 UI Thread 5 秒 内无响应。

  • BroadcastReceiver 的消息在 10秒 内没有执行完。

  • Service 在 20 秒内没处理完。




ANR 常见原因


  • UI Thread 进行耗时操作,如 IO 操作。
  • UI Thread 进程 IPC binder 和另一个进程通信,另一个进程长时间不返回结果。
  • UI Thread 和子线程竞争同一个锁,在等待子线程释放锁资源而超时.
  • UI Thread 和子线程死锁。




ANR 检测


  • Android 5.0 以下,检测是否存在 /data/anr/trace.txt 文件。

  • Android 5.0 以上,检测 /data/anr/anr_* 等文件「检测 /data/anr 文件夹内内文件变化」。

  • 如果以上都没有,就得采用类似 ANR watchdog 的策略。


ANR watchdog 的 github 地址:https://github.com/SalomonBrys/ANR-WatchDog

源码思路:一个线程安全的变量,子线程去 post 一个消息到 UI Thread,让 UI Thread 去给这个值 +1。post 的同时,子线程开始计时,5 秒后,去检测这个变量是否 +1,这样就能检测 UI Thread 是否因为 ANR 5 秒内没给这个值加一了。




ANR watchdog 源码笔记


ANRWatchDog

    1. 如果线程没中断,一直循环。
    1. 记录开始 tick,并且向主线程 post message。
    1. 线程睡 5s。
    1. 判断 tick 是不是没变。
    1. 是的话,认为有 ANR,因为 主线程没处理刚才 post 的消息。
    1. 然后生成 ANRError,ANRError 中出 dump 对应的 主线程 Thread stack 信息。
    1. 调用 ANR 回调接口,回传 ANRError,跳出循环。


package com.github.anrwatchdog;

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Salomon BRYS
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

/**
 * A watchdog timer thread that detects when the UI thread has frozen.
 *
 * 自定义的检测 ANR 的 Thread
 *
 * {@link ANRWatchDog#run()}
 * 1. 如果线程没中断,一直循环
 * 2. 记录开始 tick,并且向主线程 post message
 * 3. 线程睡 5s
 * 4. 判断 tick 是不是没变
 * 5. 是的话,认为有 ANR,因为 主线程没处理刚才 post 的消息
 * 6. 然后生成 ANRError,ANRError 中出 dump 对应的 主线程 Thread stack 信息
 * 7. 调用 ANR 回调接口,回传 ANRError,跳出循环
 */
@SuppressWarnings("UnusedDeclaration")
public class ANRWatchDog extends Thread {

    /**
     * ANR 回调接口
     * 返回 ANRError
     */
    public interface ANRListener {
        public void onAppNotResponding(ANRError error);
    }


    /**
     * InterruptedException 回调接口
     */
    public interface InterruptionListener {
        public void onInterrupted(InterruptedException exception);
    }


    /**
     * post message 的间隔时间
     */
    private static final int DEFAULT_ANR_TIMEOUT = 5000;

    /**
     * 默认 ANR 回调接口
     */
    private static final ANRListener DEFAULT_ANR_LISTENER = new ANRListener() {
        @Override
        public void onAppNotResponding(ANRError error) {
            throw error;
        }
    };

    /**
     * 默认 InterruptedException 回调接口
     */
    private static final InterruptionListener DEFAULT_INTERRUPTION_LISTENER
        = new InterruptionListener() {
        @Override
        public void onInterrupted(InterruptedException exception) {
            Log.w("ANRWatchdog", "Interrupted: " + exception.getMessage());
        }
    };

    private ANRListener _anrListener = DEFAULT_ANR_LISTENER;
    private InterruptionListener _interruptionListener = DEFAULT_INTERRUPTION_LISTENER;

    /**
     * 主线程 Handler
     * 时间间隔
     */
    private final Handler _uiHandler = new Handler(Looper.getMainLooper());
    private final int _timeoutInterval;

    private String _namePrefix = "";
    private boolean _logThreadsWithoutStackTrace = false;
    private boolean _ignoreDebugger = false;

    /**
     * 计数器,向主线程 post 消息
     * 被处理的话,_tick 会 + 1
     */
    private volatile int _tick = 0;

    /**
     * 向主线程 post 消息
     */
    private final Runnable _ticker = new Runnable() {
        @Override
        public void run() {
            _tick = (_tick + 1) % Integer.MAX_VALUE;
        }
    };


    /**
     * Constructs a watchdog that checks the ui thread every {@value #DEFAULT_ANR_TIMEOUT}
     * milliseconds
     */
    public ANRWatchDog() {
        this(DEFAULT_ANR_TIMEOUT);
    }


    /**
     * Constructs a watchdog that checks the ui thread every given interval
     *
     * @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread.
     * It is therefore the maximum time the UI may freeze before being reported as ANR.
     */
    public ANRWatchDog(int timeoutInterval) {
        super();
        _timeoutInterval = timeoutInterval;
    }


    /**
     * Sets an interface for when an ANR is detected.
     * If not set, the default behavior is to throw an error and crash the application.
     *
     * @param listener The new listener or null
     * @return itself for chaining.
     */
    public ANRWatchDog setANRListener(ANRListener listener) {
        if (listener == null) {
            _anrListener = DEFAULT_ANR_LISTENER;
        } else {
            _anrListener = listener;
        }
        return this;
    }


    /**
     * Sets an interface for when the watchdog thread is interrupted.
     * If not set, the default behavior is to just log the interruption message.
     *
     * @param listener The new listener or null.
     * @return itself for chaining.
     */
    public ANRWatchDog setInterruptionListener(InterruptionListener listener) {
        if (listener == null) {
            _interruptionListener = DEFAULT_INTERRUPTION_LISTENER;
        } else {
            _interruptionListener = listener;
        }
        return this;
    }


    /**
     * Set the prefix that a thread's name must have for the thread to be reported.
     * Note that the main thread is always reported.
     * Default "".
     *
     * @param prefix The thread name's prefix for a thread to be reported.
     * @return itself for chaining.
     */
    public ANRWatchDog setReportThreadNamePrefix(String prefix) {
        if (prefix == null) {
            prefix = "";
        }
        _namePrefix = prefix;
        return this;
    }


    /**
     * Set that only the main thread will be reported.
     *
     * @return itself for chaining.
     */
    public ANRWatchDog setReportMainThreadOnly() {
        _namePrefix = null;
        return this;
    }


    /**
     * Set that all running threads will be reported,
     * even those from which no stack trace could be extracted.
     * Default false.
     *
     * @param logThreadsWithoutStackTrace Whether or not all running threads should be reported
     * @return itself for chaining.
     */
    public ANRWatchDog setLogThreadsWithoutStackTrace(boolean logThreadsWithoutStackTrace) {
        _logThreadsWithoutStackTrace = logThreadsWithoutStackTrace;
        return this;
    }


    /**
     * Set whether to ignore the debugger when detecting ANRs.
     * When ignoring the debugger, ANRWatchdog will detect ANRs even if the debugger is connected.
     * By default, it does not, to avoid interpreting debugging pauses as ANRs.
     * Default false.
     *
     * @param ignoreDebugger Whether to ignore the debugger.
     * @return itself for chaining.
     */
    public ANRWatchDog setIgnoreDebugger(boolean ignoreDebugger) {
        _ignoreDebugger = ignoreDebugger;
        return this;
    }


    /**
     * 1. 如果线程没中断,一直循环
     * 2. 记录开始 tick,并且向主线程 post message
     * 3. 线程睡 5s
     * 4. 判断 tick 是不是没变
     * 5. 是的话,认为有 ANR,因为 主线程没处理刚才 post 的消息
     * 6. 然后生成 ANRError,ANRError 中出 dump 对应的 主线程 Thread stack 信息
     * 7. 调用 ANR 回调接口,回传 ANRError,跳出循环
     */
    @Override
    public void run() {
        setName("|ANR-WatchDog|");

        int lastTick;
        int lastIgnored = -1;
        while (!isInterrupted()) {
            lastTick = _tick;
            _uiHandler.post(_ticker);
            try {
                Thread.sleep(_timeoutInterval);
            } catch (InterruptedException e) {
                _interruptionListener.onInterrupted(e);
                return;
            }

            // If the main thread has not handled _ticker, it is blocked. ANR.
            if (_tick == lastTick) {
                if (!_ignoreDebugger && Debug.isDebuggerConnected()) {
                    if (_tick != lastIgnored) {
                        Log.w("ANRWatchdog",
                            "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
                    }
                    lastIgnored = _tick;
                    continue;
                }

                ANRError error;
                if (_namePrefix != null) {
                    error = ANRError.New(_namePrefix, _logThreadsWithoutStackTrace);
                } else {
                    error = ANRError.NewMainOnly();
                }
                _anrListener.onAppNotResponding(error);
                return;
            }
        }
    }

}


ANRError

    1. 获取主线程。
    1. 创建一个排序的 map,按照线程排序,otherThread,mainThread。
    1. 筛选出 主线程 和 prefix 关键字线程 的 栈信息。
    1. 按照上面的排序,有 mainThread 就会放在最后。
    1. 然后 for 会走到 map 的最后。
    1. 也就是说,有 mainThread 的话,会返回一个包含 mainThread 的 ANRError。没的话,就是 map 最后的一个 thread 的 ANRError。
package com.github.anrwatchdog;

import android.os.Looper;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

/**
 * Error thrown by {@link com.github.anrwatchdog.ANRWatchDog} when an ANR is detected.
 * Contains the stack trace of the frozen UI thread.
 * <p>
 * It is important to notice that, in an ANRError, all the "Caused by" are not really the cause
 * of the exception. Each "Caused by" is the stack trace of a running thread. Note that the main
 * thread always comes first.
 *
 * 自定义一个 Error
 *
 * {@link ANRError#New(String prefix, boolean logThreadsWithoutStackTrace)}
 * 1. 获取主线程
 * 2. 创建一个排序的 map,按照线程排序,otherThread,mainThread
 * 3. 筛选出 主线程 和 prefix 关键字线程 的 栈信息
 * 4. 按照上面的排序,有 mainThread 就会放在最后
 * 5. 然后 for 会走到 map 的最后
 * 6. 也就是说,有 mainThread 的话,会返回一个包含 mainThread 的 ANRError
 * -  没的话,就是 map 最后的一个 thread 的 ANRError
 *
 * {@link ANRError#NewMainOnly()}
 * 直接创建一个包含 mainThread 的 ANRError
 */
@SuppressWarnings({ "Convert2Diamond", "UnusedDeclaration", "DanglingJavadoc" })
public class ANRError extends Error {

    private static class $ implements Serializable {
        private final String _name;
        private final StackTraceElement[] _stackTrace;


        private class _Thread extends Throwable {
            private _Thread(_Thread other) {
                super(_name, other);
            }


            @Override
            public Throwable fillInStackTrace() {
                setStackTrace(_stackTrace);
                return this;
            }
        }


        private $(String name, StackTraceElement[] stackTrace) {
            _name = name;
            _stackTrace = stackTrace;
        }
    }


    private static final long serialVersionUID = 1L;


    private ANRError($._Thread st) {
        super("Application Not Responding", st);
    }


    @Override
    public Throwable fillInStackTrace() {
        setStackTrace(new StackTraceElement[] {});
        return this;
    }


    /**
     * 1. 获取主线程
     * 2. 创建一个排序的 map,按照线程排序,otherThread,mainThread
     * 3. 筛选出 主线程 和 prefix 关键字线程 的 栈信息
     * 4. 按照上面的排序,有 mainThread 就会放在最后
     * 5. 然后 for 会走到 map 的最后
     * 6. 也就是说,有 mainThread 的话,会返回一个包含 mainThread 的 ANRError
     * -  没的话,就是 map 最后的一个 thread 的 ANRError
     *
     * @param prefix prefix
     * @param logThreadsWithoutStackTrace logThreadsWithoutStackTrace
     * @return ANRError
     */
    static ANRError New(String prefix, boolean logThreadsWithoutStackTrace) {
        final Thread mainThread = Looper.getMainLooper().getThread();

        /**
         * 创建一个排序的 map,按照线程排序,otherThread,mainThread
         */
        final Map<Thread, StackTraceElement[]> stackTraces
            = new TreeMap<Thread, StackTraceElement[]>(new Comparator<Thread>() {
            @Override
            public int compare(Thread lhs, Thread rhs) {
                if (lhs == rhs) {
                    return 0;
                }
                if (lhs == mainThread) {
                    return 1;
                }
                if (rhs == mainThread) {
                    return -1;
                }
                return rhs.getName().compareTo(lhs.getName());
            }
        });

        /**
         * 筛选出 主线程 和 prefix 关键字线程 的 栈信息
         */
        for (Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {
            if (
                entry.getKey() == mainThread
                    || (
                    entry.getKey().getName().startsWith(prefix)
                        && (
                        logThreadsWithoutStackTrace
                            ||
                            entry.getValue().length > 0
                    )
                )
                ) {
                stackTraces.put(entry.getKey(), entry.getValue());
            }
        }

        /**
         * 上面没筛选出 mainThread 信息的话
         * 这里将 mainThread 塞进去
         */
        // Sometimes main is not returned in getAllStackTraces() - ensure that we list it
        if (!stackTraces.containsKey(mainThread)) {
            stackTraces.put(mainThread, mainThread.getStackTrace());
        }

        /**
         * 有 mainThread 的话,会返回一个包含 mainThread 的 ANRError
         * 没的话,就是 map 最后的一个 thread 的 ANRError
         *
         */
        $._Thread tst = null;
        for (Map.Entry<Thread, StackTraceElement[]> entry : stackTraces.entrySet()) {
            tst = new $(getThreadTitle(entry.getKey()), entry.getValue()).new _Thread(tst);
        }

        return new ANRError(tst);
    }


    /**
     * 直接创建一个包含 mainThread 的 ANRError
     *
     * @return ANRError
     */
    static ANRError NewMainOnly() {
        final Thread mainThread = Looper.getMainLooper().getThread();
        final StackTraceElement[] mainStackTrace = mainThread.getStackTrace();

        return new ANRError(new $(getThreadTitle(mainThread), mainStackTrace).new _Thread(null));
    }


    private static String getThreadTitle(Thread thread) {
        return thread.getName() + " (state = " + thread.getState() + ")";
    }
}