微信号:code_gg_home

介绍:分享技术干货,分享最新新闻动态,分享开心段子,让大家轻松愉快的成长.

ANR 原理与实战技巧

2017-09-02 11:34 陆晓明


     首先,每天看到不断有新人关注着这个公众号,心情很是愉悦。一种认可,一种信任,也是我前进的动力。感谢大家的支持与鼓励。


    如果你喜欢这篇文章,或者喜欢这个图片,或者有所感悟,或者仅仅因为喜欢代码GG,分享此文到技术群里,以便此文的价值最大化。

    

    再次感谢大家的鼎力支持,抱拳拱手,难表胜意。俯首向前,继续码文,聊表心意。



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之家 更多文章 大荔冬枣,你值得拥有 Android 启动过程框架 深入Android源码系列(二) HOOK技术大作战 vlc for android 完全编译 android native 代码内存泄露 定位方案
猜您喜欢 Spark:Yarn-cluster和Yarn-client区别与联系 C++ 多线程编程总结 mysqlproxy的读写分离实现 表单设计总结 向mongoDB勒索说不,8招帮你解决安全性问题