Android 程序未响应(ANR)

一、ANR简介

ANR全称:Application Not Responding,也就是应用程序无响应。

Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。

以下四个条件都可以造成ANR发生:

InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
ContentProvider Timeout :ContentProvider的publish在10s内没进行完。

1.1 避免

尽量避免在主线程(UI线程)中作耗时操作。

那么耗时操作就放在子线程中。
关于多线程可以参考:Android多线程:理解和简单使用总结

二、ANR分析办法

2.1 ANR重现

这里使用的是号称Google亲儿子的Google Pixel xl(Android 8.0系统)做的测试,生成一个按钮跳转到ANRTestActivity,在后者的onCreate()中主线程休眠20秒:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_anr_test);
    // 这是Android提供线程休眠函数,与Thread.sleep()最大的区别是
    // 该使用该函数不会抛出InterruptedException异常。
    SystemClock.sleep(20 * 1000);
}

二、ANR出现场景

发生ANR时会调用AppNotRespondingDialog.show()方法弹出对话框提示用户,该对话框的依次调用关系如下图所示:

image

AppErrors.appNotResponding(),该方法是最终弹出ANR对话框的唯一入口,调用该方法的场景才会有ANR提示,也可以认为在主线程中执行无论再耗时的任务,只要最终不调用该方法,都不会有ANR提示,也不会有ANR相关日志及报告;通过调用关系可以看出哪些场景会导致ANR,有以下四种场景:

  • InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件。
  • BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
  • Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
  • ContentProvider Timeout :ContentProvider的publish在10s内没进行完。

三、出现ANR原因

  • 主线程慢代码
  • 主线程IO
  • 锁竞争
  • 死锁

四、 如何避免ANR

1.UI线程尽量只做跟UI相关的工作;
2.耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理;
3.尽量用Handler来处理UI thread和别的thread之间的交互;
4.实在绕不开主线程,可以尝试通过Handler延迟加载;
5.广播中如果有耗时操作,建议放在IntentService中去执行,或者通过goAsync() + HandlerThread分发执行。

五、分析ANR的重点

1.cpu占用率方面:
可以通过分析各进程的CPU时间占用率,来判断是否为某些进程长期占用CPU导致该进程无法获取到足够的CPU处理时间,而导致ANR重点关注下CPU的负载,各个进程总的CPU时间占用率,用户CPU时间占用率,核心态CPU时间占用率,以及iowait CPU时间占用率。
2.内存方面
主要看当前应用native和dalvik层内存使用情况,结合系统给每个应用分配的最大内存来分析。

ANR日志分析

app出现ANR时会在data/anr/目录下生成traces.txt日志文件。每次发生ANR时都会删除旧的traces文件,重新创建新文件。也就是说Android只保留最后一次发生ANR时的信息。

首先,我们可以使用adb命令导出traces文件

adb pull /data/anr/traces.txt d:\

友情提示:traces.txt默认会被导出到Android SDK\platform-tools目录。

开发中最方便的是在log里面就可以看到ANR的相关信息,以下面的日志为例,我们可以从Android studio logcat很明显的看出ANR发生的原因,用户的输入超时了,问题线程的PID:879。

同时我们还可以通俗易懂的看出来 CPU平均负载,CPU的使用情况:

4.67 ,3.32 ,1.49 分别表示 发生ANR 前一分钟,五分钟,十五分钟 CPU的平均负载 Load: 4.67 / 3.32 / 1.49 CPU usage from 6021ms to 79ms ago。

anr

