编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.崩溃捕获设计实践
  • 02.崩溃治理优化总结
  • 03.Native崩溃治理实践
  • 04.ANR监控设计实践
  • 05.CPU消耗优化实践
  • 06.卡顿监控设计实践
  • 07.卡顿治理优化实践
  • 08.网络分析与优化实践
  • 09.线程优化实践操作
  • 10.高性能图片优化方案
  • 11.OOM异常优化实践
  • 12.内存监控优化方案
  • 13.内存治理优化实践
  • 14.FPS监测设计实践
  • 15.进程优化设计实践
  • 16.App启动优化实践
  • 17.App页面UI优化实践
  • 18.App稳定性专项实践
  • 19.App瘦身优化实践
  • 20.常见代码优化实践
  • 21.移动端防抓包实践
  • 22.App磁盘沙盒实践
  • 23.Ping工具开发实践
  • 24.Gradle构建优化实践
  • 25.CodeReview实践总结

04.ANR监控设计实践

目录介绍

  • 01.ANR整体概述
    • 1.1 ANR优化背景
    • 1.2 遇到问题记录
    • 1.3 ANR必备技能
  • 02.ANR基础分析
    • 2.1 究竟什么是ANR
    • 2.2 ANR产生条件
    • 2.3 ANR实际触发场景
    • 2.4 四大组件ANR
  • 03.ANR流程分析
    • 3.1 触发ANR的步骤
    • 3.2 Service的ANR分析
    • 3.3 ANR后系统执行流程
    • 3.4 那些极端情况不会ANR
    • 3.6 ANR静默和弹窗
    • 3.7 处理ANR信息收集
    • 3.8 dump进程stack
  • 04.ANR是怎么设计
    • 4.1 什么是输入系统
    • 4.2 系统进程输入系统
    • 4.3 组件类型ANR
    • 4.4 Input类型ANR
    • 4.5 事件分发异步机制
  • 05.ANR监控排查思路
    • 5.1 ANR监控整体思路
    • 5.2 目前流行方案
    • 5.3 如何查看ANR日志
    • 5.4 导出ANR信息
    • 5.5 ANR具体如何分析
    • 5.6 避免ANR操作方案
    • 5.7 ANR监控方案实践

01.ANR整体概述

1.1 ANR优化背景

1.2 遇到问题记录

在性能优化这一块,基本上只能说出传统的分析方式,例如ANR分析,是通过查看/data/anr/ 下的log,分析主线程堆栈、cpu、锁信息等。

这种方法有一定的局限性,并不是每次都奏效,很多时候是没有堆栈信息给你分析的,或者只有一张ANR的截图加上一句话描述。那么大概率会把这个问题当成“未复现”处理掉,而没有真正解决问题。

1.3 ANR必备技能

02.ANR基础分析

2.1 究竟什么是ANR

ANR的简单理解:ANR Activity not responding(页面没有响应) ;ANR Application not responding 应用没有响应

Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。

2.2 ANR产生条件

ANR的产生需要满足三个条件

  1. 主线程:只有应用程序进程的主线程响应超时才会产生ANR;
  2. 超时时间:产生ANR的上下文不同,超时时间也会不同,但只要在这个时间上限内没有响应就会ANR;
  3. 输入事件/特定操作:输入事件是指按键、触屏等设备输入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各个函数,产生ANR的上下文不同,导致ANR的原因也会不同;

造成ANR的原因一般有两种:

  1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
  2. 当前的事件正在处理,但没有及时完成

2.3 ANR实际触发场景

分别有哪些场景

  1. 主线程,被阻塞5秒钟以上,就会抛出ANR对话框。不同的组件发生ANR的时间不一样,主要是四大组件。
  2. 点击事件(按键和触摸事件)5s内没被处理: Input event dispatching timed out

那些操作会引发ANR

  1. Activity,Fragment中暴力相应点击事件有可能会导致ANR;
  2. 断点调试时,程序可能会出现ANR无限应;
  3. 主线程做了耗时操作,比如查询数据库数据导致ANR

2.4 四大组件ANR

1.service 前台20s后台200s未完成启动: Timeout executing service

Service Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。

对于Service有两类: 对于前台服务,则超时为SERVICE_TIMEOUT = 20s;对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s

2.BroadcastReceiver的事件(onReceive方法)在规定时间内没处理完:Timeout of broadcast BroadcastRecord

对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s

