ANR问题浅析

ANR问题浅析_第1张图片

  我们在Android应用开发中,会经常遇到ANR问题。本文将从ANR产生的原因、ANR的分类、ANR问题的分析、ANR问题的常见解决办法这几个方面来介绍ANR。最后会通过分析ANR源码,了解ANR产生的过程。

一、ANR简介

  ANR是Application Not Responding的简称,即应用无响应。
  ANR产生的原因是由于应用在主线程阻塞了,导致界面无响应。超时的原因一般为两种:
  1. 当前的事件没有机会得到处理(即UI线程正在处理前一个事件,没有及时地完成或者looper被某种原因阻塞了);
  2. 当前的事件正在处理,但没有及时完成;

二、ANR分类

  ANR产生的原因一般可以分为以下4种情况:
 1.InputDispatching TimeOut(5s)
  输入事件超时,如触摸、点击事件无响应
 2.Broadcast Timeout(前台广播10s、后台广播60s)
  广播超时,是指广播在指定超时时间内没有处理完成。广播执行的方式有两种:一种是串行执行,另外一种是并行执行。并行执行的广播没有时间限定,例如动态注册的非有序广播是并行执行的;串行执行的广播是有时间限定的,前台广播的超时时间为10s,后台广播的超时时间为60s,例如静态注册的广播和有序广播都是串行执行的。
 3.Service TimeOut(默认情况下为20s)
  服务超时,是指在执行服务的方法超时了,即在执行服务的周期函数onCreate()、onBind()、onStart()的时候超时了。在后台线程组中服务执行超时时间为200s,非后台线程组中服务执行超时时间为20s。默认情况下,服务都在非后台线程组中执行,超时时间为20s。
        在Process.java中,定义了几种线程组:
THREAD_GROUP_DEFAULT 默认线程组
THREAD_GROUP_BG_NONINTERACTIVE 后台线程组
THREAD_GROUP_FOREGROUND 前台线程组
THREAD_GROUP_SYSTEM 系统线程组
 4.ContentProviderClient Timeout(20s)
  Provider超时,在使用ContentProviderClient操作ContentProvider时超时,超时时间为20s。

三、ANR问题分析

   通过前面分析AppErrors的appNotResponding方法可以知道,当发生ANR时,会将ANR相关的信息,例如ANR产生的原因,CPU的状态统计信息,进程方法调用堆栈信息输出到各个日志文件中。通过分析这些日志文件,可以找出ANR发生的时间,以及ANR产生的原因。ANR日志信息记录在event.txt、main.txt、system.txt以及trace.txt文件中。
  • event日志
  在event日志中,通过查找am_anr关键字,查询ANR相关的日志信息,例如:
04-12 11:22:11.999  1538  1648 I am_anr  : [0,3026,com.android.incallui,952680013,Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it.  Outbound queue length: 0.  Wait queue length: 1.)]
   通过event日志,可以确定是否发生了ANR,以及发生ANR的应用程序,以及ANR发生的时间点。
  • main日志
  对于输入事件的ANR,可以在main日志里面查找InputDispatcher关键字,查看ANR发生的原因。例如:
04-12 11:22:11.886  1538  2037 I InputDispatcher: Application is not responding: AppWindowToken{ae8dbc1 token=Token{88984a8 ActivityRecord{4e734cb u0 com.android.incallui/.InCallActivity t13049}}} - Window{d4efe8f u0 com.android.incallui/com.android.incallui.InCallActivity}.  It has been 5410.4ms since event, 5000.3ms since wait started.  Reason: Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it.  Outbound queue length: 0.  Wait queue length: 1.
  • system日志
        在system日志中,可以查找关键字ANR in关键字,查看ANR发生时的CPU的状态信息,以及ANR发生的原因。例如:
04-12 11:22:18.724  1538  1648 E ActivityManager: ANR in com.android.incallui (com.android.incallui/.InCallActivity)
04-12 11:22:18.724  1538  1648 E ActivityManager: PID: 3026
04-12 11:22:18.724  1538  1648 E ActivityManager: Reason: Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it.  Outbound queue length: 0.  Wait queue length: 1.)
04-12 11:22:18.724  1538  1648 E ActivityManager: Load: 8.8 / 8.63 / 8.25
04-12 11:22:18.724  1538  1648 E ActivityManager: CPU usage from 0ms to 6728ms later:
04-12 11:22:18.724  1538  1648 E ActivityManager:   82% 1538/system_server: 27% user + 54% kernel / faults: 12842 minor 54 major
................
04-12 11:22:18.724  1538  1648 E ActivityManager: 37% TOTAL: 21% user + 14% kernel + 1.5% irq
   通过system日志,可以看到ANR发生时,CPU的使用情况,如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致了ANR;如果CPU使用量很少,则说明主线程被阻塞了。
  • trace日志
     trace文件主要是记录ANR发生时,最近活动进程的方法调用堆栈信息。可以通过搜索Cmd line关键字,查找记录的进程包名。然后查看对应进程的方法调用堆栈,分析ANR发生时的方法调用信息。例如:
----- pid 3026 at 2017-04-12 11:22:12 -----
Cmd line: com.android.incallui
"Signal Catcher" daemon prio=5 tid=2 Runnable
  at android.os.BinderProxy.transactNative(Native method)
  at android.os.BinderProxy.transact(Binder.java:532)
  at android.app.ActivityManagerProxy.finishActivity(ActivityManagerNative.java:2964)
  at android.app.Activity.finish(Activity.java:4923)
  at android.app.Activity.finish(Activity.java:4941)
  at com.android.incallui.InCallActivity.finish(InCallActivity.java:445)
  at com.android.incallui.InCallPresenter.attemptFinishActivity(InCallPresenter.java:331)
  at com.android.incallui.InCallPresenter.startOrFinishUi(InCallPresenter.java:1276)
  at com.android.incallui.InCallPresenter.onCallListChange(InCallPresenter.java:488)
  at com.android.incallui.CallList.notifyGenericListeners(CallList.java:528)
  at com.android.incallui.CallList.finishDisconnectedCall(CallList.java:629)
  at com.android.incallui.CallList.access$000(CallList.java:49)
  at com.android.incallui.CallList$1.handleMessage(CallList.java:659)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:171)
  at android.app.ActivityThread.main(ActivityThread.java:5610)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:732)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:622)

四、ANR问题的常见解决办法

  不同的ANR类型需要采用不同的措施来避免产生ANR:
InputDispatching Timeout
  • UI线程尽量只做跟UI相关的工作;
  • 耗时的工作放在子线程中执行,例如数据库操作、I/O耗时操作放在子线程中去操作;
  • 尽量用Handler来处理UI线程与其他线程之间的交互。
Broadcast TimeOut
  • 将耗时操作放在子线程中操作,让广播的onReceive函数尽快返回;
  • 改用动态方式注册广播,不要用静态方式注册广播;
ContentProviderClient Timeout
  • 将数据库操作放在子线程中执行;
  • 改用其他的方式来替代ContentProviderClient;
Service TimeOut
  • 将服务方法中的一些耗时操作放在子线程中执行,让服务方法尽快返回。
  从上面可以看到,一个比通用的解决策略是将耗时操作放在子线程中执行。 在下一篇文章中,将分析ANR的产生的源码,了解ANR产生的过程。

你可能感兴趣的:(ANR)