[Android] [ANR的原理、分析、实战]

ANR定义及原理

ANR
Application Not Responding, 即应用无响应 。

原理

由于Android有主线程设计,因此,凡系统向应用主线程发消息,让主线程去处理,超过时间,应用主线程未返回响应,就会导致ANR。
系统会计时,规定时间内,主线程无消息返回,系统就认为是ANR,并做相应处理(弹窗,甚至杀掉进程)。
(背景知识: Android中应用进程模型,线程模型,ActivityThread主线程,及其中的Handler内部对象(专门用于与系统AMS/WMS等进程消息通信用)。)

类型

系统都设计了哪些ANR:
1:KeyDispatchTimeout(5 seconds) –主要类型
按键或触摸事件在特定时间内无响应
TODO: 其它事件类型呢?
(背景知识: 系统输入事件派发流程)

2:BroadcastTimeout(10 seconds)
BroadcastReceiver 在特定时间内无法处理完成

3:ServiceTimeout(20 seconds)
Service 在特定的时间内无法处理完成

ContentProvider
只是一般很少见。
广播和服务,在后台启动的时候,时间会是 60s,于是我们在分析问
题时候,尽量将 anr 的 log 分析,将查看的 log 从发生 anr 的时刻向
前找 1 分钟。 ??

根本的应对方法

主线程中少做耗时操作

那么哪些算UI主线程呢?

(其实列不完的,反正老司机碰到代码,一眼就能分辨出主线程的了,只是注意守则,主线程一定少做耗时操作(TODO: 这是代码检查必备的点))
四大组件的生命周期,除了独立进程的Service,其余都是执行在主线程的。
具体如:
Activity的所有生命周期回调都是执行在主线程的.
Activity:onCreate(), onResume(), onDestroy(), onKeyDown(),
onClick(),etc
Service默认是执行在主线程的.
BroadcastReceiver的onReceive回调是执行在主线程的.

AsyncTask的回调中除了doInBackground, 其他都是执行在主线程的.
AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(),
onCancel,etc

Mainthread handler: handleMessage(), post*(runnable r), etc
没有使用子线程的looper的Handler的handleMessage, post(Runnable)是执行在主线程的.

View的post(Runnable)是执行在主线程的.

Other 。。。。。。

那么,启用子线程的方法有哪些呢?

专题 - Android子线程实现代码.md
3.2.1 启Thread方式
3.2.2 使用AsyncTask
3.2.3 HandlerThread
3.2.4 IntentService
3.2.5 Loader
3.2.6 特别注意
使用Thread和HandlerThread时, 为了使效果更好, 建议设置Thread的优先级偏低一点:
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
因为如果没有做任何优先级设置的话, 你创建的Thread默认和UI Thread是具有同样的优先级的, 你懂的. 同样的优先级的Thread, CPU调度上还是可能会阻塞掉你的UI Thread, 导致ANR的.

主线程的判定方法

如何判断是否此段代码在主线程:
方法一:使用 Looper 类判断
Looper.myLooper() != Looper.getMainLooper()
方法二:通过查看 Thread 类的当前线程
Thread.currentThread() == Looper.getMainLooper().getThread()
方法三:打印 Log,去看线程 id,看是否和进程号一样,一样是主线

耗时的工作有哪些

数据库操作,I/O,网络等
打开wifi(因为跨进程操作,有可能wifiserver那边处理超时)
读写文件(操作是个iowait负载较大的行为,很容易anr)
查询语句(在数据库内容暴增之后,出现严重的性能问题,产生anr)
SharedPreferences 的commit操作,本身是个等待操作,在我们activity退出时,有时保存当前状态,方便恢复,会使用commit,如果我们也有一个此时在操作,因为这个操作是有个锁,引起anr
list的排序。(算法的质量,以及当列表数目激增后,是否能快速算完,是个耗时操作,会产生anr)
bitmap的运算,(旋转,特效处理等)
ThreadPoolExecutor 线程池,当我们从这里获取一个线程时候,如果此时所有线程都被使用,就只能迫使等待,此时会出现anr
ANR避免情况实战

1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)
2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)
3、避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
4.通常100到200毫秒就会让人察觉程序反应慢,为了更加提升响应,
如果程序正在后台处理用户的输入,建议使用让用户得知进度,比如使用ProgressBar控件,程序启动时可以选择加上欢迎界面,避免让用户察觉卡顿,使用Systrace和TraceView找出影响响应的问题。