以BroadcastReviver为例,在onReceive()方法执行10秒内没发生第一种ANR(也就是在这个过程中没有输入事件或输入事件还没到5s)才会发生Receiver timeout,否则将先发生事件无相应ANR,所以onReceive()是有可能执行不到10s就发生ANR的,所以不要在onReceive()方法里面干活

3.ContentProvider

publish在10s内没进行完:timeout publishing content providers

4.Activity

界面如果5秒未响应 Activity not responding。

这种是特别常见的场景吗,发生原因

  • 主线程有耗时操作
  • 复杂布局
  • IO操作
  • 被子线程同步锁block
  • 被Binder对端block
  • Binder被占满导致主线程无法和SystemServer通信
  • 得不到系统资源(CPU/RAM/IO)

从进程角度看发生原因有:

  • 当前进程:主线程本身耗时或者主线程的消息队列存在耗时操作、主线程被本进程的其它子线程所blocked
  • 远端进程:binder call、socket通信

03.ANR流程分析

3.1 触发ANR的步骤

四大组件触发ANR的步骤。大概是:埋下注册超时 ----> 触发超时 ----> 引发超时ANR

3.2 Service的ANR分析

第一步埋下注册超时:

调用startService,在Service进程attach到system_server进程的过程中会调用realStartServiceLocked()方法来注册超时

  • ----> ActiveServices 类 realStartServiceLocked 方法
  • ----> ActiveServices 类 bumpServiceExecutingLocked 方法
  • ----> ActiveServices 类 scheduleServiceTimeoutLocked 方法 , handler发送延迟消息
  • ----> ActivityManagerService 类的 MainHandler 中,有处理handler延迟消息,具体看:SERVICE_TIMEOUT_MSG

第二步触发超时,触发超时分析

在system_server进程AS.realStartServiceLocked()调用的过程会埋下注册超时, 超时没有启动完成则会超时。

那么什么时候会触发超时的引线呢? 经过Binder等层层调用进入目标进程的主线程handleCreateService()的过程。

  • ----> ActivityThread 类 handleCreateService 方法
  • ----> ActivityManagerService 类 serviceDoneExecuting 方法
  • ----> ActiveServices 类 serviceDoneExecutingLocked 方法

第三步引发超时ANR,触发超时分析

介绍了埋下注册超时和触发超时的过程, 如果在超时倒计时结束之前成功拆卸注册超时,那么就没有引发ANR的机会。

但总有些极端情况下无法即时拆除注册超时,导致触发超时了, 其结果就是App发生ANR。

在system_server进程中有一个Handler线程, 名叫”ActivityManager”。当倒计时结束便会向该Handler线程发送 一条信息SERVICE_TIMEOUT_MSG

  • ----> ActivityManagerService 类 handleMessage 方法
  • ----> ActiveServices 类 serviceTimeout 方法

ANR触发流程,可以比喻为埋炸弹和拆炸弹的过程,以启动Service为例。

  1. Service的onCreate方法调用之前会使用Handler发送延时10s的消息,Service 的onCreate方法执行完,会把这个延时消息移除掉。
  2. 假如Service的onCreate方法耗时超过10s,延时消息就会被正常处理,也就是触发ANR,会收集cpu、堆栈等信息,弹ANR Dialog。

service、broadcast、provider 的ANR原理都是埋定时炸弹和拆炸弹原理。

3.3 ANR后系统执行流程

ANR后流程如下

  1. APP发生ANR
  2. 进程接收异常终止信号,开始写入进程ANR信息(当时场景,包含当前线程所有堆栈信息、CPU/IO的使用情况等);
  3. 弹出ANR提示框,提示用户关闭APP或者继续等待;(不同ROM表现不同,有的手机厂商会去掉这个提示框)

appNotResponding介绍

无论ANR的来源是哪里,最终都会走到ProcessRecord中的appNotResponding,这个方法包括了ANR的主要流程。

3.4 那些极端情况不会ANR

具体可以看appNotResponding方法中的下面代码。会发现有一些条件直接执行了return。有哪些极端的情况不会ANR

一长串if else,给出了几种比较极端的情况,会直接return,而不会产生一个ANR。这些情况包括:进程正在处于正在关闭的状态,正在crash的状态,被kill的状态,或者相同进程已经处在ANR的流程中。

3.5 ANR静默和弹窗

