Android稳定性专题之ANR

来源:互联网 发布:贵州软件评测机构 编辑:程序博客网 时间:2024/05/16 09:59

1. ANR问题

ANR,应用程序无响应,是Android应用程序稳定性问题的另一类重要问题。

1.1 ANR是什么

首先,我们来看一下Android官网上对于ANR的说明,https://developer.android.com/training/articles/perf-anr.html

在Android中,程序的响应性是由Activity Manager与Window Manager系统服务来负责监控的。当系统监测到下面的条件之一时会显示ANR的对话框:

  • 对输入事件(例如硬件点击或者屏幕触摸事件),5秒内都无响应。
  • BroadReceiver不能够在10秒内结束接收到任务

Android官网上列出的文章较老,再多补充一下触发条件

类型
触发时间
是否需要与APP交互
Input Dispatching5s是Service Foreground20s否Broadcast Foreground10s否Broadcast Background60s否

1.2 ANR的触发原因

简单的说,就是主线程(UI线程)被耗时操作阻塞,无法及时完成工作,导致了ANR。

深入了解Android的基础组件,会发现Activity,Service,BroadcastReceiver,ContentProvider等基本的生存周期回调函数是运行在主线程中的。包括UI控件的各类事件响应回调函数也是运行在主线程中的。那么,在这些函数中,如果发生了阻塞,就会触发ANR。具体有哪些问题呢,这里举一些例子:

  • 主线程中执行了耗时操作,比如:
    • 网络访问
    • 数据库读写
    • 文件读写
    • 复杂的耗时算法
    • wait()、sleep()、join()、等线程操作

  • 其他线程或进程占用了CPU资源,导致主线程无法获取CPU资源
  • 多线程发生死锁,主线程一直无法获取锁,造成阻塞

2. 如何避免ANR问题

根据1.2节中的分析,只要能够消除造成主线程阻塞的原因,就可以避免ANR的发生。

首先,做好架构设计。UI展示,与业务逻辑有效分离,比如采用MVC模式。

其次,各种耗时操作都放在子线程中处理,比如,网络访问,数据库操作,文件读写,其他各种IO,图像处理,耗时算法等等。

  • 技术手段可以选择AsyncTask,或者自己编写Thread + Handler模型,线程池等技术手段。
  • UI交互上,在等待耗时操作的时候,引入更多的提示,比如进度条,提示框等。
    也可以通过修改页面流程让交互更流畅,比如启动时增加一个闪屏,为应用的初始化争取到足够的加载时间。

再次,优化多线程机制,sleep(),wait(),join()放到子线程中处理,可以在主线程中增加回调,比如在子线程中持有主线程中的handler,采用handler-message机制,可以回调触发主线程任务。

另外,避免主线程中发生死锁。

3. 发生了ANR,如何分析解决

理想很丰满,现实很骨感。种种原因,特别是在压力测试中,开发人员还是要面对各种各样的ANR问题。

那么,对于ANR问题,我们该如何分析呢。

3.1 ANR日志的生成

当ANR发生时,Android系统中的AMS(ActivityManagerService)会进行处理。ANR的日志会输出到 /data/anr/traces.txt 文件中。

 这个过程会清空/data/anr/traces.txt的老文件, 那么原来之前的traces信息一般地会先输出到 /data/system/dropbox 实际工作中,存在trace文件丢失的情况。


关注细节的话,这里可以多提一下,对于Java和Native,系统在ANR的处理过程中采用不同的策略。

Java层的处理

所有的Java进程都运行在Java虚拟机上,当应用发生ANR时,其最终的一个环节是向目标进程通过kill -3,发送信号SIGNAL_QUIT。Android进程收到SIGQUIT时,虚拟机会捕获这个信号,并输出相应的traces信息,保存到 /data/anr/traces.txt 中。

Native层的处理

Native进程在发生ANR时,debuggerd服务接收到系统发来的 DEBUGGER_ACTION_DUMP_BACKTRACE 命令,最后通过dump_backtrace()将日志输出到 /data/anr/traces.txt 。

3.2 ANR日志分析

通常ANR问题的分析需要将trace日志和logcat日志结合起来综合分析,才能较好的解决。关于线程状态以及CPU、内存指标在日志中的含义,请参考附录部分的介绍。


总结:

  1. trace日志和logcat日志要结合起来看。
  2. trace中的ANR原因,进程号,时间,首个调用栈等信息非常关键。
  3. logcat日志中一方面查找对应时间的日志,检查应用的行为,检查具体导致SIG:3发出的原因。
  4. ANR in XXX也是一个关键字,可以在logcat日志中检索。不过这句日志打印的时间通常要比ANR发生的时间晚一些。找到这部分log后,应该顺着向前继续查找ANR发生的日志。

附录

线程状态

查看trace日志时,堆栈信息部分,会显示线程的状态。这里列出Java中定义的线程状态。

Thread.java中定义的状态

Thread.cpp中定义的状态

说明

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

未知状态


日志中关于
CPU和内存的关键指标含义

CPU的使用时间:读取 /proc/stat

  • user: 用户进程的CPU使用时间
  • nice: 降低过优先级进程的CPU使用时间。Linux进程都有优先级,这个优先级可以进行动态调整,譬如进程初始优先级的值设为10,运行时降低为8,那么,修正值-2就定义为nice。 Android将user和nice这两个时间归类成user
  • sys: 内核进程的CPU使用时间
  • idle: CPU空闲的时间
  • wait: CPU等待IO的时间
  • hw irq: 硬件中断的时间。如果外设(譬如硬盘)出现故障,需要通过硬件终端通知CPU保存现场,发生上下文切换的时间就是CPU的硬件中断时间
  • sw irg: 软件中断的时间。同硬件中断一样,如果软件要求CPU中断,则上下文切换的时间就是CPU的软件中断时间

CPU负载:读取 /proc/loadavg

统计最近1分钟,5分钟,15分钟内,CPU的平均活动进程数。 CPU的负载可以比喻成超市收银员负载,如果有1个人正在买单,有2个人在排队,那么该收银员的负载就是3。 在收银员工作时,不断会有人买单完成,也不断会有人排队,可以在固定的时间间隔内(譬如,每隔5秒)统计一次负载,那么,就可以统计出一段时间内的平均负载。

页错误信息:进程的CPU使用率最后输出的“faults: xxx minor/major”部分表示的是页错误次数,当次数为0时不显示。

  • major是指Major Page Fault(主要页错误,简称MPF),内核在读取数据时会先后查找CPU的高速缓存和物理内存,如果找不到会发出一个MPF信息,请求将数据加载到内存。
  • minor是指Minor Page Fault(次要页错误,简称MnPF),磁盘数据被加载到内存后,内核再次读取时,会发出一个MnPF信息。 一个文件第一次被读写时会有很多的MPF,被缓存到内存后再次访问MPF就会很少,MnPF反而变多,这是内核为减少效率低下的磁盘I/O操作采用的缓存技术的结果。
0 0
原创粉丝点击