CPU满负荷, I/O阻塞的
I/O阻塞一般来说就是文件读写或数据库操作执行在主线程了, 也可以通过开辟子线程的方式异步执行.
内存不够用的
增大VM内存, 使用largeHeap属性, 排查内存泄露(这个在内存优化那篇细说吧)等.

分析方法及解决
出现anr的时候,如何定位,分析问题呢?

1:查看 bug 上面的描述信息,看下堆栈,cpu 使用情况。
首先我们要确定的是否此 log 有效。
确认依据:看 bug 的描述
看 bug 提供的描述信息,堆栈异常是否和标题一致。
● 如果不一致,此问题直接给出分析结果,转出对应模块负责。
● 如果一致,我们需要去看 trace 文件,查看里面的出现的栈信息是否和描述的一致。(通过看测试贴出的 anr 栈里面的时间信息,和我们的 trace 的时间是否一致,一致,此份 trace 有效)
● 如果不一致,我们需要去看 log,搜索 am_anr,看下是否在测试贴出的 anr 栈的时间信息处,是否发生了 anr,如果有,此份 log有效,可以进行分析。
● 如果我们看到栈信息,去看对应代码,发现此处是个跨进程调用,循环调用,查询语句,那么出现 anr 的原因,可以去怀疑这里耗时,等待。
● 如果是跨进程调用,那么需要看下对应进程的堆栈,看下请求是否响应,是否在等待锁。(搜索下栈里面是否有 block)
关于 app 出现的 anr,不能只看栈调用了系统方法,就转出给 frm,应该拿到手里,先做一些判断。
● 判断 cpu 的使用情况,主要关注前三四个即可。
● 判断当前负载如何
负载如果在 6-7 以下,属于正常,如果高于 8,在 11 以上,可以表明,当前系统负载过重,系统出现问题,需要再次定位。
负载过高,需要调查具体哪种原因,比如是 iowait 比重过高,系统频繁的读写操作引起。
负载一般,正常,那么就要去看下是否写的代码处会产生挂起等待,导致 anr
● 关注 log 信息,在发生 anr 的前一分钟内,看下系统在忙于哪些事情。
主要就是通过看 log 输出,查看下当前系统在干什么,核心可以围绕着 ams wms input 去看。
比如之前 systemui 出现的 time_tick 消息广播 anr ,由于我们的time_tick 是个频繁调用的广播,正常情况出现不了 anr 的,如果出现,我们需要怀疑的是系统的 cpu 当前到底在忙于做什么。比如常见的此处发生的时候,伴随着大量的 lowmem kill,那么问题可能会是系统瓶颈,或者 lowmem 配置不当,虚拟机内存配置不当等等,如果发现是此类问题,得出结论,优化系统性能。内存问题,可以去看下dumpsys meminfo查看下每个应用的内存占用情况。

log 查找
搜索 am_anr 会搜到一段异常信息。
iowait 故障:
http://blog.csdn.net/lixin88/article/details/54345842

DDMS:Start Methord Tracing:
使用traceview的方式,获取每个方法的耗时。
BlockCanary:
加入三方库,完成自动检测anr
堆栈信息trace方法
adb shell bugreport 不仅可以获得 trace.txt,还可以获得当时的 memory 信息,以及其他进程信息。
手动获取trace.txt:
set enforce 0 (不执行这个,会无法写入文件)
chmod 777 /data/anr
rm /data/anr/traces.txt
ps
kill -3 PID
adb pull data/anr/traces.txt ~/traces.txt
Trace文件及解析
Android框架分析系列之Android traces.txt文件 - Android移动开发技术文章_手机开发 - 红黑联盟
ANR 原理与实战技巧 - CSDN博客
堆栈信息trace.txt里面都有什么,关于ANR要看的

Jit thread pool worker thread 0 实时编译代码线程池**
JDWP 调试线程
HeapTaskDaemon 内存管理线程

所有Binder开头的线程。这里为Binder:5859_1 关键,这里Binder说明是一个跨进程的线程,于是乎我们调用AMS WMS等等一系列服务方法,都会在这个里面的堆栈体现出来,然后对应的system_server进程的trace.txt里面就有对应的响应的Binder在执行具体代码,有时我们的anr会在这里,就会是跨进程调用等待引起的anr。

来源: https://blog.csdn.net/a332324956/article/details/77800315
ANR分析实战
ANR 原理与实战技巧 - CSDN博客
更多,待学习:
Android App优化之ANR详解 - 简书
Android的ANR详解(原因和方案) - 世上只有一种英雄主义

猜你喜欢

转载自blog.csdn.net/Hendy_Raw/article/details/88767514