看下面的源码分析,还是appNotResponding方法。

  1. 大概的意思是,通过isSilentAnr()判断是否是静默ANR,如果是那么则直接kill杀死app。其实就是后台ANR。
  2. 如果是前台,则是通过handler发送一个消息,创建anr弹窗对象,赋值给message的obj对象。
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
        String parentShortComponentName, WindowProcessController parentProcess,
        boolean aboveSystem, String annotation) {
    synchronized (mService) {
        if (isSilentAnr() && !isDebugging()) {
            kill("bg anr", true);
            return;
        }
        makeAppNotRespondingLocked(activityShortComponentName,
                annotation != null ? "ANR " + annotation : "ANR", info.toString());
        if (mService.mUiHandler != null) {
            Message msg = Message.obtain();
            msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
            msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);
            mService.mUiHandler.sendMessage(msg);
        }
    }
}

那么究竟是怎么判断是后台服务呢?除非另有配置,否则在后台进程中吞下anr并杀死进程。非私有访问仅用于测试。

boolean isSilentAnr() {
    return !getShowBackground() && !isInterestingForBackgroundTraces();
}

再来看看处理弹窗展示的逻辑,具体看ActivityManagerService类的handleMessage方法。what是:SHOW_NOT_RESPONDING_UI_MSG

final class UiHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case SHOW_NOT_RESPONDING_UI_MSG: {
            mAppErrors.handleShowAnrUi(msg);
            ensureBootCompleted();
        } 
    }
}

3.7 处理ANR信息收集

为了方便定位,因此需要收集ANR信息。下面代码还是appNotResponding方法,主要是收集信息的重要代码。

  1. dump很多信息到ANR Trace文件里,下面的逻辑就是选择需要dump的进程。
  2. ANR Trace文件是包含许多进程的Trace信息的,因为产生ANR的原因有可能是其他的进程抢占了太多资源

具体看:AMS.appNotResponding流程代码

  • 输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息。
  • 收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时。
  • 输出当前各个进程的CPU使用情况以及CPU负载情况。
  • 将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录(ANR信息最为重要的信息)。
  • 根据进程类型,来决定直接后台杀掉,还是弹框告知用户。

选择需要dump的进程的逻辑需要大概说下

  • 需要被dump的进程被分为了firstPids、nativePids以及extraPids三类。
  • 拿到需要dump的所有进程的pid后,AMS开始按照firstPids、nativePids、extraPids的顺序dump这些进程的堆栈
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
        (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids,
        nativePids);

3.8 dump进程stack

具体看:AMS.dumpStackTraces流程代码

1、收集firstPids进程的stacks: 第一个是发生ANR进程;第二个是system_server;其余的是mLruProcesses中所有的persistent进程。

2、收集Native进程的stacks。(dumpNativeBacktraceToFile)。依次是mediaserver,sdcard,surfaceflinger进程。

3、收集lastPids进程的stacks: 依次输出CPU使用率top 5的进程;

04.ANR是怎么设计

4.1 什么是输入系统

ANR机制到底是什么,其背后的原理究竟如何,为什么要设计出这样的机制?这些问题时时刻刻会萦绕脑海,而想搞清楚这些,就不得不提到Android自身的 输入系统 (Input System)。

Android自身的 输入系统 是什么?任何与Android设备的交互——称之为 输入事件,都需要通过 输入系统 进行管理和分发;这其中最靠近上层,并且最典型的一个小环节就是View的 事件分发 流程。

4.2 系统进程输入系统

Android系统在启动的时候,会初始化zygote进程和由zygote进程fork出来的SystemServer进程;作为 系统进程 之一,SystemServer进程会提供一系列的系统服务,而InputManagerService也正是由 SystemServer 提供的。

在SystemServer的初始化过程中,InputManagerService(下称IMS)和WindowManagerService(下称WMS)被创建出来;其中WMS本身的创建依赖IMS对象的注入:

// SystemServer.java
private void startOtherServices() {
 InputManagerService inputManager = new InputManagerService(context);
 // inputManager作为WindowManagerService的构造参数
 WindowManagerService wm = WindowManagerService.main(context,inputManager, ...);
}

在 输入系统 中,WMS非常重要,其负责管理IMS、Window与ActivityManager之间的通信,先来看IMS。

IMS服务的作用就是负责输入模块在Java层级的初始化,并通过JNI调用,在Native层进行更下层输入子系统相关功能的创建和预处理。

在JNI的调用过程中,IMS创建了NativeInputManager实例,NativeInputManager则在初始化流程中又创建了EventHub和InputManager:

