Android ANR

Android ANR

ANR即Application Not Responding,给使用使用者带来的感受是程序长时间无响应,最终应用闪退。

在 Android 里,应用程序的响应性是由 Activity Manager 和 WindowManager 系统服务监视的。当它监测到以下情况中的一个时,Android 就会针对特定的应用程序显示 ANR:

  • InputDispatching Timeout
  • Service Timeout
  • BroadcastQueue Timeout
  • ContentProvider Timeout

触发机制

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

  1. 埋定时炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。

  2. 拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。

  3. 引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后是炸毁目标。

  • InputDispatching Timeout 超时机制(核心代码位置 InputDispatcher.cpp,ActivityManagerService.java)


    ANR机制-input.png
  • Service Timeout超时机制


    ANR机制-service.png
  • BroadcastQueue Timeout超时机制


    ANR机制-broadcast.png
  • ContentProvider Timeout超时机制


    ANR机制-provider.png

ANR超时阈值

不同组件的超时阈值各有不同,关于service、broadcast、contentprovider以及input的超时阈值如下表:


ANR超时阈值.png
  • service前后台判断

    ActiveServices在startService过程根据发起方进程callerApp所属的进程调度组来决定被启动的服务是属于前台还是后台。当发起方进程不等于ProcessList.SCHED_GROUP_BACKGROUND(后台进程组)则认为是前台服务,否则为后台服务,并标记在ServiceRecord的成员变量createdFromFg。

  • broadcoast前后台判断

    根据发送广播sendBroadcast(Intent intent)中的intent的flags是否包含FLAG_RECEIVER_FOREGROUND来决定把该广播是放入前台广播队列或者后台广播队列,,默认情况下广播是放入后台广播队列,除非指明加上FLAG_RECEIVER_FOREGROUND标识。

    另外,只有串行处理的广播才有超时机制,因为接收者是串行处理的,前一个receiver处理慢,会影响后一个receiver;并行广播通过一个循环一次性向所有的receiver分发广播事件,所以不存在彼此影响的问题,则没有广播超时。

什么进程属于SCHED_GROUP_BACKGROUND调度组呢?进程调度组大体可分为TOP、前台、后台,进程优先级(Adj)和进程调度组(SCHED_GROUP)算法较为复杂,其对应关系可粗略理解为Adj等于0的进程属于Top进程组,Adj等于100或者200的进程属于前台进程组,Adj大于200的进程属于后台进程组。关于Adj的含义见下表,简单来说就是Adj>200的进程对用户来说基本是无感知,主要是做一些后台工作,故后台服务拥有更长的超时阈值,同时后台服务属于后台进程调度组,相比前台服务属于前台进程调度组,分配更少的CPU时间片。


ANR-adj级别.png

ANR问题定位

logcat日志分析

查看mobilelog文件夹下的events_log,从日志中搜索关键字:am_anr,找到出现ANR的时间点、进程PID、ANR类型。

出现ANR的一般有以下几种类型:
1:KeyDispatchTimeout(常见)
input事件在5S内没有处理完成发生了ANR。
logcat日志关键字:ActivityManagerService,inputDispatchingTimedOut,Input event dispatching timed out

2:BroadcastTimeout
前台Broadcast:onReceiver在10S内没有处理完成发生ANR。
后台Broadcast:onReceiver在60s内没有处理完成发生ANR。
logcat日志关键字:Timeout of broadcast BroadcastRecord

3:ServiceTimeout
前台Service:onCreateonStartonBind等生命周期在20s内没有处理完成发生ANR。
后台Service:onCreateonStartonBind等生命周期在200s内没有处理完成发生ANR
logcat日志关键字:Timeout executing service

4:ContentProviderTimeout
ContentProvider 在10S内没有处理完成发生ANR。 logcat日志关键字:timeout publishing content providers

trace文件分析

  1. 人为的收集trace.txt的命令
    adb shell kill -3 888 //可指定进程pid
    执行完该命令后traces信息的结果保存到文件/data/anr/traces.txt
  2. trace文件解读
