Android ANR 经验汇总一 —— ANR介绍
Android ANR 经验汇总二 —— ANR日志分析
一、概述
最近在项目中遇到线上项目出现ANR,在解决问题的过程中总结了一些经验,在此汇总。
- 首先我们得了解ANR是什么,它在系统中产生的机制是什么,以及产生的原因又是什么
- 其次是如何解决ANR,解决ANR一直是Android 开发者需要掌握的重要技巧,一般从三个方面着手。
- 开发阶段: 通过工具检查各个方法的耗时,卡顿情况,发现一处修改一处。
- 线上阶段: 这个阶段主要依靠监控工具发现ANR并上报,比如matrix。
- 分析阶段: 如果线上用户发生ANR,并且你获取了一份日志。而这个是做维护项目比较常见的,会着重讲解——ANR日志分析技巧。
二、ANR产生机制
ANR全称:Application Not Responding,也就是应用程序无响应。
1. ANR是谁做的?
Android系统中,ActivityManagerService(简称AMS) 和WindowManagerService(简称WMS) 会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。还有Activity Service,Input Manager Service这些服务,也参与了ANR的管理。
2. ANR是怎么判断超时时间的而触发的?
ANR是一套监控Android应用响应是否及时的机制,可以把发生ANR比作是引爆炸弹,那么整个流程包含三部分组成:
- 埋定时炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。
- 拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。
- 引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后是炸毁目标。
在Android中,用Handler机制发送延时消息(炸弹),如果超时了,就发出ANR(引爆炸弹),如果没有超时,就取消队列里的延时消息(拆炸弹)。
ANR的基本原理如下:
3. ANR的4种类型:
1. InputEvent Timeout : 输入事件超时(5s)
- InputDispatcher发送Key事件给对应的进程的Focused Window,对应的window不存在、处于暂停态、或通道(input channel)占满、通道未注册、通道异常、或5s内没有处理完一个事件,就会发生ANR
- InputDispatcher发送MotionEvent事件有个例外之处:当对应Touched Window的 input waitQueue中有超过0.5s的事件,inputDispatcher会暂停该事件,并等待5s,如果仍旧没有收到window的‘finish’事件,则触发ANR
- 下一个事件到达,发现有一个超时事件才会触发ANR
例子:
创建一个Button,在点击事件中sleep 10秒去阻塞主线程,如下。
findViewById<Button>(R.id.btn1).setOnClickListener {
SystemClock.sleep(10 * 1000);
}
点击Button后,再点击返回键,过几秒钟即可触发“没有响应”的ANR。
查看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
- 静态注册的广播和有序广播会ANR,动态注册的非有序广播并不会ANR
- 广播发送时,会判断该进程是否存在,不存在则创建,创建进程的耗时也算在超时时间里
- 只有当进程存在前台显示的Activity才会弹出ANR对话框,否则会直接杀掉当前进程
- 当onReceive执行超过阈值(前台15s,后台60s),将产生ANR
- 如何发送前台广播: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
- Service的以下方法都会触发ANR:onCreate(),onStartCommand(), onStart(), onBind(), onRebind(), onTaskRemoved(), onUnbind(), onDestroy().
- 前台Service超时时间为20s,后台Service超时时间为200s
- 如何区分前台、后台执行————当前APP处于用户态,此时执行的Service则为前台执行。
- 用户态:有前台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内没有处理完成
- ContentProvider创建发布超时并不会ANR
- 使用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(耗时操作)
- 函数阻塞:如死循环、主线程IO、处理大数据
- 锁出错:主线程等待子线程的锁
- 内存紧张:系统分配给一个应用的内存是有上限的,长期处于内存紧张,会导致频繁内存交换,进而导致应用的一些操作超时
系统导致ANR
- CPU被抢占:一般来说,前台在玩游戏,可能会导致你的后台广播被抢占CPU
- 系统服务无法及时响应:比如获取系统联系人等,系统的服务都是Binder机制,服务能力也是有限的,有可能系统服务长时间不响应导致ANR
- 其他应用占用的大量内存