
- 造成系统卡顿的根本原因 ⭐⭐⭐
- 什么是 ANR ?导致原因有哪些?⭐⭐⭐⭐⭐
- 如何避免发生 ANR ?⭐⭐⭐⭐
- 分别说说Activity、BroadcastReceiver、Serice最长可耗时时间为多少?⭐⭐⭐⭐⭐
- 谈谈你项目中避免ANR的一些经验⭐⭐
- 有什么方法和工具可以分析ANR ⭐⭐⭐
卡顿
造成卡顿的原因
造成卡顿的原因很多,如代码设计、内存占用、CPU使用率、IO操作等。这些因素造成的卡顿很难被追溯,所以发生卡顿的时候很难定位到此次卡顿的根因。
CPU使用情况获取的方法
造成卡顿因素确实很多,但无论是哪个因素,最终造成的卡顿都会反映到CPU时间上。CPU时间包含用户时间和系统时间:
- 用户时间:执行用户态应用程序代码所消耗的时间。
- 系统时间:执行内核态系统调用所消耗的时间,包括I/O、锁、中断和其它系统调用所消耗的时间。
那么,有什么方法可以获取CPU使用率呢?《Android系统性能监控最全面分析与实践》一文,详细介绍如何获取CPU整体使用率、每个核的使用率、占用CPU使用率最高的5个进程,这三个指标。在此,简单介绍获取CPU使用情况的方法:
- top指令
top指令非常强大,如下图我们可以知道,系统当前有432个进程,总CPU使用率为800%,其中697%处于空闲状态,其中占用CPU使用率最高的是system_server进程,占用了23.6%的CPU。由此可见top指令可以监视系统整体和各个进程的CPU使用率,但无法获取每个核的使用率。
- ps指令
ps(Process Status的缩写)指令是最基本同时也是非常强大的进程查看指令,可以查询系统内每个进程的运行状态、CPU使用率、占用的内存等,因为现在需要获取CPU使用率,可以执行:
1 | ps -eo pid,%cpu,CMD --sort=-%cpu |head -n 6 |
ps指令耗时是毫秒级别,优于top指令,然而也无法获取整体或者每个核的使用率
- dumpsys cpuinfo
Android提供的dumpsys工具可以用于查看感兴趣的系统服务信息与状态,dumpsys cpuinfo可以用来查看安卓系统当前的cpu使用情况,执行的速度是非常快的,因为其打印的内容并非执行该命令的时候临时去更新的,而是系统内部有着相关的机制会在特定情况更新cpuinfo。为方便查看下图经过裁剪,我们可以看到系统整体CPU使用率和前5个进程的使用率,还缺少每个核的使用率,因此还不是最优的方案。不过在3.3小节dumpsys cpuinfo另有他用,其实现原理也可见下文介绍。
- 读取/proc/stat文件
读取/proc/stat文件来计算CPU使用率是目前最常见最有用的方法,Android源码获取CPU使用率也同样采用这种方法。
- 读取/proc/[pid]/stat
该文件包含了某一个进程的所有活动信息,该文件中所有值都是从系统启动开始累计到当前时刻,可通过改文件计算每个进程的CPU使用率。
什么是ANR
Application Not Respongding,简称ANR,翻译为中文即是“应用程序无响应”。手机使用过程中,偶尔就会出现一个提示弹窗,说XXX软件无响应,让用户选择继续等待还是关闭软件。这就是发生了ANR。
Activity、BroadcastReceiver、Serice发生ANR的超时时间
Android系统会监控程序的响应情况,每个组件都有规定的发生ANR时间,具体是:
- Activity在5秒内无响应用户操作;
- BroadCastReceiver在10秒内未执行完所设置的操作,如果应用在后台,则延长到60秒;
- Service在20秒没有执行完操作,如果应用在后台,则延长到200秒;
常见的几种可能发生ANR的情况
- 主线程进行IO操作,引起阻塞;
- 主线程执行耗时操作;
- 主线程有错误操作,如Thread.sleep等直接导致主线程阻塞;
- 执行速度缓慢的广播接收器;
- 出现死锁;
- 主线程使用Binder和另一个进程通讯,另一个进程需要很长时间才返回;
- 主线程使用 SharedPrederence:SharedPrederence 调用 apply 方法,会创建一个等待锁放到 QueuedWork 中,并将真正数据持久化封装成一个任务放到异步队列中执行,任务执行结束会释放锁。Activity onStop 以及 Service 处理 onStop,onStartCommand 时,执行 QueuedWork.waitToFinish() 等待所有的等待锁释放。其实不管是 commit 还是 apply,都是在主线程进行 IO 操作,那么都是可能会产生 ANR 的。
当ANR发生时,系统单独起一个AnrConsumer线程去记录当前应用的堆栈信息、CPU、内存占用、进程列表中各个线程的用户时间和系统时间占比等情况,输出logcat,并保存到/data/anr/traces.txt文件,然后再根据是否是前台进程判断是否弹出ANR弹窗。
改善ANR的方法
ANR出现的类型一般分为两种:
- 主线程耗时操作导致;
- CPU、内存、IO操作占用资源太高导致;
因此,避免ANR的核心就是主线程中少执行耗时的代码,以及避免CPU使用率太高或者内存占用太高;
- 耗时操作在子线程里处理,如使用AsyncTask、线程池、IntentService、RxJava等;
- 在Activity的onCreate()和onResume()等方法少执行耗时的代码。 BroadcastReceiver中onReceive()也同理;
- 查看对主线程所需资源持有的锁,将持有锁的时间降到最少,或者评估应用是否需要持有锁。如果需要使用锁来确定何时根据工作线程的处理情况来更新界面,建议使用AsyncTask机制在工作线程和主线程之间进行通信;
- 如果程序在后台处理用户输入或者执行耗时操作,建议使用ProgressBar等控件可以让用户感知当前进度;
卡顿分析的方法和工具
当ANR发生时,首先查看应用程序的堆栈信息,看看是否有死锁发生。接着查看ANR日志里CPU、IO、GC(垃圾回收)、system server等信息,进一步确认是否出现了CPU高使用率或者IO阻塞问题,或者大量的GC导致系统卡顿。以下介绍的方法或者工具仅起普及作用,具体的做法不属于本文内容,后续计划出一个Android项目实践的专栏再做详细介绍,敬请关注。卡顿优化可以:《 深入探索Android卡顿优化》
- traces.txt文件
当一个进程发生了ANR,系统会在/data/anr目录下创建一个traces.txt文件,可以通过该文件分析定位出现ANR的原因。或者通过FileObserver来监听该文件的变化。
- ANR-WatchDog 高版本的Android系统需要root权限才能对/data/anr/trace.txt文件进行监听,因此可以使用ANR-WatchDog,这是一种非侵入式的ANR监控组件,可以用于线上ANR的监控。具体使用过程和原理参:《 深入探索Android卡顿优化》
- 监听 SIGQUIT 信号
这个来源于微信的 ANR 监控方案,这个原理是,当发生 ANR 的时候,系统会向发生 ANR 的进程发送一个 SIGQUIT 信号来 dump 进程堆栈。发生 ANR 的进程一定会收到 SIGQUIT 信号,但收到 SIGQUIT 信号的进程并不一定发生了 ANR。原因是发生了 ANR 时,系统并不只是会 dump 发生了 ANR 的进程,还会 dump 一些其他进程、比如 system_server 进程等,所以存在误报的情况。解决办法就是在监听 SIGQUIT 信号后,在 20s (20s 是 ANR dump 的 timeout 时间)内不断的轮询自己是否被设置了 NOT_RESPONDING 标志位,一旦发现有这个 flag,那么就可以认定是发生了一次 ANR。
- CPU Profiler
gperftools是google开发的一款非常实用的工具集,CPU profiler是其中的工具之一,用于程序性能分析和监控。
- Systrace
Systrace 是第一代系统级性能分析工具,Systrace利用了 Linux 的ftrace调试工具。基本原理是在代码里加了一些性能监控的埋点。它将 Android 系统和 App 的运行信息以图形化的方式展示出来。具体使用参考:《Android Systrace 基础知识 – Systrace 简介》
- Perfetto
Perfetto是新一代性能分析全栈工具,相比 Systrace 最大的改进是可以支持长时间数据抓取,也都是基于 Linux 内核的 Ftrace 机制实现了用户空间与内核空间关键事件的记录。
- Android Studio的CPU Profiler
Android Studio集成的CPU分析工具,可以通过图形的形式展示执行时间和调用栈,信息也很全面,可以实时检查应用程序的CPU使用率和线程使用情况,还有函数调用及其开销时间。AS CPU Profiler 集成了多个性能分析工具:Perfetto、Simpleperf、Java Method Trace,它自然而然具备了这些工具的全部或部分功能。
- StrictMode StrictMode是Andraoid性能调优的利器,从名字上就知道,这是“严苛模式”。StrictMode不仅可以分析卡顿,也常用于捕获在应用主线程中发生的磁盘I/O、网络访问违例等问题,有助于开发应用时发现主线程上的意外 I/O 操作。StrictMode主要检测两大问题:线程策略(TreadPolicy)和VM策略(VmPolicy):
- ThreadPolicy线程策略:
- 自定义的耗时调用,使用detectCustomSlowCalls()开启;
- 磁盘读取操作,使用detectDiskReads()开启;
- 磁盘写入操作,使用detectDiskWrites()开启;
- 网络操作,使用detectNetwork()开启。
- VmPolicy虚拟机策略:
- Activity泄漏,使用detectActivityLeaks()开启;
- 未关闭的Closable对象泄漏,使用detectLeakedClosableObjects()开启;
- 泄漏的Sqlite对象,使用detectLeakedSqlLiteObjects()开启;
- 检测实例数量,使用setClassInstanceLimit()开启。
- ThreadPolicy线程策略:
- 其他的工具还有:Facebook开源库Profilo、腾讯的PerfDog等。