----- pid 888 at 2016-11-11 22:22:22 -----
Cmd line: system_server
ABI: arm
Build type: optimized
Zygote loaded classes=4113 post zygote classes=3239
Intern table: 57550 strong; 9315 weak
JNI: CheckJNI is off; globals=2418 (plus 115 weak)
Libraries: /system/lib/libandroid.so /system/lib/libandroid_servers.so /system/lib/libaudioeffect_jni.so /system/lib/libcompiler_rt.so /system/lib/libjavacrypto.so /system/lib/libjnigraphics.so /system/lib/libmedia_jni.so /system/lib/librs_jni.so /system/lib/libsechook.so /system/lib/libshell_jni.so /system/lib/libsoundpool.so /system/lib/libwebviewchromium_loader.so /system/lib/libwifi-service.so /vendor/lib/libalarmservice_jni.so /vendor/lib/liblocationservice.so libjavacore.so (16)
//已分配堆内存大小40MB,其中29M已用,总分配207772个对象 
Heap: 27% free, 29MB/40MB; 307772 objects
... //省略GC相关信息

//当前进程总99个线程
DALVIK THREADS (99):
//主线程调用栈
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x75bd9fb0 self=0x5573d4f770
  | sysTid=12078 nice=-2 cgrp=default sched=0/0 handle=0x7fa75fafe8
  | state=S schedstat=( 5907843636 827600677 5112 ) utm=453 stm=137 core=0 HZ=100
  | stack=0x7fd64ef000-0x7fd64f1000 stackSize=8MB
  | held mutexes=
  //内核栈
  kernel: __switch_to+0x70/0x7c
  kernel: SyS_epoll_wait+0x2a0/0x324
  kernel: SyS_epoll_pwait+0xa4/0x120
  kernel: cpu_switch_to+0x48/0x4c
  native: #00 pc 0000000000069be4 /system/lib64/libc.so (__epoll_pwait+8)
  native: #01 pc 000000000001cca4 /system/lib64/libc.so (epoll_pwait+32)
  native: #02 pc 000000000001ad74 /system/lib64/libutils.so (_ZN7android6Looper9pollInnerEi+144)
  native: #03 pc 000000000001b154 /system/lib64/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+80)
  native: #04 pc 00000000000d4bc0 /system/lib64/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+48)
  native: #05 pc 000000000000082c /data/dalvik-cache/arm64/system@[email protected] (Java_android_os_MessageQueue_nativePollOnce__JI+144)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:323)
  at android.os.Looper.loop(Looper.java:135)
  at com.android.server.SystemServer.run(SystemServer.java:290)
  at com.android.server.SystemServer.main(SystemServer.java:175)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)
  
"Binder_1" prio=5 tid=8 Native
  | group="main" sCount=1 dsCount=0 obj=0x12c610a0 self=0x5573e5c750
  | sysTid=12092 nice=0 cgrp=default sched=0/0 handle=0x7fa2743450
  | state=S schedstat=( 796240075 863170759 3586 ) utm=50 stm=29 core=1 HZ=100
  | stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB
  | held mutexes=
  kernel: __switch_to+0x70/0x7c
  kernel: binder_thread_read+0xd78/0xeb0
  kernel: binder_ioctl_write_read+0x178/0x24c
  kernel: binder_ioctl+0x2b0/0x5e0
  kernel: do_vfs_ioctl+0x4a4/0x578
  kernel: SyS_ioctl+0x5c/0x88
  kernel: cpu_switch_to+0x48/0x4c
  native: #00 pc 0000000000069cd0 /system/lib64/libc.so (__ioctl+4)
  native: #01 pc 0000000000073cf4 /system/lib64/libc.so (ioctl+100)
  native: #02 pc 000000000002d6e8 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb+164)
  native: #03 pc 000000000002df3c /system/lib64/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+24)
  native: #04 pc 000000000002e114 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+124)
  native: #05 pc 0000000000036c38 /system/lib64/libbinder.so (???)
  native: #06 pc 000000000001579c /system/lib64/libutils.so (_ZN7android6Thread11_threadLoopEPv+208)
  native: #07 pc 0000000000090598 /system/lib64/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+96)
  native: #08 pc 0000000000014fec /system/lib64/libutils.so (???)
  native: #09 pc 0000000000067754 /system/lib64/libc.so (_ZL15__pthread_startPv+52)
  native: #10 pc 000000000001c644 /system/lib64/libc.so (__start_thread+16)
  (no managed stack frames)
