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里面都有什么:

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

JDWP 调试线程

HeapTaskDaemon 内存管理线程

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

实战:

1按键响应超时

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

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

2广播接收超时

我们去看下堆栈信息:

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

问题定位。

3ContentResolver

继续去看堆栈:

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

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

主要看main:

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

5Memoryleak/Thread leak

继续分析

6Broadcast timeout

7普通的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 使用率采样时间段内新建的;

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

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

这种一般需要注意,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路上的一些学习经验,以及学习方法,支持投稿

08

参考文档:

Android 信号处理面面观 之 trace 文件含义 :

http://blog.csdn.net/rambo2188/article/details/7017241

Android ANR 分析

http://blog.csdn.net/yxz329130952/article/details/50087731

原文发布于微信公众号 - 代码GG之家(code_gg_home)

原文发表时间:2017-09-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Adamshuang 技术文章

Zookeeper 通知更新可靠吗? 解读源码找答案!

遇到Keepper通知更新无法收到的问题,思考节点变更通知的可靠性,通过阅读源码解析了解到zk Watch的注册以及触发的机制,本地调试运行模拟zk更新的不可靠...

1.2K80
来自专栏Seebug漏洞平台

如何通过TTL调试光猫

众所周知,光猫是现在每个家庭必备的一款设备,但是光猫背面写的账号密码,只是普通用户权限,会限制很多功能。这篇文章讲述,如何通过TTL调试的方法获取光猫超级管理员...

427100
来自专栏北京马哥教育

超详细!使用 LVS 实现负载均衡原理及安装配置详解

负载均衡集群是 load balance 集群的简写,翻译成中文就是负载均衡集群。常用的负载均衡开源软件有nginx、lvs、haproxy,商业的硬件负载均衡...

499100
来自专栏运维小白

19.13/19.14 配置邮件告警

配置邮件告警 使用163或者QQ邮箱发告警邮件 首先登录你的163邮箱,设置开启POP3、IMAP、SMTP服务 开启并记录授权码 然后到监控中心设置邮件告警 ...

256100
来自专栏程序员与猫

JSON Patch

34210
来自专栏Seebug漏洞平台

如何通过TTL调试光猫

作者:Sebao@知道创宇404实验室 序 言 总所周知,光猫是现在每个家庭必备的一款设备,但是光猫背面写的账号密码,只是普通用户权限,会限制很多功能。这篇...

47180
来自专栏大魏分享(微信公众号:david-share)

Openshift 3.11的14大新功能详解

聂健是大魏的红帽同事,本文已获得授权转载,欢迎读者阅读他的技术blog:https://www.cnblogs.com/ericnie/

1.7K30
来自专栏Eugene's Blog

黑客常用的扫描器盒子分类目录文章标签友情链接联系我们

35290
来自专栏Seebug漏洞平台

披着狼皮的羊——寻找惠普多款打印机中的RCE漏洞

原文:《A Sheep in Wolf’s Clothing – Finding RCE in HP’s Printer Fleet》

41080
来自专栏Seebug漏洞平台

披着狼皮的羊——寻找惠普多款打印机中的RCE漏洞

原文:https://foxglovesecurity.com/2017/11/20/a-sheep-in-wolfs-clothing-finding-rce...

12430

扫码关注云+社区

领取腾讯云代金券