ANR系列(二)——ANR监听方案之WatchDog

这篇具有很好参考价值的文章主要介绍了ANR系列(二)——ANR监听方案之WatchDog。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

ANR的监控在Android6.0之前可以通过监听文件data/anr/trace读取trace信息来分析,但从6.0之后就被禁止了。随着Android的发展,手机里的ANR越来越多,对ANR的监控方案也就五花八门。

WatchDog方案

WatchDog是个开源的框架,是一个短小精悍的UI卡顿监测框架,只有2个源文件,ANRWatchDogANRError

1、WatchDog核心原理

启动一个异步线程,在while循环中,使用主线程的Handler发送一个消息,线程休眠指定的时间5s,当线程唤醒之后,如果发送的消息还没被主线程执行,即认为主线程发生了卡顿。

  • 成员变量
  1. _anrListener:当监控到ANR时的回调方法。如打印日志、崩溃抛出异常
  2. _anrInterceptor:当监控到ANR时候,判断是否拦截该ANR。如果拦截则不会触发ANRListener,暴露给开发者使用
  3. _timeoutInterval:默认的线程卡顿时间为5秒
  4. _uiHandler:主线程的Handler
  5. _tick:判断ANR是否发生的开关,非0则没收到主线程消息,即ANR超时,0就收到主线程消息
  6. _reported:指当前ANR是否报告过,报告过则忽略
  7. _namePrefix:ANR发生时,表示统计哪个线程的堆栈信息,如果是null则表示主线程堆栈信息
  • 核心原理

ANRWatchDog继承自Thread,分析其原理,则从启动ANRWatchDog线程开始,执行ANRWatchDog$run()

public class ANRWatchDog extends Thread {

    private final Runnable _ticker = new Runnable() {
        @Override public void run() {
            _tick = 0;
            _reported = false;
        }
    };
    
    @Override
    public void run() {
        // 1、设置线程名称
        setName("|ANR-WatchDog|");
        // 2、获取线程休眠时间,即 UI 卡顿超时时间。默认是5000
        long interval = _timeoutInterval;
        // 3、如果线程中断,则直接退出线程。
        while (!isInterrupted()) {
            boolean needPost = _tick == 0;
            // 4、_tick置为非0,等待主线程改为0
            _tick += interval;
            // 5、如果主线程一直在阻塞的话,就不要一直发消息。如果主线程未阻塞,发送消息。
            if (needPost) {
                _uiHandler.post(_ticker);
            }
            try {
                // 6、休眠指定的时间。
                Thread.sleep(interval);
            } catch (InterruptedException e) {
                // 如果线程休眠过程中,线程被中断,则回调 onInterrupted() 方法。
                _interruptionListener.onInterrupted(e);
                return ;
            }
    
            // 7、睡眠过后,如果主线程还没有把_tick置为0,就认为发生ANR。
            if (_tick != 0 && !_reported) {
                // 如果 _ignoreDebugger 为 false,且 AndroidStudio 正在断点调试,则忽略 ANR
                if (!_ignoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
                    Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
                    _reported = true;
                    continue ;
                }
               // 拦截器是否拦截 ANR
                interval = _anrInterceptor.intercept(_tick);
                if (interval > 0) {
                    continue;
                }
    
                final ANRError error;
                // _namePrefix!=null 表示统计所有线程或者统计指定 _namePrefix 前缀的线程堆栈信息。
                if (_namePrefix != null) {
                    error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
                } else {
                 // _namePrefix ==null 表示只统计主线程堆栈信息。
                    error = ANRError.NewMainOnly(_tick);
                }
                // 8、回调onAppNotResponding。
                _anrListener.onAppNotResponding(error);
                interval = _timeoutInterval;
                _reported = true;
            }
        }
    }
}

最终通过ANRError.New或者ANRError.NewMainOnly生成所有线程或者主线程的堆栈信息抛出来。作者通过继承Throwable的形式,并覆写fillInStackTrace()让系统不要自己收集堆栈信息,系统这个方法耗时太多,而是通过Thread.getAllStackTraces()获取所有线程,并从线程中拿出他们当前的堆栈信息即可。

public class ANRError extends Error {

    // 方法一:生成所有堆栈信息
    static ANRError New(long duration, String prefix, boolean logThreadsWithoutStackTrace) {
        // 获取主线程
        final Thread mainThread = Looper.getMainLooper().getThread();
        // 将主线程的堆栈信息,排到到第一位输出
        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());
            }
        });
        
        // 获取所有线程,并根据传递过来的参数,对线程信息进行过滤
        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());

        // 主线程信息加进来
        if (!stackTraces.containsKey(mainThread)) {
            stackTraces.put(mainThread, mainThread.getStackTrace());
        }

        $._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);
    }

    // 方法二:生成主线程的堆栈信息
    static ANRError NewMainOnly(long duration) {
        final Thread mainThread = Looper.getMainLooper().getThread();
        final StackTraceElement[] mainStackTrace = mainThread.getStackTrace();

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

    private static String getThreadTitle(Thread thread) {
        return thread.getName() + " (state = " + thread.getState() + ")";
    }
}
2、WatchDog问题

WatchDog虽然思路很巧妙,但是ANR统计并不准确

