Android ANR 经验汇总一 —— ANR介绍

5,696 阅读8分钟

Android ANR 经验汇总一 —— ANR介绍
Android ANR 经验汇总二 —— ANR日志分析

一、概述

最近在项目中遇到线上项目出现ANR,在解决问题的过程中总结了一些经验,在此汇总。

  1. 首先我们得了解ANR是什么,它在系统中产生的机制是什么,以及产生的原因又是什么
  2. 其次是如何解决ANR,解决ANR一直是Android 开发者需要掌握的重要技巧,一般从三个方面着手。
    • 开发阶段: 通过工具检查各个方法的耗时,卡顿情况,发现一处修改一处。
    • 线上阶段: 这个阶段主要依靠监控工具发现ANR并上报,比如matrix。
    • 分析阶段: 如果线上用户发生ANR,并且你获取了一份日志。而这个是做维护项目比较常见的,会着重讲解——ANR日志分析技巧

二、ANR产生机制

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

1. ANR是谁做的?

Android系统中,ActivityManagerService(简称AMS)WindowManagerService(简称WMS) 会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。还有Activity ServiceInput Manager Service这些服务,也参与了ANR的管理。

2. ANR是怎么判断超时时间的而触发的?

ANR是一套监控Android应用响应是否及时的机制,可以把发生ANR比作是引爆炸弹,那么整个流程包含三部分组成:

  1. 埋定时炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。
  2. 拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。
  3. 引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后是炸毁目标。

在Android中,用Handler机制发送延时消息(炸弹),如果超时了,就发出ANR(引爆炸弹),如果没有超时,就取消队列里的延时消息(拆炸弹)。

ANR的基本原理如下:

image.png

3. ANR的4种类型:

1. InputEvent Timeout : 输入事件超时(5s)

  1. InputDispatcher发送Key事件给对应的进程的Focused Window,对应的window不存在、处于暂停态、或通道(input channel)占满、通道未注册、通道异常、或5s内没有处理完一个事件,就会发生ANR
  2. InputDispatcher发送MotionEvent事件有个例外之处:当对应Touched Window的 input waitQueue中有超过0.5s的事件,inputDispatcher会暂停该事件,并等待5s,如果仍旧没有收到window的‘finish’事件,则触发ANR
  3. 下一个事件到达,发现有一个超时事件才会触发ANR
例子:

创建一个Button,在点击事件中sleep 10秒去阻塞主线程,如下。

findViewById<Button>(R.id.btn1).setOnClickListener {
    SystemClock.sleep(10 * 1000);
}

点击Button后,再点击返回键,过几秒钟即可触发“没有响应”的ANR。

image.png

查看log信息如下

01-13 16:32:26.823 1580-1594/system_process I/Process: Sending signal. PID: 1678 SIG: 3
01-13 16:32:26.824 1678-1686/com.android.systemui I/art: Thread[2,tid=1686,WaitingInMainSignalCatcherLoop,Thread*=0xae3c2000,peer=0x12c2d0a0,"Signal Catcher"]: reacting to signal 3
01-13 16:32:26.891 1580-1584/system_process I/art: Wrote stack traces to '/data/anr/traces.txt'
01-13 16:32:26.972 1678-1686/com.android.systemui I/art: Wrote stack traces to '/data/anr/traces.txt'

在data/anr目录下,导出trace.txt,可以在日志中找到一下信息

1. "main" prio=5 tid=1 Sleeping
2.   | group="main" sCount=1 dsCount=0 obj=0x74088258 self=0xb4034500
3.   | sysTid=2686 nice=0 cgrp=default sched=0/0 handle=0xb770ac00
4.   | state=S schedstat=( 0 0 0 ) utm=21 stm=1 core=1 HZ=100
5.   | stack=0xbf678000-0xbf67a000 stackSize=8MB
6.   | held mutexes=
7.   at java.lang.Thread.sleep!(Native method)
8.   - sleeping on <0x0b4cba68> (a java.lang.Object)
9.   at java.lang.Thread.sleep(Thread.java:1031)
10.   - locked <0x0b4cba68> (a java.lang.Object)
11.   at java.lang.Thread.sleep(Thread.java:985)
12.   at com.example.kotlintest.MainActivity$onCreate$1.onClick(MainActivity.kt:14)
13.   at android.view.View.performClick(View.java:5198)
14.   at android.view.View$PerformClick.run(View.java:21147)
15.   at android.os.Handler.handleCallback(Handler.java:739)
16.   at android.os.Handler.dispatchMessage(Handler.java:95)
17.   at android.os.Looper.loop(Looper.java:148)
18.   at android.app.ActivityThread.main(ActivityThread.java:5417)
19.   at java.lang.reflect.Method.invoke!(Native method)
20.   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
21.   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

7——12行,标明了出现ANR的位置和原因,第1行说明是主线程处于Sleeping状态。

2. BroadcastTimeout 广播类型超时(前台10s,后台60s)

BroadcastReceiver onReceiver处理事务时前台广播在15S内,后台广播在60s内. 没有处理完成发生ANR

  1. 静态注册的广播和有序广播会ANR,动态注册的非有序广播并不会ANR
  2. 广播发送时,会判断该进程是否存在,不存在则创建,创建进程的耗时也算在超时时间里
  3. 只有当进程存在前台显示的Activity才会弹出ANR对话框,否则会直接杀掉当前进程
  4. 当onReceive执行超过阈值(前台15s,后台60s),将产生ANR
  5. 如何发送前台广播:Intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)

