特别申明:本文仅供自己学习记录使用,所写内容来自各网页,如需转载自己去查找内容出处。如有侵权请联系在下,评论、私信等不论。
目录
一、ANR
1 ANR简介
2 ANR原因
3 分析ANR的重点
3.1 cpu占用率方面:
3.2 内存方面
4 ANR日志分析
5 版本声明
二、File类
一、ANR
1 ANR简介
ANR全称:Application Not Responding,也就是应用程序无响应。
ANR原因
Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。
首先,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。ANR由消息处理机制保证,Android在系统层实现了一套精密的机制来发现ANR,核心原理是消息调度和超时处理。
其次,ANR机制主体实现在系统层。所有与ANR相关的消息,都会经过系统进程(system_server)调度,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,譬如CPU/IO使用情况、进程函数调用栈,并且报告用户有进程无响应了(ANR对话框)。
然后,ANR问题本质是一个性能问题。ANR机制实际上对应用程序主线程的限制,要求主线程在限定的时间内处理完一些最常见的操作(启动服务、处理广播、处理输入), 如果处理超时,则认为主线程已经失去了响应其他操作的能力。主线程中的耗时操作,譬如密集CPU运算、大量IO、复杂界面布局等,都会降低应用程序的响应能力。
ANR出现场景
发生ANR时会调用AppNotRespondingDialog.show()方法弹出对话框提示用户,该对话框的依次调用关系如下图所示:
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内没进行完。
2 ANR原因
- 主线程慢代码
- 主线程IO
- 锁竞争
- 死锁
如何避免ANR
1.UI线程尽量只做跟UI相关的工作;
2.耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理;
3.尽量用Handler来处理UI thread和别的thread之间的交互;
4.实在绕不开主线程,可以尝试通过Handler延迟加载;
5.广播中如果有耗时操作,建议放在IntentService中去执行,或者通过goAsync() + HandlerThread分发执行。
3 分析ANR的重点
3.1 cpu占用率方面:
可以通过分析各进程的CPU时间占用率,来判断是否为某些进程长期占用CPU导致该进程无法获取到足够的CPU处理时间,而导致ANR重点关注下CPU的负载,各个进程总的CPU时间占用率,用户CPU时间占用率,核心态CPU时间占用率,以及iowait CPU时间占用率。
3.2 内存方面
主要看当前应用native和dalvik层内存使用情况,结合系统给每个应用分配的最大内存来分析。
4 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。
接下来还是回到进一步分析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了。
二、File类
Constructor and Description |
从父抽象路径名和子路径名字符串创建新的 |
通过将给定的路径名字符串转换为抽象路径名来创建新的 |
从父路径名字符串和子路径名字符串创建新的 |
通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。 |
|
测试应用程序是否可以执行此抽象路径名表示的文件。 |
|
测试应用程序是否可以读取由此抽象路径名表示的文件。 |
|
测试应用程序是否可以修改由此抽象路径名表示的文件。 |
|
比较两个抽象的路径名字典。 |
|
当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。 |
|
在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称。 |
|
在指定的目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。 |
|
删除由此抽象路径名表示的文件或目录。 |
|
请求在虚拟机终止时删除由此抽象路径名表示的文件或目录。 |
|
测试此抽象路径名与给定对象的相等性。 |
|
测试此抽象路径名表示的文件或目录是否存在。 |
|
返回此抽象路径名的绝对形式。 |
|
返回此抽象路径名的绝对路径名字符串。 |
|
返回此抽象路径名的规范形式。 |
|
返回此抽象路径名的规范路径名字符串。 |
|
返回分区未分配的字节数 named此抽象路径名。 |
|
返回由此抽象路径名表示的文件或目录的名称。 |
|
返回此抽象路径名的父 |
|
返回此抽象路径名的父,或抽象路径名 |
|
将此抽象路径名转换为路径名字符串。 |
|
通过此抽象路径名返回分区 named的大小。 |
|
返回上的分区提供给该虚拟机的字节数 named此抽象路径名。 |
|
计算此抽象路径名的哈希码。 |
|
测试这个抽象路径名是否是绝对的。 |
|
测试此抽象路径名表示的文件是否为目录。 |
|
测试此抽象路径名表示的文件是否为普通文件。 |
|
测试此抽象路径名命名的文件是否为隐藏文件。 |
|
返回此抽象路径名表示的文件上次修改的时间。 |
|
返回由此抽象路径名表示的文件的长度。 |
|
返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。 |
|
返回一个字符串数组,命名由此抽象路径名表示的目录中满足指定过滤器的文件和目录。 |
|
返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。 |
|
返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。 |
|
返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。 |
|
列出可用的文件系统根。 |
|
创建由此抽象路径名命名的目录。 |
|
创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。 |
|
重命名由此抽象路径名表示的文件。 |
|
为此抽象路径名设置所有者的执行权限的便利方法。 |
|
设置该抽象路径名的所有者或每个人的执行权限。 |
|
设置由此抽象路径名命名的文件或目录的最后修改时间。 |
|
一种方便的方法来设置所有者对此抽象路径名的读取权限。 |
|
设置此抽象路径名的所有者或每个人的读取权限。 |
|
标记由此抽象路径名命名的文件或目录,以便只允许读取操作。 |
|
一种方便的方法来设置所有者对此抽象路径名的写入权限。 |
|
设置此抽象路径名的所有者或每个人的写入权限。 |
|
返回从此抽象路径构造的java.nio.file.Path对象。 |
|
返回此抽象路径名的路径名字符串。 |
|
构造一个表示此抽象路径名的 file: URI。 |