... //此处省略剩余的N个线程.
  1. trace参数解读
"Binder_1" prio=5 tid=8 Native
  | group="main" sCount=1 dsCount=0 obj=0x12c610a0 self=0x5573e5c750
  | sysTid=12092 nice=0 cgrp=default sched=0/0 handle=0x7fa2743450
  | state=S schedstat=( 796240075 863170759 3586 ) utm=50 stm=29 core=1 HZ=100
  | stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB
  | held mutexes=
  • 第0行:
    • 线程名: Binder_1(如有daemon则代表守护线程)

    • prio: 线程优先级

    • tid: 线程内部id

    • 线程状态: NATIVE


      ANR线程状态对照表.png
  • 第1行:
    • group: 线程所属的线程组
    • sCount: 线程挂起次数
    • dsCount: 用于调试的线程挂起次数
    • obj: 当前线程关联的java线程对象
    • self: 当前线程地址
  • 第2行:
    • sysTid:线程真正意义上的tid
    • nice: 调度有优先级
    • cgrp: 进程所属的进程调度组
    • sched: 调度策略
    • handle: 函数处理地址
  • 第3行:
    • state: 线程状态
    • schedstat: CPU调度时间统计
    • utm/stm: 用户态/内核态的CPU时间(单位是jiffies)
    • core: 该线程的最后运行所在核
    • HZ: 时钟频率
  • 第4行:
    • stack:线程栈的地址区间
    • stackSize:栈的大小
  • 第5行:
    • mutex: 所持有mutex类型,有独占锁exclusive和共享锁shared两类
  • schedstat含义说明:
    • nice值越小则优先级越高。此处nice=-2, 可见优先级还是比较高的;

    • schedstat括号中的3个数字依次是Running、Runable、Switch,紧接着的是utm和stm

      • Running时间:CPU运行的时间,单位ns
      • Runable时间:RQ队列的等待时间,单位ns
      • Switch次数:CPU调度切换次数
      • utm: 该线程在用户态所执行的时间,单位是jiffies,jiffies定义为sysconf(_SC_CLK_TCK),默认等于10ms
      • stm: 该线程在内核态所执行的时间,单位是jiffies,默认等于10ms
  • 可见,该线程Running=186667489018ns,也约等于186667ms。在CPU运行时间包括用户态(utm)和内核态(stm)。 utm + stm = (12112 + 6554) ×10 ms = 186666ms。
  • 结论:utm + stm = schedstat第一个参数值。

ANR 案例整理

一、主线程被其他线程lock,导致死锁

waiting on <0x1cd570> (a android.os.MessageQueue)
DALVIK THREADS:
"main" prio=5 tid=3 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 s=0 obj=0x400143a8
  | sysTid=691 nice=0 sched=0/0 handle=-1091117924
  at java.lang.Object.wait(Native Method)
  - waiting on <0x1cd570> (a android.os.MessageQueue)
  at java.lang.Object.wait(Object.java:195)
  at android.os.MessageQueue.next(MessageQueue.java:144)
  at android.os.Looper.loop(Looper.java:110)
  at android.app.ActivityThread.main(ActivityThread.java:3742)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
  at dalvik.system.NativeStart.main(Native Method)

"Binder Thread #3" prio=5 tid=15 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x434e7758
  | sysTid=734 nice=0 sched=0/0 handle=1733632
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #2" prio=5 tid=13 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x1cd570
  | sysTid=696 nice=0 sched=0/0 handle=1369840
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #1" prio=5 tid=11 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433aca10
  | sysTid=695 nice=0 sched=0/0 handle=1367448
  at dalvik.system.NativeStart.run(Native Method)