NativeInputManager::NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper) : mLooper(looper), mInteractive(true) {
    // ...
    // 创建一个EventHub对象
sp<EventHub> eventHub = new EventHub();
// 创建一个InputManager对象
mInputManager = new InputManager(eventHub, this, this);
}

对于整个Native层级而言,其向下负责与Linux的设备节点中获取输入,向上则与靠近用户的Java层级相通信,可以说是非常重要。而在该层级中,EventHub和InputManager又是最核心的两个角色。

首先来说EventHub,它是底层 输入子系统 中的核心类,负责从物理输入设备中不断读取事件(Event),然后交给InputManager,后者内部封装了InputReader和InputDispatcher,用来从EventHub中读取事件和分发事件

InputManager::InputManager(...) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

EventHub建立了Linux与输入设备之间的通信,InputManager中的InputReader和InputDispatcher负责了输入事件的读取和分发,在 输入系统 中,两者的确非常重要。

4.3 组件类型ANR

ANR是一套监控Android应用响应是否及时的机制,可以把发生ANR比作是 引爆炸弹,那么整个流程包含三部分组成:

  1. 埋定时炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。
  2. 拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。
  3. 引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后是炸毁目标。

4.4 Input类型ANR

  • Input类型的ANR在日常开发中更为常见且更复杂,比如用户或者测试反馈,点击屏幕中的UI元素导致「卡死」。
  • 少数情况下开发者能够很快定位到问题,但更常见的情况是,该问题是 随机 且 难以复现 的,导致该问题的原因也更具有综合性,比如低端设备的系统本身资源已非常紧张,或者多线程相互持有彼此需要的资源导致 死锁 ,亦或其它复杂的情况,因此处理这类型问题就需要开发者对 输入系统 中的ANR机制有一定的了解。
  • 和组件类ANR不同的是,Input类型的超时机制并非时间到了一定就会爆炸,而是处理后续上报事件的过程才会去检测是否该爆炸,所以更像是 扫雷 的过程。
  • 扫雷,对于 输入系统 而言,即使某次事件执行时间超过预期的时长,只要用户后续没有再生成输入事件,那么也不需要ANR。
  • 而只有当新一轮的输入事件到来,此时正在分发事件的窗口(即App应用本身)迟迟无法释放资源给新的事件去分发,这时InputDispatcher才会根据超时时间,动态的判断是否需要向对应的窗口提示ANR信息。
  • 这也正是用户在第一次点击屏幕,即使事件处理超时,也没有弹出ANR窗口,而当用户下意识再次点击屏幕时,屏幕上才提示出了ANR信息的原因。
  • 由此可见,组件类ANR和Input ANR原理上确实有所不同;除此之外,前者是在ActivityManager线程中处理的ANR信息,后者则是在InputDispatcher线程中处理的ANR

