ANR 原理与实战技巧

首先,每天看到不断有新人关注着这个公众号,心情很是愉悦。一种认可,一种信任,也是我前进的动力。感谢大家的支持与鼓励。
如果你喜欢这篇文章,或者喜欢这个图片,或者有所感悟,或者仅仅因为喜欢代码GG,分享此文到技术群里,以便此文的价值最大化。
再次感谢大家的鼎力支持,抱拳拱手,难表胜意。俯首向前,继续码文,聊表心意。

《ANR 原理与实战技巧》

00
手机用用,就卡卡卡。莫名其妙的出现一堆程序无响应,欲哭无泪。这是为什么呢?因为你用的android手机。
android手机,为了让手机卡的不成样子,还想让用户知道,就发明了ANR弹框。弹框就弹框,一般的继续等待都是无果,只有结束之才能解决。就像电脑卡死之后,任务管理器启动不起来,想禁止某个进程,徒劳无返。今天我们来唠唠嗑,看看ANR到底是 何方妖怪。
01
ANR:Application Not Responding,即应用无响应 。简洁有力,直奔主题,不做过多解释。怎么产生的呢?
android设计了一种机制,认为一些阻挡它生命周期的返回,不能无限制下去。比如一个点击触屏动作,android系统就计个时,希望你5s内完成动作,如果你5s还没返回,android系统就会认为你傻了,处理这么久还不返回,android系统就干脆弹个框个,给用户说下,这个过程太长了,你等不等,你最好不要等,把它干掉。android默认写入的按键超时配置为:
static final int KEY_DISPATCHING_TIMEOUT = 5*1000

系统都设计了哪些ANR:
1:KeyDispatchTimeout(5 seconds) –主要类型
按键或触摸事件在特定时间内无响应
2:BroadcastTimeout(10 seconds)
BroadcastReceiver 在特定时间内无法处理完成
3:ServiceTimeout(20 seconds)
Service 在特定的时间内无法处理完成
除此之外,还有 ContentProvider,只是一般很少见。

广播和服务,在后台启动的时候,时间会是 60s,于是我们在分析问
题时候,尽量将 anr 的 log 分析,将查看的 log 从发生 anr 的时刻向
前找 1 分钟。
02
UI线程(主线程)就干简单轻量的事情,主要维护和system_server的通信交互,耗时的就交给其他线程(new Thread 或者AsyncTask),如果你干了比较耗时的事情,从而导致system_server跟你聊天的时候,你不在线,那么你就危险了,system_server里面就开始给你倒计时了,时间一到,它就认为你可以去 go to hell,然后你就出局了。所以,各司其职,主线程主要搞好和系统的关系,系统是个急性子,没事就弹框搞掉你,有点监工的意思。
那么哪些算UI主线程呢?
Activity:onCreate(), onResume(), onDestroy(), onKeyDown(),
onClick(),etc
AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(),
onCancel,etc
Mainthread handler: handleMessage(), post*(runnable r), etc
Other

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

扯了这么多犊子,我们继续扯。。
03
出现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查看下每个应用的内存占用情况。
04

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

线程的状态:
ThreadState (defined at “dalvik/vm/thread.h “)
THREAD_UNDEFINED = -1, /* makes enum compatible with int32_t /
THREAD_ZOMBIE = 0, /
TERMINATED /
THREAD_RUNNING = 1, /
RUNNABLE or running now /
THREAD_TIMED_WAIT = 2, /
TIMED_WAITING in Object.wait() /
THREAD_MONITOR = 3, /
BLOCKED on a monitor /
THREAD_WAIT = 4, /
WAITING in Object.wait() /
THREAD_INITIALIZING= 5, /
allocated, not yet running /
THREAD_STARTING = 6, /
started, not yet on thread list /
THREAD_NATIVE = 7, /
off in a JNI native method /
THREAD_VMWAIT = 8, /
waiting on a VM resource /
THREAD_SUSPENDED = 9, /
suspended, usually by GC or debugger
*/
log 查找
搜索 am_anr 会搜到一段异常信息。
iowait 故障:
http://blog.csdn.net/lixin88/article/details/54345842
手动获取trace.txt:
setenforce 0 (不执行这个,会无法写入文件)
chmod 777 /data/anr
rm /data/anr/traces.txt
ps
kill -3 PID
adb pull data/anr/traces.txt ~/traces.txt

DDMS:Start Methord Tracing:
使用traceview的方式,获取每个方法的耗时。

BlockCanary:
加入三方库,完成自动检测anr

05
trace.txt里面都有什么:

《ANR 原理与实战技巧》
《ANR 原理与实战技巧》

《ANR 原理与实战技巧》

Jit thread pool worker thread 0 实时编译代码线程池****

JDWP 调试线程

HeapTaskDaemon 内存管理线程

《ANR 原理与实战技巧》

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

实战:
1按键响应超时

《ANR 原理与实战技巧》