----- end 691 -----

二、主线程做耗时的操作:比如数据库读写。

"main" prio=5 tid=1 Native
held mutexes=
kernel: (couldn't read /proc/self/task/11003/stack)
native: #00 pc 000492a4 /system/lib/libc.so (nanosleep+12)
native: #01 pc 0002dc21 /system/lib/libc.so (usleep+52)
native: #02 pc 00009cab /system/lib/libsqlite.so (???)
native: #03 pc 00011119 /system/lib/libsqlite.so (???)
native: #04 pc 00016455 /system/lib/libsqlite.so (???)
native: #16 pc 0000fa29 /system/lib/libsqlite.so (???)
native: #17 pc 0000fad7 /system/lib/libsqlite.so (sqlite3_prepare16_v2+14)
native: #18 pc 0007f671 /system/lib/libandroid_runtime.so (???)
native: #19 pc 002b4721 /system/framework/arm/boot-framework.oat (Java_android_database_sqlite_SQLiteConnection_nativePrepareStatement__JLjava_lang_String_2+116)
at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:294)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:215)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:808)
locked <0x0db193bf> (a java.lang.Object)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:793)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:696)
at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:690)
at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:299)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:223)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)
locked <0x045a4a8c> (a com.xxxx.video.common.data.DataBaseHelper)
at com.xxxx.video.common.data.DataBaseORM.(DataBaseORM.java:46)
at com.xxxx.video.common.data.DataBaseORM.getInstance(DataBaseORM.java:53)
locked <0x017095d5> (a java.lang.Class)

三、binder数据量过大

07-21 04:43:21.573  1000  1488 12756 E Binder  : Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 388568 (data: 1, 32, 7274595)
07-21 04:43:21.573  1000  1488 12756 E Binder  : android.util.Log$TerribleFailure: Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 388568 (data: 1, 32, 7274595)
07-21 04:43:21.607  1000  1488  2951 E Binder  : Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 211848 (data: 1, 23, 7274595)
07-21 04:43:21.607  1000  1488  2951 E Binder  : android.util.Log$TerribleFailure: Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 211848 (data: 1, 23, 7274595)
07-21 04:43:21.662  1000  1488  6258 E Binder  : Unreasonably large binder reply buffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling 1 size 259300 (data: 1, 33, 7274595)

四、binder 通信失败

07-21 06:04:35.580 <6>[32837.690321] binder: 1698:2362 transaction failed 29189/-3, size 100-0 line 3042
07-21 06:04:35.594 <6>[32837.704042] binder: 1765:4071 transaction failed 29189/-3, size 76-0 line 3042
07-21 06:04:35.899 <6>[32838.009132] binder: 1765:4067 transaction failed 29189/-3, size 224-8 line 3042
07-21 06:04:36.018 <6>[32838.128903] binder: 1765:2397 transaction failed 29189/-22, size 348-0 line 2916

ANR监控方案

  1. ANR-WatchDog

    ANR-WatchDog 是参考 Android WatchDog 机制(com.android.server.WatchDog.java)起个单独线程向主线程发送一个变量 +1 操作,自我休眠自定义 ANR 的阈值,休眠过后判断变量是否 +1 完成,如果未完成则告警。

