美文网首页
ANR-WatchDog 技术分析:Android ANR 检测

ANR-WatchDog 技术分析:Android ANR 检测

作者: 放羊娃华振 | 来源:发表于2025-12-17 00:08 被阅读0次

在 Android 开发中,应用程序无响应(ANR)问题是影响用户体验的重要因素之一。当应用的 UI 线程被长时间阻塞时,系统会弹出 ANR 对话框,严重影响用户对应用的评价。虽然 Google 提供了一些工具来帮助开发者诊断 ANR,但在实际开发中,我们往往需要更灵活、更自动化的解决方案。ANR-WatchDog 就是这样一个轻量级的开源库,它可以帮助开发者在运行时检测 ANR 并收集详细的堆栈信息。

本文将从使用方式和实现原理两个方面深入分析 ANR-WatchDog 库。

一、ANR-WatchDog 简介

ANR-WatchDog 是一个简单的看门狗定时器,用于检测 Android 应用程序的 UI 线程是否冻结。当检测到 ANR 时,它会抛出一个包含所有线程堆栈跟踪的错误(主线程优先),这使得开发者可以轻松地捕获和分析 ANR 错误。

核心特性

  1. 实时 ANR 检测:监控 UI 线程是否响应
  2. 完整的线程堆栈:提供所有运行线程的堆栈跟踪信息
  3. 可配置的超时时间:默认 5 秒,可根据需求调整
  4. 多种配置选项
    • 可设置 ANR 回调监听器
    • 支持调试器模式配置
    • 可过滤特定线程的报告
    • 支持 ANR 拦截器
  5. 兼容主流崩溃报告系统:如 ACRA、Crashlytics、HockeyApp、Bugsnag 等

二、ANR-WatchDog 使用方式

1. 集成方式

Gradle 依赖

app/build.gradle 文件中添加依赖:

implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'

Eclipse 集成

  1. 下载最新 jar 包
  2. 将 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 线程是否响应。具体工作流程如下:

  1. 启动一个独立的看门狗线程
  2. 定期向 UI 线程的消息队列发送一个 Runnable 任务
  3. 看门狗线程等待指定的时间(默认 5 秒)
  4. 检查 Runnable 任务是否被执行
  5. 如果任务未被执行,说明 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);
    }
}

这种设计的巧妙之处在于:

  1. 利用 Throwable 的嵌套机制来组织多个线程的堆栈信息
  2. 每个线程的堆栈信息作为"Caused by"显示,便于阅读
  3. 主线程总是排在第一位,符合 ANR 分析的习惯

四、总结

ANR-WatchDog 作为一个轻量级的 ANR 检测库,具有以下优势:

  1. 实现简单高效:基于 Android 消息机制,无需复杂的 native 代码
  2. 配置灵活:提供了丰富的配置选项满足不同场景需求
  3. 易于集成:支持主流的崩溃报告系统
  4. 信息丰富:不仅提供 ANR 检测,还收集完整的线程堆栈信息

对于 Android 开发者来说,ANR-WatchDog 是一个非常实用的工具,特别是在需要精确监控应用性能和快速定位 ANR 问题的场景下。通过合理配置和使用,它可以显著提升应用的稳定性和用户体验。
参考:
github代码仓库:https://github.com/SalomonBrys/ANR-WatchDog

相关文章

网友评论

      本文标题:ANR-WatchDog 技术分析:Android ANR 检测

      本文链接:https://www.haomeiwen.com/subject/ktlslltx.html