4.5 事件分发异步机制

  • 先抛出一个新的问题,对处于system_server进程Native层级的 事件分发 而言,其向下与 应用进程 的通信的过程应该是同步还是异步的?
    • 对于读者而言,不难得出答案是异步的,因为两者之间双向通信的建立是通过SocketPair,并且,因为system_server中InputDispatcher对事件的分发实际上是一对多的,如果是同步的,那么一旦其中一个应用分发超时,那么InputDispatcher线程自然被卡住,其永远都不可能进入到下一轮的事件分发中,扫雷 机制更是无从谈起。
    • 因此,与应用进程中事件分发不同的是,后者我们通常可以认为是在主线程中同步的,而对于整个 输入系统 而言,因为涉及到 系统进程 与多个 应用进程 之间异步的通信,因此其内部的实现更为复杂。
    • 因为事件分发涉及到异步回调机制,因此InputDispatcher需要对事件进行维护和管理,那么问题就变成了,使用什么样的数据结构去维护这些输入事件比较合适。
  • 三个队列
    • InputDispatcher的源码实现中,整体的事件分发流程共使用到3个事件队列:
    • mInBoundQueue:用于记录InputReader发送过来的输入事件;
    • outBoundQueue:用于记录即将分发给目标应用窗口的输入事件;
    • waitQueue:用于记录已分发给目标应用,且应用尚未处理完成的输入事件。
    • 笔者通过2轮事件分发的示例,对三个队列的作用进行简单的梳理。
  • 第一轮事件分发
    • 首先InputReader线程通过EventHub监听到底层的输入事件上报,并将其放入了mInBoundQueue中,同时唤醒了InputDispatcher线程。
    • 然后InputDispatcher开始了第一轮的事件分发,此时并没有正在处理的事件,因此InputDispatcher从mInBoundQueue队列头部取出事件,并重置ANR的计时,并检查窗口是否就绪,此时窗口准备就绪,将该事件转移到了outBoundQueue队列中,因为应用管道对端连接正常,因此事件从outBoundQueue取出,然后放入了waitQueue队列,因为Socket双向通信已经建立,接下来就是 应用进程 接收到新的事件,然后对其进行分发。
    • 如果 应用进程 事件分发正常,那么会通过Socket向system_server通知完成,则对应的事件最终会从waitQueue队列中移除。
  • 第二轮事件分发
    • 如果第一轮事件分发尚未接收到回调通知,第二轮事件分发抵达又是如何处理的呢?
    • 第二轮事件到达InputDispatcher时,此时InputDispatcher发现有事件正在处理,因此不会从mInBoundQueue取出新的事件,而是直接检查窗口是否就绪,若未就绪,则进入ANR检测状态。
  • 以下几种情况会导致进入ANR检测状态:

    1、目标应用不会空,而目标窗口为空。说明应用程序在启动过程中出现了问题; 2、目标Activity的状态是Pause,即不再是Focused的应用; 3、目标窗口还在处理上一个事件。

  • 读者需要理解,并非所有「目标窗口还在处理上一个事件」都会抛出ANR,而是需要通过检测时间,如果未超时,那么直接中止本轮事件分发,反之,如果事件分发超时,那么才会确定ANR的发生。
    • 这也正是将Input类型的ANR描述为 扫雷 的原因:这里的扫雷是指当前输入系统中正在处理着某个耗时事件的前提下,后续的每一次input事件都会检测前一个正在处理的事件是否超时(进入扫雷状态),检测当前的时间距离上次输入事件分发时间点是否超时。如果前一个输入事件,则会重置ANR的timeout,从而不会爆炸。
    • 至此,输入系统 检测到了ANR的发生,并向上层抛出了本次ANR的相关信息。

05.ANR监控排查思路

5.1 ANR监控整体思路

Android系统监测ANR的核心原理是消息调度和超时处理。

5.2 目前流行方案

1、使用FileObserver监听 /data/anr/traces.txt的变化。缺点:高版本ROM需要root权限。解决方案:海外Google Play服务、国内Hardcoder

2、监控消息队列的运行时间(WatchDog)

  • 利用主线程的消息队列处理机制,应用发生卡顿,一定是在dispatchMessage中执行了耗时操作。通过给主线程的Looper设置一个Printer,打点统计dispatchMessage方法执行的时间,如果超出阀值,表示发生卡顿,则dump出各种信息,提供开发者分析性能瓶颈。
  • 为卡顿监控代码增加ANR的线程监控,在发送消息时,在ANR线程中保存一个状态,主线程消息执行完后再Reset标志位。如果在ANR线程中收到发送消息后,超过一定时间没有复位,就可以任务发生了ANR。

5.3 如何查看ANR日志

如何查看ANR信息?

  1. 抓取bugreport。adb shell bugreport > bugreport.txt
  2. 直接导出/data/anr/traces.txt文件。adb pull /data/anr/traces.txt

5.4 导出ANR信息

在AS的Terminal中,使用: adb pull data/anr/traces.txt 要存储在本地的路径。导出上面提到的ANR现场信息文件;导出来后,便可对文件内容进行详细分析:从CPU、IO、锁冲突等原因思考;

导出ANR生成文件遇到的问题

  1. 输入:adb pull data/anr/traces.txt /Users/didi/yc 报错
  2. 报错日志:adb: error: failed to stat remote object 'data/anr/traces.txt': No such file or directory
  3. 为何会出现这种情况:查了些资料,发现厂商有对这块做优化。以前anr一直放在traces文件中,多次出现有覆盖的问题。高版本厂商做了优化,会根据时间戳分别生成一个文件,打包导出。
  4. 遇到权限问题:在查看data文件夹时,看到权限不允许。ls: .: Permission denied,这个该怎么办呢?
  5. 使用 adb bugreport 导出文件:此命令会导出一个zip压缩包,解压后在FS/data/anr目录下就可以看到traces文件了。adb bugreport 命令也可以指定文件导出目录

5.5 ANR具体如何分析

ANR问题是由于主线程的任务在规定时间内没处理完任务,而造成这种情况的原因大致会有一下几点:

  1. 主线程在做一些耗时的工作导致线程卡死
  2. 主线程被其他线程锁
  3. cpu被其他进程占用,该进程没被分配到足够的cpu资源。