ANR-watchDog.png
  • 优点:

    • (1) 兼容性好,无需适配机型。

    • (2) 无需改动APP逻辑代码,非侵入性。

    • (3) 性能影响不大。

    • 缺点:

      • (1) 无法保证能捕获所有ANR,对阈值设置影响捕获概率。如时间过长,中间发生的ANR则可能被遗漏掉。

    ANR-WatchDog仓库链接 https://github.com/SalomonBrys/ANR-WatchDog#how-it-works

  1. xCrash

    Android 应用在收到异常终止信号(SIGQUIT)时,没有遵循传统 UNIX信号模型的默认行为 (终止 + core )。而是打印出trace 文件来,以利于记录应用异常终止的原因。

    核心代码位于xc_trace.c ,初始化时开启了一个常驻线程,当捕捉SIGQUIT信号时,既没有nativecrash也没有javacrash,就认为是ANR了。


    while(1)
    {
        //block here, waiting for sigquit
        XCC_UTIL_TEMP_FAILURE_RETRY(read(xc_trace_notifier, &data, sizeof(data)));
        
        //check if process already crashed
        if(xc_common_native_crashed || xc_common_java_crashed) break;
        
        ......
        //记录ANR信息
ANR-Xcrash原理.png

xCrash仓库链接 https://github.com/iqiyi/xCrash/blob/master/README.zh-CN.md

  1. BlockCanary

    利用 Looper.setMessageLogging(Printer printer);

    • 优点:灵活配置可监控常见APP应用性能也可作为一部分场景的ANR监测,并且可以准确定位ANR和耗时调用栈。

    • 缺点:

      (1) 谷歌已经明确标注This must be in a local variable, in case a UI event sets the logger这个looger对象是可以被更改的,已经有开发者遇到在使用WebView时logger被set为Null导致BlockCanary失效,只只能让BlockCanary在WebView初始化之后调用start。
      (2) 如果dispatchMessage执行的非常久是无法触发BlockCanary的逻辑。
      (3) 在Printer输出之前,有一段代码queue.next()也会可能发生ANR,从而造成无法监控到ANR。
      (4) 无法监控CPU资源紧张造成系统卡顿无法响应的ANR。

    BlockCanary 仓库链接 https://github.com/seiginonakama/BlockCanaryEx

  2. matrix-TraceCanary模块

    利用 Looper.setMessageLogging(Printer printer);

    利用IdleHandler 闲时机制循环设置Printer,避免Looper中的Printer为空

    核心代码位于com.tencent.matrix.trace.core.LooperMonitor的构造方法中

        private synchronized void resetPrinter() {
            Printer originPrinter = null;
            try {
                if (!isReflectLoggingError) {
                    originPrinter = ReflectUtils.get(looper.getClass(), "mLogging", looper);
                    if (originPrinter == printer && null != printer) {
                        return;
                    }
                }
            } catch (Exception e) {
                isReflectLoggingError = true;
                Log.e(TAG, "[resetPrinter] %s", e);
            }
    
            if (null != printer) {
                MatrixLog.w(TAG, "maybe thread:%s printer[%s] was replace other[%s]!",
                        looper.getThread().getName(), printer, originPrinter);
            }
            looper.setMessageLogging(printer = new LooperPrinter(originPrinter));
            if (null != originPrinter) {
                MatrixLog.i(TAG, "reset printer, originPrinter[%s] in %s", originPrinter, looper.getThread().getName());
            }
        }
    

    matrix仓库链接 https://github.com/mmin18/SafeLooper

  3. SafeLooper(弃用)

    SafeLooper通过hook Looper,捕获dispatchMsg() 产生的异常并通过 Thread.UncaughtExceptionHandler捕获异常,这种方式与常用的java层crash监控方式有冲突,并且SafeLooper最后维护时间为2013年

ANR-safelooper.png

SafeLooper仓库链接 https://github.com/mmin18/SafeLooper

  1. FileObserver(不建议)

    FileObserver监听/data/anr目录下文件是否有新增".tarce"结尾的文件,如果有则认为发生ANR,并导出trace文件,注意如果当多个APP同时发生ANR,里面会有多个trace文件,需要对包名时间等进行过滤。

    • 优点
      1、基于原生接口调用,时机和内容准确。
      2、无性能问题实现简单

    • 缺点
      最大的困难是兼容性问题,这个方案受限于 Android 系统的 SELinux 机制,5.0 以后基本已经使低权限应用无法监听到 trace 文件了,但是可以在开发内测阶段通过 root 手机修改 app 对应的 te 文件提权进行监控。

      SEAndroid及SELinux 参考链接 https://blog.csdn.net/qq_19923217/article/details/81240027

你可能感兴趣的:(Android ANR)