接下来还是回到进一步分析traces.txt文件上来,看文件里面的内容:

 ----- pid 879 at 2019-01-02 08:05:04 -----Cmd line: com.sandiyu.lcd JNI: CheckJNI is off; workarounds are off; pins=2; globals=273 DALVIK THREADS:(mutexes: tll=0 tsl=0 tscl=0 ghl=0) "main" prio=5 tid=1 WAIT  | group="main" sCount=1 dsCount=0 obj=0x4159cd68 self=0x414d6510  | sysTid=879 nice=0 sched=0/0 cgrp=apps handle=1074020692  | state=S schedstat=( 0 0 0 ) utm=602 stm=168 core=1  at java.lang.Object.wait(Native Method)  - waiting on <0x4159ce38> (a java.lang.VMThread) held by tid=1 (main)  at java.lang.Thread.parkFor(Thread.java:1205)  at sun.misc.Unsafe.park(Unsafe.java:325)  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:157)  at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2017)  at java.util.concurrent.LinkedBlockingQueue.put(LinkedBlockingQueue.java:318)  at com.sandiyu.lcd.utils.DeviceCommandSender$CommandSendThread.send(DeviceCommandSender.java:156)  at com.sandiyu.lcd.utils.DeviceCommandSender.displayNull(DeviceCommandSender.java:81)  at com.sandiyu.lcd.DlpPrintActivity$PrintRunnable.clearImage(DlpPrintActivity.java:884)  at com.sandiyu.lcd.DlpPrintActivity$PrintRunnable.access$1900(DlpPrintActivity.java:253)  at com.sandiyu.lcd.DlpPrintActivity.onBackPressed(DlpPrintActivity.java:954)  at android.app.Activity.onKeyUp(Activity.java:2193)    ...

一般trace文件顶部的线程即为ANR的元凶,找到了犯罪线程我们就可以查看、分析一下犯罪现场。

  • line 1,2
----- pid 879 at 2019-01-02 08:05:04 -----Cmd line: com.sandiyu.lcd

可以看到ANR 发生的进程id,时间,名称。

  • line 3,4,5
JNI: CheckJNI is off; workarounds are off; pins=2; globals=273 DALVIK THREADS:(mutexes: tll=0 tsl=0 tscl=0 ghl=0)

可以看到线程的基本信息(tll:thread list lock,tsl:thread suspend lock,tscl:thread suspend count lock,ghl:gc heap lock)。

  • line "main"
"main" prio=5 tid=1 WAIT

这一行说明了线程名称,优先级,线程锁id和线程状态。可以看到本次ANR 线程为WAIT状态。

额外补充一下线程状态有如下几种:

java thread 状态 cpp thread状态 说明
TERMINATED ZOMBIE 线程死亡,终止运行
RUNNABLE RUNNING/RUNNABLE 线程可运行或正在运行
TIMED_WAITING TIMED_WAIT 执行了带有超时参数的wait、sleep或join函数
BLOCKED MONITOR 线程阻塞,等待获取对象锁
WAITING WAIT 执行了无超时参数的wait函数
NEW INITIALIZING 新建,正在初始化,为其分配资源
NEW STARTING 新建,正在启动
RUNNABLE NATIVE 正在执行JNI本地函数
WAITING VMWAIT 正在等待VM资源
RUNNABLE SUSPENDED 线程暂停,通常是由于GC或debug被暂停
UNKNOWN 未知状态

接着往下面的信息看

at com.sandiyu.lcd.utils.DeviceCommandSender$CommandSendThread.send(DeviceCommandSender.java:156)  at com.sandiyu.lcd.utils.DeviceCommandSender.displayNull(DeviceCommandSender.java:81)  at com.sandiyu.lcd.DlpPrintActivity$PrintRunnable.clearImage(DlpPrintActivity.java:884)  at com.sandiyu.lcd.DlpPrintActivity$PrintRunnable.access$1900(DlpPrintActivity.java:253)  at com.sandiyu.lcd.DlpPrintActivity.onBackPressed(DlpPrintActivity.java:954)

在这里我们就找到了原因,CommandSendThread.send需要等待网络资源来更新UI,连接中断了,这时候点击onBackPressed长时间得不到相应,它就报了ANR了。

六、造成ANR的原因及解决办法

上面例子只是由于简单的主线程耗时操作造成的ANR,造成ANR的原因还有很多:

主线程阻塞或主线程数据读取
解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务。尽量避免在主线程query provider、不要滥用SharePreferenceS

CPU满负荷,I/O阻塞
解决办法:文件读写或数据库操作放在子线程异步操作。

内存不足
解决办法:AndroidManifest.xml文件中可以设置 android:largeHeap=“true”,以此增大App使用内存。不过不建议使用此法,从根本上防止内存泄漏,优化内存使用才是正道。

各大组件ANR
各大组件生命周期中也应避免耗时操作,注意BroadcastReciever的onRecieve()、后台Service和ContentProvider也不要执行太长时间的任务。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容