查找ANR发生的位置:在ANR日志文件中,你会看到类似下面的信息:

----- pid 1234 at 2021-01-01 12:34:56 -----
Cmd line: com.example.app
...

查找主线程堆栈跟踪:在ANR日志文件中,你会看到类似下面的信息【主要是查找 main 线程】,这些信息显示了主线程("main")的堆栈跟踪。你可以查看堆栈跟踪中的方法调用,以确定ANR发生的位置。

DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x12345678 self=0xabcdefg
  | sysTid=1234 nice=0 cgrp=default sched=0/0 handle=0xabcdefg
  | state=S schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100
  | stack=0x1abc0000-0x1abc4000 stackSize=8MB
  | held mutexes=
  at com.example.app.MainActivity$1.onClick(MainActivity.java:123)
  - waiting to lock <0x98765432> (a java.lang.Object) held by thread 2
  ...

分析堆栈跟踪:根据堆栈跟踪中的方法调用,你可以确定导致ANR的代码位置。通常,你需要查找长时间运行的操作、阻塞的I/O操作、网络请求等。

解决ANR问题:一旦你确定了导致ANR的代码位置,你可以采取相应的措施来解决问题。例如,将耗时操作移至后台线程、优化代码逻辑、减少I/O操作等。

5.6 避免ANR操作方案

  1. 将所有耗时操作,比如访问网络,Socket通信,查询大量SQL 语句,复杂逻辑计算等都放在子线程中去
  2. 然后通过handler.sendMessage、runOnUIThread、AsyncTask 等方式更新UI。无论如何都要确保用户界面作的流畅度。如果耗时操作需要让用户等待,那么可以在界面上显示度条。
  3. 在一些同步的操作主线程有可能被锁,需要等待其他线程释放相应锁才能继续执行,这样会有一定的ANR风险,对于这种情况有时也可以用异步线程来执行相应的逻辑。另外, 要避免死锁的发生。
  4. 调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
  5. 使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。

四大组件ANR避免

  1. Activity的onCreate和onResume回调中尽量避免耗时的代码
  2. BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。
  3. 各个组件的生命周期函数都不应该有太耗时的操作,即使对于后台Service或者ContentProvider来讲
  4. 应用在后台运行时候其onCreate()时候不会有用户输入引起事件无响应ANR,但其执行时间过长也会引起Service的ANR和ContentProvider的ANR

5.7 ANR监控方案实践

5.8 模拟测试ANR

直接在activity的onCreate方法中添加一个耗时任务,如下所示:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Thread.sleep(60000);
}

ANR检测结果,打印如下所示:

05.参考资料

  • 需要列出方案设计过程的文档,包括但不局限于PM需求文档,技术参考文档等。
  • 一波深入的Android 性能优化【必看】
    • https://mp.weixin.qq.com/s/RSH9EF9wFN6bqYiSNpbx8g
  • 微信ANR监测方案
    • https://mp.weixin.qq.com/s/fWoXprt2TFL1tTapt7esYg
  • 理解Android ANR的触发原理
    • http://gityuan.com/2016/07/02/android-anr/
  • 微信Android客户端的ANR监控方案
    • https://mp.weixin.qq.com/s/fWoXprt2TFL1tTapt7esYg
  • 今日头条 ANR 优化实践系列
    • https://juejin.cn/post/6947986170135445535
    • https://juejin.cn/post/6942665216781975582
  • 今日头条 ANR 优化实践系列
    • https://mp.weixin.qq.com/s/_Z6GdGRVWq-_JXf5Fs6fsw
  • Android SIGQUIT(3) 信号拦截与处理
    • https://mp.weixin.qq.com/s?__biz=Mzg4MjE5OTI4Mw==&mid=2247494779&idx=1&sn=b8c84e8e8389543cfbda93cbadd1593c&chksm=cf58f3e7f82f7af1ffff70e1c9f2d6a6a0d0a4b5e4f3f3dd42e9d79fd950bfa32913942ec6d3&scene=178&cur_album_id=2495132597375975425#rd
  • 今日头条 ANR 优化实践系列 - 监控工具与分析思路
    • https://juejin.cn/post/6942665216781975582
  • 稳定性优化:ANR监控方案
    • https://juejin.cn/post/7371779128477302823
贡献者: yangchong211
上一篇
03.Native崩溃治理实践
下一篇
05.CPU消耗优化实践