Android开发——APP ANR治理

news2024/9/23 13:19:59

一、背景介绍

ANR(Application Not Response)指应用程序无响应,通常出现在主线程被阻塞时,并伴随ANR弹窗出现。ANR发生时要么关闭当前app,要么等待,等待的结果大概率还是继续ANR,最终需要杀掉应用进程。ANR的治理难点是不像Crash一样有崩溃日志,定位问题比较困难,但是ANR带来的用户体验是极差的,是必须要解决的问题。

本文将着眼于三个方面,ANR的统计、ANR的定位,以及线上ANR治理的几个case介绍。

二、 ANR的统计

2.1 ANR初步挖掘

目前判定发生ANR的原理,和大部分卡顿判定、FPS监控的原理是一致的,即为主线程Looper设置一个LooperPrinter,根据回传信息头区分消息执行开始与结束,计算Message耗时。原理如下:

public static void loop() {
      ...
      if (logging != null) {
          logging.println(">>>>> Dispatching to " + msg.target + " " +
                      msg.callback + ": " + msg.what);
      }
      ...
      if (logging != null) {
          logging.println("<<<<< Finished to " + msg.target + " " +
                      msg.callback);
      }
}

自定义LooperPrinter如下:

class LooperPrinter implements Printer {

    @Override
    public void println(String x) {
       ...
       if (isValid) {
           <!--区分开始结束,计算消息耗时-->
           dispatch(x.charAt(0) == '>', x);
       }
}

利用回调日志中的参数">>>>""<<<<",即可诊断出Message执行耗时,原则上UI线程所有的消息都应该保持轻量级,任何消息超时都应当算作异常行为。从而确定单位时间内的掉帧数,达到间接计算FPS的目的;若执行耗时更长一些,则认为发生了一次卡顿;若耗时超过了5秒,就可以认为发生了ANR。

目前团队在用的ANR监控源码已经公开,具体代码可以见源码中ANRMonitorRunnable的实现,在实现中可以看到,在监控到日志中的开始标记">>>>"时,便使用mANRHandler#postDelayed一个延迟5秒的ANRMonitorRunnable,并在Runnable中维护invalid字段,用于在接收到结束标记"<<<<"时阻止ANR的判定。最后,在确定发生ANR时,将发生ANR的Activity信息进行上传。

2.2 初版本上线效果

自上线以来,我们监测到了线上存在的ANR情况,包括每日ANR数量以及在哪些页面发生,量级如下图所示:
在这里插入图片描述
我们开心的发现我们的APP中确实存在大量的ANR,但是我们好像并没有定位到具体堆栈的方法:

  • 通过对相关Activity中的代码进行Review去主动发现ANR的方式,显然太过于笨拙,只能靠猜。一段时间过去了,ANR数量和上线初期相比并无明显改善。根本原因在于,缺乏ANR的具体堆栈定位以及上报的机制,防止大海捞针的情况出现;

  • 同时,通过研究源码发现,线上的ANR监测库在原理层面存在一些先天不足,其无法监控IdleHandler卡顿、以及View#TouchEvent卡顿 (下文将详细说明)。

2.3 ANR监控的完善

2.3.1 已有框架的监控原理 & 漏洞

在这里插入图片描述
如上图所示,已有监控框架是通过计算执行dispatchMessage()方法之前和之后打印字符串的时间差来获得该方法执行的时间,并实现卡顿的监控;
在这里插入图片描述
然而,在处理消息之前需要获取到消息,MessageQueue#next()本身可能会阻塞导致ANR,显然是无法被监控到的。

2.3.2 对监控框架进行完善

完善监控框架的第一反应是,直接监控两次dispatchMessage()方法之前的时间差,这样就可以把next()方法的耗时也计算在内。不幸的是,主线程空闲时,也会阻塞在MessageQueue#next()方法中,我们很难区分究竟是发生了卡顿还是主线程空闲。

因此我们只能深入研究可能会引起queue.next()阻塞的原因。通过研究MessageQueue#next()的源码发现,有两个重要的case可能会引起next()阻塞的情况:

  • IdleHandler处理,通常用于在主线程空闲时候的业务处理;
  • View#TouchEvent,通常用于自定义View中的一些坐标记录。
    在这里插入图片描述

2.3.3 关于IdleHandler的耗时监控

首先对于第一种情况,IdleHandler耗时的监控是比较重要的,因为涉及到的业务较多。我们发现MessageQueue中的mIdleHandlers是可以被反射的,这个变量保存了所有将要执行的IdleHandler,我们只需要把ArrayList类型的mIdleHandlers,通过反射替换为MyArrayList,在我们自定义的MyArrayList中重写add()方法,再将我们自定义的MyIdleHandler添加到MyArrayList中,即可完成对queueIdle()方法的耗时监控。实现源码可以参考Matrix的实现,源码在这里。

  • 原理图如下所示:
    在这里插入图片描述

2.3.4 关于View#TouchEvent的耗时监控

接着,对于第二种View#TouchEvent的监控。Touch事件是通过server端的inputDispatcher线程传递给Client端的UI线程的,并且使用的是一对Socket进行通讯。我们可以通过PLT Hook,将libinput.so中的recvfromsendto方法进行替换。并计算两者的时间差来验证产生了一次Touch事件的卡顿。这种方案只能说理论上可行,但是实操有一定的兼容性风险,建议还是通过自行Review自定义View中TouchEvent的代码实现进行问题的检查。

  • 原理图如下所示:
    在这里插入图片描述

三、 ANR堆栈的定位

3.1 尝试将ANR堆栈上报到APM

当定位到发生ANR时,我们一定是希望获得主线程目前被卡在哪里的。首先想到的就是在子线程中去dump主线程的堆栈信息,并上传堆栈到APM平台。

//在子线程中获取主线程当前的堆栈信息
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();

流程图如下所示:
在这里插入图片描述
然而实际操作下来,发现一个非常麻烦的问题,就是堆栈聚合问题。我们自己的APM平台本质上就是一个数据库,对上传的信息进行存储和展示:
在这里插入图片描述
可以看出,APM上的堆栈无法聚合,每天数万条的堆栈,根本无法人肉去处理;因此无法使用APM进行堆栈上传和分析。

3.2 尝试使用Firebase平台的堆栈聚合能力

我们想到,我们日常使用的Crash监控平台,本身是有堆栈聚合能力的,我们希望可以利用这一点,完成我们自己ANR堆栈信息的上报。通过阅读Firebase SDK的源码,发现其私有API是可以支持自定义上报堆栈的,我们通过反射的方式进行调用。

  • 流程图如下所示:
    在这里插入图片描述
  • 源码如下所示:
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
FirebaseCrashlytics instance = FirebaseCrashlytics.getInstance();
CrashlyticsCore core = (CrashlyticsCore) ReflectUtil.reflectObject(instance, "core");
Object controller = ReflectUtil.reflectObject(core, "controller");
Method method = ReflectUtil.reflectMethod(controller, "writeNonFatalException", Thread.class, Throwable.class);
Throwable throwable = new Throwable("ANR异常 " + activity.getClass().getSimpleName());
throwable.setStackTrace(stackTrace);
method.invoke(controller, Looper.getMainLooper().getThread(), throwable);
class ReflectUtil {

    public static Method reflectMethod(Object instance, String name, Class<?>... argTypes) {
        try {
            Method method = instance.getClass().getDeclaredMethod(name, argTypes);
            method.setAccessible(true);
            return method;
        } catch (Exception e) {

        }
        return null;
    }

    public static <T> T reflectObject(Object instance, String name) {
        try {
            java.lang.reflect.Field field = instance.getClass().getDeclaredField(name);
            field.setAccessible(true);
            return (T) field.get(instance);
        } catch (Exception e) {
            e.printStackTrace();

        }
        return null;
    }
}

通过测试,发现确实可以完成目标。可以从下图看出,堆栈信息明确且已聚合,真正定位到了引起ANR的具体代码位置。
在这里插入图片描述

四、 线上ANR治理的一些case分享

Case1: String Format性能问题导致ANR

在这里插入图片描述

  • 上述代码一开始是死活想不对会有什么问题,但是经过分析堆栈,发现只有在倒计时View模块的setText会发生ANR,猜测是频繁调用导致性能问题,
  • 继续分析测试,在频繁调用的情况下,String.format()的耗时比简单的字符串+号拼接慢40-60倍

Case2: 网络诊断库netanalysis里的一个隐蔽ANR

在这里插入图片描述

