ANR-WatchDog 技术分析:Android ANR 检测
在 Android 开发中,应用程序无响应(ANR)问题是影响用户体验的重要因素之一。当应用的 UI 线程被长时间阻塞时,系统会弹出 ANR 对话框,严重影响用户对应用的评价。虽然 Google 提供了一些工具来帮助开发者诊断 ANR,但在实际开发中,我们往往需要更灵活、更自动化的解决方案。ANR-WatchDog 就是这样一个轻量级的开源库,它可以帮助开发者在运行时检测 ANR 并收集详细的堆栈信息。
本文将从使用方式和实现原理两个方面深入分析 ANR-WatchDog 库。
一、ANR-WatchDog 简介
ANR-WatchDog 是一个简单的看门狗定时器,用于检测 Android 应用程序的 UI 线程是否冻结。当检测到 ANR 时,它会抛出一个包含所有线程堆栈跟踪的错误(主线程优先),这使得开发者可以轻松地捕获和分析 ANR 错误。
核心特性
- 实时 ANR 检测:监控 UI 线程是否响应
- 完整的线程堆栈:提供所有运行线程的堆栈跟踪信息
- 可配置的超时时间:默认 5 秒,可根据需求调整
-
多种配置选项:
- 可设置 ANR 回调监听器
- 支持调试器模式配置
- 可过滤特定线程的报告
- 支持 ANR 拦截器
- 兼容主流崩溃报告系统:如 ACRA、Crashlytics、HockeyApp、Bugsnag 等
二、ANR-WatchDog 使用方式
1. 集成方式
Gradle 依赖
在 app/build.gradle 文件中添加依赖:
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
Eclipse 集成
- 下载最新 jar 包
- 将 jar 包放入项目的
libs/目录
2. 基本使用
在 Application 类的 onCreate 方法中启动 ANRWatchDog:
new ANRWatchDog().start();
3. 高级配置
设置超时时间
// 设置 10 秒超时
new ANRWatchDog(10000 /*timeout*/).start();
设置 ANR 回调监听器
new ANRWatchDog().setANRListener(new ANRWatchDog.ANRListener() {
@Override
public void onAppNotResponding(ANRError error) {
// 处理 ANR 错误,例如发送到崩溃报告系统
ExceptionHandler.saveException(error, new CrashManager());
}
}).start();
调试器配置
// 即使连接了调试器也检测 ANR
new ANRWatchDog().setIgnoreDebugger(true).start();
线程过滤
// 只报告以 "APP:" 为前缀的线程
new ANRWatchDog().setReportThreadNamePrefix("APP:").start();
// 只报告主线程
new ANRWatchDog().setReportMainThreadOnly().start();
ANR 拦截器
new ANRWatchDog(2000).setANRInterceptor(new ANRWatchDog.ANRInterceptor() {
@Override
public long intercept(long duration) {
long ret = 5000 - duration;
if (ret > 0) {
Log.w(TAG, "拦截了太短的 ANR (" + duration + " ms),推迟 " + ret + " ms 报告");
}
return ret;
}
}).start();
三、ANR-WatchDog 实现原理
1. 工作机制
ANRWatchDog 的工作机制非常巧妙,其核心思想是利用 Android 主线程的消息机制来检测 UI 线程是否响应。具体工作流程如下:
- 启动一个独立的看门狗线程
- 定期向 UI 线程的消息队列发送一个 Runnable 任务
- 看门狗线程等待指定的时间(默认 5 秒)
- 检查 Runnable 任务是否被执行
- 如果任务未被执行,说明 UI 线程被阻塞,触发 ANR 检测
2. 核心代码分析
ANRWatchDog 类结构
public class ANRWatchDog extends Thread {
// ANR 监听器接口
public interface ANRListener {
void onAppNotResponding(@NonNull ANRError error);
}
// ANR 拦截器接口
public interface ANRInterceptor {
long intercept(long duration);
}
// 主要成员变量
private final Handler _uiHandler = new Handler(Looper.getMainLooper());
private final int _timeoutInterval;
private volatile long _tick = 0;
private final Runnable _ticker = new Runnable() {
@Override public void run() {
_tick = 0;
}
};
}
核心检测逻辑
@Override
public void run() {
setName("|ANR-WatchDog|");
long interval = _timeoutInterval;
while (!isInterrupted()) {
boolean needPost = _tick == 0;
_tick += interval;
if (needPost) {
_uiHandler.post(_ticker);
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
// 如果 ticker 没有被执行,说明主线程被阻塞
if (_tick != 0 && !_reported) {
// 检查调试器状态
if (!_ignoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
Log.w("ANRWatchdog", "检测到 ANR 但被忽略,因为调试器已连接");
_reported = true;
continue ;
}
// 调用拦截器
interval = _anrInterceptor.intercept(_tick);
if (interval > 0) {
continue;
}
// 创建 ANRError 并通知监听器
final ANRError error;
if (_namePrefix != null) {
error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
} else {
error = ANRError.NewMainOnly(_tick);
}
_anrListener.onAppNotResponding(error);
interval = _timeoutInterval;
_reported = true;
}
}
}
3. ANRError 设计
ANRError 是 ANRWatchDog 抛出的自定义错误类型,它的设计非常独特:
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
@NonNull
public Throwable fillInStackTrace() {
setStackTrace(_stackTrace);
return this;
}
}
}
// 收集所有线程的堆栈信息
static ANRError New(long duration, @Nullable String prefix, boolean logThreadsWithoutStackTrace) {
final Thread mainThread = Looper.getMainLooper().getThread();
// 获取所有线程的堆栈跟踪
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());
}
}
// 构建嵌套的 Throwable 结构
$._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, duration);
}
}
这种设计的巧妙之处在于:
- 利用 Throwable 的嵌套机制来组织多个线程的堆栈信息
- 每个线程的堆栈信息作为"Caused by"显示,便于阅读
- 主线程总是排在第一位,符合 ANR 分析的习惯
四、总结
ANR-WatchDog 作为一个轻量级的 ANR 检测库,具有以下优势:
- 实现简单高效:基于 Android 消息机制,无需复杂的 native 代码
- 配置灵活:提供了丰富的配置选项满足不同场景需求
- 易于集成:支持主流的崩溃报告系统
- 信息丰富:不仅提供 ANR 检测,还收集完整的线程堆栈信息
对于 Android 开发者来说,ANR-WatchDog 是一个非常实用的工具,特别是在需要精确监控应用性能和快速定位 ANR 问题的场景下。通过合理配置和使用,它可以显著提升应用的稳定性和用户体验。
参考:
github代码仓库:https://github.com/SalomonBrys/ANR-WatchDog