例:

// 创建Receiver
class ANRReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context, "start", Toast.LENGTH_SHORT).show()
        SystemClock.sleep(15 * 1000)
        Toast.makeText(context, "end", Toast.LENGTH_SHORT).show()
    }
}

//AndroidManifest.xml注册
<receiver android:name=".ANRReceiver">
</receiver>
//发送显式广播
findViewById<Button>(R.id.anr_broadcast_btn).setOnClickListener {
    val intent = Intent()
    intent.component = ComponentName(this, ANRReceiver::class.java)
    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)   //这里使用前台广播,更容易测试
    sendBroadcast(intent)
}

/data/anr/trace.txt日志

"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x74088258 self=0xb4034500
  | sysTid=3918 nice=0 cgrp=default sched=0/0 handle=0xb770ac00
  | state=S schedstat=( 0 0 0 ) utm=12 stm=4 core=0 HZ=100
  | stack=0xbf678000-0xbf67a000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x0c9a235a> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x0c9a235a> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at com.example.kotlintest.ANRReceiver.onReceive(ANRReceiver.kt:12)
  at android.app.ActivityThread.handleReceiver(ActivityThread.java:2725)
  at android.app.ActivityThread.-wrap14(ActivityThread.java:-1)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1421)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:148)
  at android.app.ActivityThread.main(ActivityThread.java:5417)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

3. ServiceTimeout 服务超时(前台20s,后台200s)

bind,create,start,unbind等操作,前台Service在20s内,后台Service在200s内没有处理完成发生ANR

  1. Service的以下方法都会触发ANR:onCreate(),onStartCommand(), onStart(), onBind(), onRebind(), onTaskRemoved(), onUnbind(), onDestroy().
  2. 前台Service超时时间为20s,后台Service超时时间为200s
  3. 如何区分前台、后台执行————当前APP处于用户态,此时执行的Service则为前台执行。
  4. 用户态:有前台activity、有前台广播在执行、有foreground service执行

例:

//创建Service
class ANRService : Service() {

    private val TAG = "ANRService"

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "onCreate: ANRService start")
        SystemClock.sleep(20 * 1000)
        Log.i(TAG, "onCreate: ANRService end")
    }

    override fun onBind(intent: Intent?): IBinder? {
        TODO("Not yet implemented")
    }
}

//注册service
<service android:name=".ANRService" />

//添加按钮事件,启动Service
findViewById<Button>(R.id.anr_service_btn).setOnClickListener {
    val intent = Intent()
    intent.component = ComponentName(this, ANRService::class.java)
    startService(intent)
}

/data/anr/trace.txt日志

"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x74088258 self=0xb40b4500
  | sysTid=3750 nice=0 cgrp=default sched=0/0 handle=0xb7777c00
  | state=S schedstat=( 0 0 0 ) utm=14 stm=1 core=0 HZ=100
  | stack=0xbf067000-0xbf069000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x0adfdbe3> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x0adfdbe3> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at com.hjc.aartest.ANRService.onCreate(ANRService.kt:16)
  at android.app.ActivityThread.handleCreateService(ActivityThread.java:2877)
  at android.app.ActivityThread.-wrap4(ActivityThread.java:-1)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1427)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:148)
  at android.app.ActivityThread.main(ActivityThread.java:5417)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

4. ProcessContentProviderPublishTimedOutLocked :ContentProvider publish在10s内没有处理完成

  1. ContentProvider创建发布超时并不会ANR
  2. 使用ContentProviderclient来访问ContentProverder可以自主选择触发ANR,超时时间自己定 client.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);

PS:

1. 各个ANR超时时间在哪里查看?
主要是ActivityManagerService和ActiveServices中,以静态变量的方式定义

/*-------------------------ActivityManagerService-----------------------------*/
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
//输入事件
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;

//前后台广播
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;

// ContentProviderPublish
// How long we wait for an attached process to publish its content providers
// before we decide it must be hung.
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;

/*--------------------------ActiveServices------------------------------------*/
/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
//服务
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;

// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;

2. Activity生命周期超时会不会ANR?
经测试并不会,但是界面会卡顿。但是如果在卡顿的时候,做了按键操作则会触发输入事件超时。

override fun onCreate(savedInstanceState: Bundle?) {
    Thread.sleep(60000) 
    super.onCreate(savedInstanceState) 
    setContentView(R.layout.activity_main) 
}

三、ANR产生的原因

很多开发者认为,ANR就是耗时操作导致,全部是app应用层的问题。实际上,线上环境大部分ANR由系统原因导致。

应用层导致ANR(耗时操作)

  1. 函数阻塞:如死循环、主线程IO、处理大数据
  2. 锁出错:主线程等待子线程的锁
  3. 内存紧张:系统分配给一个应用的内存是有上限的,长期处于内存紧张,会导致频繁内存交换,进而导致应用的一些操作超时

系统导致ANR

  1. CPU被抢占:一般来说,前台在玩游戏,可能会导致你的后台广播被抢占CPU
  2. 系统服务无法及时响应:比如获取系统联系人等,系统的服务都是Binder机制,服务能力也是有限的,有可能系统服务长时间不响应导致ANR
  3. 其他应用占用的大量内存

感谢文章