  • 问题1(BUG) :每个页面Created时机都会开启一个30秒的循环去上报当前的网络状态,这显然是不合理的,因为开启N个页面就是开启N个循环,导致获取时机越来越繁杂,算是发现了一个隐藏BUG;
  • 问题2(性能):系统服务在主线程调用,可优化到子线程中去开启循环。

Case3: ROM信息获取<兼容问题>导致ANR

在这里插入图片描述

  • 在判断是不是小米Rom的时候,使用了Runtime.getRuntime().exex()的方式,这种方式是阻塞调用,且会在某些机型上导致卡死。
  • 判断小米Rom的方式替换为官网推荐方式:Build.MANUFACTURER.contains("Xiaomi");

五、 线上ANR治理总结

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/137949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在msys2上安装武汉大学的开源PPP解算软件-PRIDE PPP-AR

最近一篇文章《免费提供POSMV的GNSS数据解算服务&#xff0c;验潮仪丢失的一种补救》发表了以后&#xff0c;有一位相识的网友提供了一个重要信息&#xff1a;武汉大学PPP解算软件名字为PRIDE PPP-AR 开源了&#xff0c;其GUI自动下载星历文件进行自主解算。PRIDE PPP-AR 的官网…

2022年中小企业数字化转型路径报告(附下载链接)

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年11月份热门报告盘点罗振宇2023年跨年演讲PPT原稿吴晓波2022年年终秀演讲PPT原稿2023年&#xff0c;如何科学制定年度规划&#xff1f;《底层逻辑》高清配图清华大学256页…

(机器学习深度学习常用库、框架|Pytorch篇)第三节:Pytorch之torchvision详解

文章目录一&#xff1a;torchvision概述二&#xff1a;torchvision.datasets&#xff08;1&#xff09;官方数据集&#xff08;2&#xff09;自定义数据集类&#xff08;3&#xff09;ImageFolder手动实现三&#xff1a;torchvision.transforms四&#xff1a;torchvision.model…

【元宇宙欧米说】左手追星,右手造富——用Web3加持娱乐行业

娱乐圈如何才能与资本市场保持步调一致&#xff1f;Web3浪潮来袭&#xff0c;文娱行业如何才能踏上Web3世界的巨轮&#xff1f; 1月4日下午三点&#xff0c;VegaIdol联合创始人Linnea将以**“左手追星&#xff0c;右手造富——用Web3加持娱乐行业”为题&#xff0c;与大家共同…

界面组件DevExpress WinForms v22.2 -全新升级的皮肤和矢量图标

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

RabbitMQ的简单介绍与使用

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;曾经在某央企公司实习&#xff0c;目前入职某税务公司。本篇文章将记录和分享RabbitMQ相关的知识点。 本篇文章记录的基础知识&#xff0c;适合在学Java的小白&#xff0c;也适合复习中&#xff…

乐视宣布每周工作4天半

老板跑了&#xff0c;公司不但没倒&#xff0c;而且员工还过上了不加班不内卷的神仙生活。 典型的老虎不在家&#xff0c;规矩自己定啊&#xff01; 神仙日子 前段时间&#xff0c;网上流传着一则消息&#xff0c;说乐视目前还有400多名员工&#xff0c;靠着《甄嬛传》版权和…

Cadence PCB仿真使用Allegro PCB SI 创建含差分对网络元器件的IBIS模型图文教程

⏪《上一篇》   🏡《总目录》   ⏩《下一篇》 1,概述 本文简单介绍使用Allegro PCB SI软件为BRD PCB设计文件中的含有差分对网络的元器件创建IBIS模型的方法。 2,创建方法 第1步:确定打开PCB文件的软件是 Allegro PCB SI 如果不是Allegro PCB SI,可执行File→Chan…

回望2022,依然值得仰望星空

转眼间 2022 年已经过去&#xff0c;这是我在 CSDN 创作的第二年&#xff0c;在文章的创作上也是脱离“博客新手”身份&#xff0c;正式蜕变为“博客老手”的一年&#xff0c;各方面收获颇丰。2021 初见 CSDN来到 CSDN 是在 2020 年的 11 月份&#xff0c;但是那时候并没有开始…

传感器与传感器通道

传感器 Def:以一定精确度 把 被测量转换为与之有确定对应关系的&#xff0c;便于应用的某种物理量的测量系统。 作用&#xff1a; 捕获并转换信息&#xff0c;非电量物理参数转换为电参数。 e.g: 速度 ->电压&#xff0c;电流 组成&#xff1a; 敏感元件&#xff08;直接感受…

css移动端适配最佳实践

移动端适配&#xff0c;在移动端里经常有遇到&#xff0c;在不同分辨率移动端设备精确还原UI设计稿&#xff0c;这是一个令人抓狂的问题&#xff0c;好在有flex&#xff0c;box布局解决了自适应很大一部分问题。 在开始本文之前主要介绍几种笔者常用的适配方案 1、设置meta标…

美赛Day1

1 层次分析法 评价类问题 1.1 模型介绍 1.1.1 模型介绍 在对B的评价中&#xff0c;判断A个物体哪个最好。将B分为k个可以评价的方面分别进行打分&#xff08;每个方面A个物体的分数和为1&#xff09;&#xff0c;最终对A个物体的k个方面加权求和进行比较。 1.1.2 解题思路…

剑指政企数智办公市场,通信厂商融云有何看家本领?

近年来&#xff0c;数字经济正在加速赋能千行百业&#xff0c;我国的政务办公也正加速由数字化向智能化深度扩展。在线办公市场从公有云到政企私有云的热度&#xff0c;已然节节攀升。近日&#xff0c;作为通信厂商被熟知的融云推出了“百幄”数智办公平台&#xff0c;正式宣布…

05数据结构——顺序表与链表

开始系统学习算法啦&#xff01;为后面力扣和蓝桥杯的刷题做准备&#xff01;这个专栏将记录自己学习算法是的笔记&#xff0c;包括概念&#xff0c;算法运行过程&#xff0c;以及代码实现&#xff0c;希望能给大家带来帮助&#xff0c;感兴趣的小伙伴欢迎评论区留言或者私信博…

【DETR】DETR训练VOC数据集/自定义数据集

训练DETR一、数据准备二、配置DETRReferences一、数据准备 DETR用的是COCO格式的数据集。 如果要用DETR训练自己的数据集&#xff0c;直接利用Labelimg标注成COCO格式。 如果是VOC数据集的话&#xff0c;要做一个格式转换。网上一大堆格式转换的代码都很乱&#xff0c;所以自己…

java基于springboot外卖系统在线订餐系统app源码厨艺论坛APP

简介 本项目主要包括了外卖订餐系统&#xff08;在线订餐和外卖配送&#xff09;、厨艺论坛系统、管理员后台、用户中心等功能。用户注册后可以选择餐桌在线点餐支付&#xff0c;也可以选择外卖配送到家的方式。 演示视频 https://www.bilibili.com/video/BV1xv411t7JD/?sha…

Thinkphp5框架简单理解

说明 该文章来源于同事lu2ker转载至此处&#xff0c;更多文章可参考&#xff1a;https://github.com/lu2ker/ 目录说明TP5框架简单理解1. 架构总览1.1 控制器/操作1.2 MVC模式流程1.3 类库自动加载1.4 URL访问检测1.5 路由模式1.5.1 普通模式1.5.2 混合模式1.5.4 强制路由1.6 …

数据结构与算法学习——栈结构

在程序设计中&#xff0c;一定接触过“堆栈”的概念。其实&#xff0c;“栈 ” 和 “堆 ” 是两个不同的概念。这里&#xff0c;栈是一种特殊的数据结构&#xff0c;在中断处理特别是重要数据的现场保护有着重要意义。 什么是栈结构 从数据的逻辑结构来看&#xff0c;栈结构其…

59. 微调(fine-tuning)代码实现

1. 热狗识别 让我们通过具体案例演示微调&#xff1a;热狗识别。 我们将在一个小型数据集上微调ResNet模型。该模型已在ImageNet数据集上进行了预训练。 这个小型数据集包含数千张包含热狗和不包含热狗的图像&#xff0c;我们将使用微调模型来识别图像中是否包含热狗。 %matp…

专访中银金科:数字驱动成为新的增长引擎,未来业务转化是关键

大数据和信息科技正在逐步颠覆银行业过往的业务模式。建立以数据驱动为核心&#xff0c;以优化客户体验为目标的可持续营销理念&#xff0c;逐渐成为行业的共识。但是&#xff0c;伴随着银行业数字化转型进程加速发展&#xff0c;海量客户数据和低效营销之间的矛盾日益凸显。在…