anrwatchdog,ANR合集,java,android,ui

  • 例如ANRWatchDog线程休眠指定的时间为5秒,线程阻塞8秒
  • 当第1s发送的时候,此时主线程处于空闲状态,马上回包表示没问题
  • 当第5s发送的时候,此时主线程处于阻塞状态,但是到第8s,恢复了空闲状态,马上回包表示没问题
  • 中间的8s阻塞就被忽略掉了

优点

  1. 兼容性好,各个机型版本通用
  2. 无需修改 APP 逻辑代码,非侵入式
  3. 逻辑简单,性能影响不大

缺点文章来源地址https://www.toymoban.com/news/detail-630876.html

  1. 无法保证能捕捉所有 ANR,对阈值的设置直接影响捕获概率
3、WatchDog改进
  1. 可以考虑针对发送到主线程的消息做个策略,将原来的5s发送一次改为1s发送一次,假如累计有5次发出且5次都回不了包,则表示有ANR的现象,再采集线程信息

参考资料

  • Android - ANRWatchDog

到了这里,关于ANR系列(二)——ANR监听方案之WatchDog的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Service启动ANR原理

        在Service组件StartService()方式启动流程分析文章中,针对Context#startService()启动Service流程分析了源码,其实关于Service启动还有一个比较重要的点是Service启动的ANR,因为因为线上出现了上百例的 \\\"executing service \\\" + service.shortName 的异常。      本文中所有源码都只粘贴关键代

    2024年02月15日
    浏览(41)
  • Android中ANR的分析和解决

    1、 ANR的定义 ANR,是“Application Not Responding”的缩写,即“应用程序无响应”。如果你应用程序在UI线程被阻塞太长时间,就会出现ANR,通常出现ANR,系统会弹出一个提示提示框,让用户知道,该程序正在被阻塞,是否继续等待还是关闭。 2、ANR的类型 (1) KeyDispatchTimeout (常

    2024年02月02日
    浏览(38)
  • Android ANR产生的原因以及解决方式

    一、什么是ANR ANR是Android系统中的一种错误状态,全称为Application Not Responding,中文翻译为“应用无响应”。当Android系统检测到应用程序在一段时间内未能响应用户输入或无法执行主要的UI线程操作时,就会触发ANR错误。ANR是一种系统保护机制,旨在确保应用的响应性,防止用

    2024年04月26日
    浏览(32)
  • Android性能优化之游戏引擎初始化ANR

    近期,着手对bugly上的anr 处理,记录下优化的方向。 借用网上的一张图: 这里的anr 问题是属于主线程的call 耗时操作。需要使用trace 来获取发生anr前一些列的耗时方法调用时间,再次梳理业务,才可能解决。 问题1 java 调用栈: 从调用栈中发现onActivityResult()执行对游戏侧的

    2024年02月15日
    浏览(48)
  • Android进阶宝典 —如何通过ANR日志分析问题原因

    当系统发生ANR时,会主动dump trace日志并保存在data/anr/trace.txt文件夹下,我们在拿到anr日志之后,就可以着手分析日志;或者可以通过bugreport命令来拉取日志,具体命令如下: 当然我们在解读日志的时候,肯定是需要一些去查询,判断到底是哪种类型的问题导致了ANR。

    2024年02月05日
    浏览(44)
  • Redis-监听过期key-JAVA实现方案

    一、创建监听配置类  RedisListenerConfig。 二、 创建监听器: RedisKeyExpirationListener 继承  KeyExpirationEventMessageListener . 这里只能获取到 过期的key,无法获取过期的value.  三: 设置redis的配置文件redis.config中的  notify-keyspace-events 为 Ex  四、验证一下:           在redis客户端执行

    2024年02月11日
    浏览(34)
  • 【Flutter 问题系列第 78 篇】Android Studio 升级后提示 org.gradle.java.home Gradle property is invalid 的解决方案

    这是【Flutter 问题系列第 78 篇】,如果觉得有用的话,欢迎关注专栏。 Flutter SDK:3.3.5,Dart SDK:2.18.2, 操作系统:macOS Ventura 13.0.1 Intel Core i9,Android Studio 版本:Flamingo 2022.2.1 Patch 2 一:问题描述 在公司的 M2 电脑上,直接使用 Android Studio 的检查更新,升级到 Flamingo 2022.2.1 版

    2024年02月05日
    浏览(50)
  • 安卓之导致ANR的原因分析,问题定位以及解决方案

            在Android应用开发中,Application Not Responding(ANR)是一种常见的性能问题,它直接关系到用户体验的质量。当应用在特定时间段内无法及时响应用户的交互或者系统事件时,系统将会抛出ANR错误,提示用户应用已停止响应。为了确保应用的流畅性和用户满意度,理解

    2024年03月13日
    浏览(50)
  • Android 查看ANR和Crash日志(adb bugreport)

    今天测试那儿出了个ANR,我自己手机没问题,很烦,定位不了位置。 于是还是得用ADB连接来看一下,之前用,但是老是会忘记,今天总结一下。 ADB命令查看应用包名_adb查看包名命令_岁月不待人的博客-CSDN博客 上面的连接是之前用adb后写的一篇常用的一些密令总结。 ANR的排

    2024年02月13日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包