美文网首页
Android ANR

Android ANR

作者: maybe0813 | 来源:发表于2021-05-12 22:43 被阅读0次

    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@framework@boot.oat (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.<init>(DataBaseORM.java:46)
    at com.xxxx.video.common.data.DataBaseORM.getInstance(DataBaseORM.java:53)
    locked <0x017095d5> (a java.lang.Class<com.xxxx.video.common.data.DataBaseORM>)
    

    三、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

          本文链接:https://www.haomeiwen.com/subject/gjulvktx.html