特别申明:本文仅供自己学习记录使用,所写内容来自各网页,如需转载自己去查找内容出处。如有侵权请联系在下,评论、私信等不论。

目录

一、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(File parent, String

从父抽象路径名和子路径名字符串创建新的 File实例。

File(String

通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

File(String parent, String

从父路径名字符串和子路径名字符串创建新的 File实例。

File(URI

通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。

boolean

canExecute()

测试应用程序是否可以执行此抽象路径名表示的文件。

boolean

canRead()

测试应用程序是否可以读取由此抽象路径名表示的文件。

boolean

canWrite()

测试应用程序是否可以修改由此抽象路径名表示的文件。

int

compareTo(File

比较两个抽象的路径名字典。

boolean

createNewFile()

当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。

static File

createTempFile(String prefix, String

在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称。

static File

createTempFile(String prefix, String suffix, File

在指定的目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。

boolean

delete()

删除由此抽象路径名表示的文件或目录。

void

deleteOnExit()

请求在虚拟机终止时删除由此抽象路径名表示的文件或目录。

boolean

equals(Object

测试此抽象路径名与给定对象的相等性。

boolean

exists()

测试此抽象路径名表示的文件或目录是否存在。

File

getAbsoluteFile()

返回此抽象路径名的绝对形式。

String

getAbsolutePath()

返回此抽象路径名的绝对路径名字符串。

File

getCanonicalFile()

返回此抽象路径名的规范形式。

String

getCanonicalPath()

返回此抽象路径名的规范路径名字符串。

long

getFreeSpace()

返回分区未分配的字节数 named此抽象路径名。

String

getName()

返回由此抽象路径名表示的文件或目录的名称。

String

getParent()

返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。

File

getParentFile()

返回此抽象路径名的父,或抽象路径名 null如果此路径名没有指定父目录。

String

getPath()

将此抽象路径名转换为路径名字符串。

long

getTotalSpace()

通过此抽象路径名返回分区 named的大小。

long

getUsableSpace()

返回上的分区提供给该虚拟机的字节数 named此抽象路径名。

int

hashCode()

计算此抽象路径名的哈希码。

boolean

isAbsolute()

测试这个抽象路径名是否是绝对的。

boolean

isDirectory()

测试此抽象路径名表示的文件是否为目录。

boolean

isFile()

测试此抽象路径名表示的文件是否为普通文件。

boolean

isHidden()

测试此抽象路径名命名的文件是否为隐藏文件。

long

lastModified()

返回此抽象路径名表示的文件上次修改的时间。

long

length()

返回由此抽象路径名表示的文件的长度。

String[]

list()

返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。

String[]

list(FilenameFilter

返回一个字符串数组,命名由此抽象路径名表示的目录中满足指定过滤器的文件和目录。

File[]

listFiles()

返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。

File[]

listFiles(FileFilter

返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。

File[]

listFiles(FilenameFilter

返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。

static File[]

listRoots()

列出可用的文件系统根。

boolean

mkdir()

创建由此抽象路径名命名的目录。

boolean

mkdirs()

创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。

boolean

renameTo(File

重命名由此抽象路径名表示的文件。

boolean

setExecutable(boolean executable)

为此抽象路径名设置所有者的执行权限的便利方法。

boolean

setExecutable(boolean executable, boolean ownerOnly)

设置该抽象路径名的所有者或每个人的执行权限。

boolean

setLastModified(long time)

设置由此抽象路径名命名的文件或目录的最后修改时间。

boolean

setReadable(boolean readable)

一种方便的方法来设置所有者对此抽象路径名的读取权限。

boolean

setReadable(boolean readable, boolean ownerOnly)

设置此抽象路径名的所有者或每个人的读取权限。

boolean

setReadOnly()

标记由此抽象路径名命名的文件或目录,以便只允许读取操作。

boolean

setWritable(boolean writable)

一种方便的方法来设置所有者对此抽象路径名的写入权限。

boolean

setWritable(boolean writable, boolean ownerOnly)

设置此抽象路径名的所有者或每个人的写入权限。

Path

toPath()

返回从此抽象路径构造的java.nio.file.Path对象。

String

toString()

返回此抽象路径名的路径名字符串。

URI

toURI()

构造一个表示此抽象路径名的 file: URI。