从 LOG 可以看出 ANR 的类型,CPU 的使用情况,如果 CPU 使用量接近 100%,说明当前设备很忙,有可能是 CPU 饥饿导致了 ANR.如果 CPU 使用量很少,说明主线程被 BLOCK 了。如果 IOwait 很高,说明 ANR 有可能是主线程在进行 I/O 操作造成的。除了看 LOG,解决 ANR 还得需要 trace.txt 文件,adb

shell bugreport 不仅可以获得 trace.txt,还可以获得当时的 memory 信息,以及其他进程信息。

2广播接收超时

《ANR 原理与实战技巧》

我们去看下堆栈信息:

《ANR 原理与实战技巧》

可以看到堆栈明显的指向,这段代码是个数据操作,以及数据库写入动作。

《ANR 原理与实战技巧》

问题定位。

**3ContentResolver **

《ANR 原理与实战技巧》

继续去看堆栈:

《ANR 原理与实战技巧》

找到代码,去看,去修改:

《ANR 原理与实战技巧》

4在 UI 线程进行网络数据的读写

《ANR 原理与实战技巧》

主要看main:

《ANR 原理与实战技巧》

如果它卡在一个方法的时候,等待,我们就要去找,是否有异步线程操作,主线程在等待结果的状态。之前看过一个问题是:主线程做了最大延时10s,来等待一个异步的结果,一般情况下,异步结果很快出来,但是异常情况,非常慢。最后定位的原因是异步操作,是基于数据库里面的图书列表,如果网络上推送下来很多书,然后查询数据,遍历以及整理数据,非常耗时,导致的anr。

《ANR 原理与实战技巧》

5Memoryleak/Thread leak

《ANR 原理与实战技巧》

继续分析

《ANR 原理与实战技巧》

《ANR 原理与实战技巧》

《ANR 原理与实战技巧》

6Broadcast timeout

《ANR 原理与实战技巧》

《ANR 原理与实战技巧》

《ANR 原理与实战技巧》

7普通的anr:

《ANR 原理与实战技巧》

通常情况下我们只需要关注 total 的数值 ,total 数值高的情况下

关注一下 cpu 占用高的进程

字段及意义 :

user : CPU 在用户态的运行时

kernel : CPU 在内核态运行的时间

idle : CPU 空闲时间,不包括 iowait 时间

iowait : CPU 等待 I/O 操作的时间

irq : CPU 硬中断的时间

softirq : CPU 软中断的时间

minor/major: 表示页错误次数 , 如果 ANR 发生时发现 CPU 使用率中 iowait

占比很高,可以通过查看进程的 major 次数来推断是哪个进程在进行磁

盘 I/O 操作

“+” ,说明该进程或线程是在最后两次 CPU 使用率采样时间段内新建的;

反之如果是“ -” ,说明该进程或线程在采样时间段内终止了

《ANR 原理与实战技巧》

一个数目变大后,会严重耗时的地方:

《ANR 原理与实战技巧》
《ANR 原理与实战技巧》

这种一般需要注意,application的oncreate里面,不要写太多的init,不要太过庞大,需要异步的推迟去初始化或者第一次用时,再去初始化。之前遇到的问题为:google浏览器启动过程anr,最后你会发现原因在于google浏览器在启动的时候,加载了大量的class,导致启动的时候,时间耗费的太长,如果系统比较忙(android.bg cpu负载较大),很容易在启动时候anr。这种,只能从手机本身的性能去着手,比如出现anr的时候,kswapd cpu使用高,则可以认为,内核配置的交换大小不正确,如果logd.w等 cpu占用高,则说明log太频繁,需要去除一些log,如果是mm开头的一个(具体没记住),是管理sdcard的,所以和频繁操作sd卡有关。
06
总结:
android设计了一种机制,保证主要的事务线不让无限的不返回,设计了anr。主要为UI线程,因为系统关注的就是它,其他线程不care,这是我们为什么看trace的时候,会直接跑去main线程先去看,同时注意,不是main线程也是需要看的,举个例子,之前遇到的问题为三方的一个亚马逊商场,在测试中发现了anr,随后进行查看trace,发现主线程栈是个正常执行流程中,发生。(就是这段代码本身不可能出现anr),然而发生了anr,我们从log中看到cpu中,亚马逊占比例非常高,然后我们找到亚马逊的trace文件,查看线程都有哪些,然后发现,当前后台出现了十几个线程,都在忙着下载图片,在write文件中,导致了cpu饥饿,发生anr。
07
予人玫瑰 ,手有余香。少些心机,多谢诚意。让心与心能靠在一起,温暖这世界的一隅角落。于是推荐一公号,欢迎大家围观。
总结自己在Android路上的一些学习经验,以及学习方法,支持投稿

《ANR 原理与实战技巧》

08

参考文档:
Android 信号处理面面观 之 trace 文件含义 :
http://blog.csdn.net/rambo2188/article/details/7017241
Android ANR 分析
http://blog.csdn.net/yxz329130952/article/details/50087731

《ANR 原理与实战技巧》

    原文作者:代码GG陆晓明
    原文地址: https://www.jianshu.com/p/1b8dc9d02b87
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