美文网首页
39.ANR专题

39.ANR专题

作者: 任振铭 | 来源:发表于2023-02-06 08:02 被阅读0次

    ANR

    Application Not Responding

    ANR类型

    Service Timeout:比如前台服务在20s内未执行完成,后台服务Timeout时间是前台服务的10倍,200s;
    BroadcastQueue Timeout:比如前台广播在10s内未执行完成,后台60s
    ContentProvider Timeout:内容提供者,在publish过超时10s;
    InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
    对于input来说即便某次事件执行时间超过Timeout时长,只要用户后续没有再生成输入事件,则不会触发ANR

    ANR文件获取方式

    1.低于Android10系统的手机

    adb pull data/anr
    

    2.大于等于Android10系统的手机
    如果在Android10以上系统,使用adb pull的方式拉取trace文件,会得到permission denied。需要使用新的命令完成

    adb bugreport
    

    导致ANR的原因

    应用层导致ANR:

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

    系统导致ANR:

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

    系统ANR检测机制

    ANR由消息处理机制保证,核心原理是消息调度和超时处理。
    虽然系统触发anr的地方有好几种,但是检测机制和处理机制其实是差不多的,都是在操作前记录时间,然后向消息队列中丢向一个触发Anr的消息(基于消息机制),再执行完响应的操作的时候将消息移除,如果超过指定时间没有移除,那么则会触发anr操作。

    ps发生anr的时候是主线程已经被阻塞了,anr也是基于消息机制,是一个延时消息,那为什么它还能被执行呢?
    发生anr时,被阻塞的是app进程的主线程,而anr延时消息并不是在app进程中处理的。它是通过AMS所在进程中的子线程消息队列中执行的(ams内部除了有主线程的消息队列外,还持有了一个异步的消息队列),所以时间到了之后还是可以被looper取出; 然后将显示anr弹窗的消息发送到AMS主线程的消息队列(mUiHandler),去展示弹窗。

    service: 当Service的生命周期开始时,bumpServiceExecutingLocked()会被调用,紧接着会调用scheduleServiceTimeoutLocked(),向消息队列发送一个延迟消息(20s(后台服务200s)),当Service的生命周期结束时,在serviceDoneExecutingLocked方法中移除这个消息,如果时间到了还没有移除,就提示anr。由AMS调度,利用Handler和Looper,设计了一个TIMEOUT消息交由AMS线程来处理,整个超时机制的实现都是在Java层。

    broadcast:Broadcast ANR,前台的“串行广播消息”必须在10秒内处理完毕,后台的“串行广播消息”必须在60秒处理完毕, 每派发串行广播消息到一个接收器时,都会抛出一个定时消息BROADCAST_TIMEOUT_MSG,如果定时消息响应,则判断是否广播消息处理超时,超时就说明发生了ANR。由AMS调度,利用Handler和Looper,设计了一个TIMEOUT消息交由AMS线程来处理,整个超时机制的实现都是在Java层。

    input: 在InputDispatcher派发输入事件时,会寻找接收事件的窗口,如果无法正常派发,则可能会导致当前需要派发的事件超时(默认是5秒)。 Native层发现超时了,会通知Java层,Java层经过一些处理后,会反馈给Native层,是继续等待还是丢弃当前派发的事件。 InputEvent由InputDispatcher调度,待处理的输入事件都会进入队列中等待,设计了一个等待超时的判断,超时机制的实现在Native层。

    service、broadcast、provider 的ANR原理都是埋定时炸弹和拆炸弹原理,

    但是input的超时检测机制稍微有点不同,需要等收到下一次input事件,才会去检测上一次input事件是否超时,input事件里埋的炸弹是普通炸弹,需要通过扫雷来排查。

    SNR(System Not Respoding)

    SNR由Watchdog机制保证。Watchdog定时(30s)检查一些重要的系统服务,举报长时间阻塞的事件,甚至杀掉system_server进程,让Android系统重启。

    Monitor Checker(addMonitor),用于检查是Monitor对象可能发生的死锁, AMS, PKMS, WMS等核心的系统服务都是Monitor对象。

    Looper Checker(addThread),用于检查线程的消息队列是否长时间处于工作状态。一些重要的线程的消息队列,也会加入到Looper Checker中,譬如AMS, PKMS,这些是在对应的对象初始化时加入的。

    Monitor Checker预警我们不能长时间持有核心系统服务的对象锁,否则会阻塞很多函数的运行; Looper Checker预警我们不能长时间的霸占消息队列,否则其他消息将得不到处理。这两类都会导致系统卡住(System Not Responding)。

    ANR主要流程

    所有ANR,最终都会调用AppErrors的appNotResponding方法

    1.系统监控到app发生ANR后,收集了一些相关进程pid(包括发生ANR的进程),准备让这些进程dump堆栈,从而生成ANR Trace文件。
    2.系统开始向这些进程发送SIGQUIT信号(通过监听这个信号可以实现对anr的监控,如matrix的实现),进程收到SIGQUIT信号之后开始dump堆栈。
    AMS开始按照firstPids、nativePids、extraPids的顺序dump这些进程的堆栈。并且最长dump时间限制为20秒。

    dump的调用过程:

    → dumpStackTraces
    → dumpJavaTracesTombstoned
    → ActivityManagerService#dumpJavaTracesTombstoned()
    → Debug#dumpJavaBacktraceToFileTimeout()
    → android_os_Debug#android_os_Debug_dumpJavaBacktraceToFileTimeout()
    → android_os_Debug#dumpTraces()
    → debuggerd_client#dump_backtrace_to_file_timeout()
    → debuggerd_client#debuggerd_trigger_dump()。

    1.AMS进程间接给需要dump堆栈那个进程发送了一个SIGQUIT信号
    2.除Zygote进程外,每个进程都会创建一个SignalCatcher守护线程,用于捕获SIGQUIT,然后开始dump
    3.这个过程从收到SIGQUIT信号开始到使用socket写Trace结束。

    ANR监控

    一. WatchDog

    ANRWatchDog 是一个自动检测ANR的开源库,往主线程发送消息,并设置超时检测,如果超时还没执行相应消息,则判定为可能发生ANR。需要进一步从系统服务获取相关数据(可通过ActivityManagerService.getProcessesInErrorState()方法获取进程的ANR信息),进一步判定是否真的发生了ANR。

    缺点
    1.存在5s的误差,假设当ANRWatchDog post消息的时候,已经卡了2s,然后再过3s卡完了恢复了,那么ANRWatchDog是监测不到的。
    2.和BlockCanary一样不支持touch事件的监测

    优化ANRWatchDog存在5s的误差的缺点(AnrMonitor)。每隔1s就会检查一下,并提示可能的风险,累计5s就触发ANR流程,这是一个实现思路,代码只提供了一个demo,还需要完善。此方案可以结合ProcessLifecycleOwner,应用在前台才开启检测,进入后台则停止检测。

    二. SIGQUIT信号

    该方案通过去监听SIGQUIT信号,从而感知当前进程可能发生了ANR,需配合当前进程是否处于NOT_RESPONDING状态以及主线程是否卡顿来进行甄别,以免误判。这种方案才是真正的监控ANR,matrix、xCrash都在使用这种方案。已经在微信等app上检验过,稳定性和可靠性都能得到保证。

    1.监听SIGQUIT信号。

    系统监控到app发生ANR后,收集了一些相关进程pid,并向这些进程发送SIGQUIT信号,进程收到SIGQUIT信号之后开始dump堆栈,通过对这个信号的监听,实现anr的监听,实现方式见matrix框架(https://github.com/Tencent/matrix/tree/master/matrix/matrix-android/matrix-trace-canary/src/main)。

    2.监听NOT_RESPONDING标记。

    发生ANR的进程一定会收到SIGQUIT信号;但是收到SIGQUIT信号的进程并不一定发生了ANR。为了解决这个问题,回到源码中,我们会发现执行makeAppNotRespondingLocked方法时,会给发生ANR的进程标记一个NOT_RESPONDING的flag,这个flag可以通过ActivityManager来获取:

    private static boolean checkErrorState() {
        try {
            Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
            ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
            if (procs == null) return false;
            for (ActivityManager.ProcessErrorStateInfo proc : procs) {
                if (proc.pid != android.os.Process.myPid()) continue;
                if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
                return true;
            }
            return false;
        } catch (Throwable t){
            MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
        }
        return false;
    }
    

    监控到SIGQUIT后,我们在20秒内(20秒是ANR dump的timeout时间)不断轮询自己是否有NOT_RESPONDING的flag,一旦发现有这个flag,那么马上就可以认定发生了一次ANR。

    3.结合线程是否处于卡顿状态判断

    发生ANR的进程并不一定会被设置为NOT_RESPONDING状态,如后台ANR会直接被杀死,另外有些机型修改了anr的逻辑,发生anr后直接杀死了进程,所以需要一种机制,在收到SIGQUIT信号后,需要非常快速的侦查出自己是否已经处于ANR的状态,进行快速的dump和上报,可以通过主线程释放处于卡顿状态来判断,可以通过Looper的mMessage对象,该对象的when变量,表示的是当前正在处理的消息入队的时间,我们可以通过when变量减去当前时间,得到的就是等待时间,如果等待时间过长,就说明主线程是处于卡住的状态。这时候收到SIGQUIT信号基本上就可以认为的确发生了一次ANR。

    ANR Trace.txt文件获取

    低版本可以直接拿到,但是高版本无法拿到,怎么解决这个问题?从上边的分析我们知道,除了Zygote进程之外,每个进程都有一个SignalCatcher线程来监听SIGQUIT信号,后边通过socket将dump的内容写入(write)文件中,可以直接hook这里的write,就能直接拿到系统dump的ANR Trace内容。

    Trace.txt文件分析

    "Signal Catcher" daemon prio=5 tid=4 Runnable
      | group="system" sCount=0 dsCount=0 flags=0 obj=0x18c84570 self=0x7252417800
      | sysTid=7772 nice=0 cgrp=default sched=0/0 handle=0x725354ad50
      | state=R schedstat=( 16273959 1085938 5 ) utm=0 stm=1 core=4 HZ=100
      | stack=0x7253454000-0x7253456000 stackSize=991KB
      | held mutexes= "mutator lock"(shared held)
    

    -"Signal Catcher" daemon :线程名,有daemon表示守护线程
    -prio:线程优先级
    -tid:线程内部id
    -线程状态:Runnable
    -group:线程所属的线程组
    -sCount:线程挂起次数
    -dsCount:用于调试的线程挂起次数
    -obj:当前线程关联的Java线程对象
    -self:当前线程地址
    -sysTid:线程真正意义上的tid
    -nice:调度优先级,值越小则优先级越高
    -cgrp:进程所属的进程调度组
    -sched:调度策略
    -handle:函数处理地址
    -state:线程状态
    -schedstat:CPU调度时间统计(schedstat括号中的3个数字依次是Running、Runable、Switch,Running时间:CPU运行的时间,单位ns,Runable时间:RQ队列的等待时间,单位ns,Switch次数:CPU调度切换次数)
    -utm/stm:用户态/内核态的CPU时间
    -core:该线程的最后运行所在核
    -HZ:时钟频率
    -stack:线程栈的地址区间
    -stackSize:栈的大小
    -mutex:所持有mutex类型,有独占锁exclusive和共享锁shared两类

    ANR案例分析

    1.主线程无卡顿,处于正常状态堆栈

    "main" prio=5 tid=1 Native
      | group="main" sCount=1 dsCount=0 flags=1 obj=0x74b38080 self=0x7ad9014c00
      | sysTid=23081 nice=0 cgrp=default sched=0/0 handle=0x7b5fdc5548
      | state=S schedstat=( 284838633 166738594 505 ) utm=21 stm=7 core=1 HZ=100
      | stack=0x7fc95da000-0x7fc95dc000 stackSize=8MB
      | held mutexes=
      kernel: __switch_to+0xb0/0xbc
      kernel: SyS_epoll_wait+0x288/0x364
      kernel: SyS_epoll_pwait+0xb0/0x124
      kernel: cpu_switch_to+0x38c/0x2258
      native: #00 pc 000000000007cd8c  /system/lib64/libc.so (__epoll_pwait+8)
      native: #01 pc 0000000000014d48  /system/lib64/libutils.so (android::Looper::pollInner(int)+148)
      native: #02 pc 0000000000014c18  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
      native: #03 pc 00000000001275f4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
      at android.os.MessageQueue.nativePollOnce(Native method)
      at android.os.MessageQueue.next(MessageQueue.java:330)
      at android.os.Looper.loop(Looper.java:169)
      at android.app.ActivityThread.main(ActivityThread.java:7073)
      at java.lang.reflect.Method.invoke(Native method)
      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:536)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
    

    看起来很正常,主线程是空闲的,因为它正处于nativePollOnce,正在等待新消息。处于这个状态,那还发生了ANR,可能有2个原因:
    1.dump堆栈时机太晚了,ANR已经发生过了,才去dump堆栈,此时主线程已经恢复正常了。
    2.CPU抢占或者内存紧张等其他因素引起。

    2.主线程执行耗时操作

    //模拟主线程耗时操作,View点击的时候调用这个函数
    fun makeAnr(view: View) {
        var s = 0L
        for (i in 0..99999999999) {
            s += i
        }
        Log.d("xxx", "s=$s")
    }
    
    suspend all histogram:    Sum: 206us 99% C.I. 0.098us-46us Avg: 7.629us Max: 46us
    DALVIK THREADS (16):
    "main" prio=5 tid=1 Runnable
      | group="main" sCount=0 dsCount=0 flags=0 obj=0x73907540 self=0x725f010800
      | sysTid=32298 nice=-10 cgrp=default sched=1073741825/2 handle=0x72e60080d0
      | state=R schedstat=( 6746757297 5887495 256 ) utm=670 stm=4 core=6 HZ=100
      | stack=0x7fca180000-0x7fca182000 stackSize=8192KB
      | held mutexes= "mutator lock"(shared held)
      at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:58)
      at java.lang.reflect.Method.invoke(Native method)
      at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
      at android.view.View.performClick(View.java:7317)
      at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
      at android.view.View.performClickInternal(View.java:7291)
      at android.view.View.access$3600(View.java:838)
      at android.view.View$PerformClick.run(View.java:28247)
      at android.os.Handler.handleCallback(Handler.java:900)
      at android.os.Handler.dispatchMessage(Handler.java:103)
      at android.os.Looper.loop(Looper.java:219)
      at android.app.ActivityThread.main(ActivityThread.java:8668)
      at java.lang.reflect.Method.invoke(Native method)
      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
    

    从日志上看,主线程处于执行状态,不是空闲状态,导致ANR了,说明com.xfhy.watchsignaldemo.MainActivity.makeAnr这里有耗时操作。

    3.主线程被锁阻塞

    fun makeAnr(view: View) {
    
        val obj1 = Any()
        val obj2 = Any()
    
        //搞个死锁,相互等待
    
        thread(name = "卧槽") {
            synchronized(obj1) {
                SystemClock.sleep(100)
                synchronized(obj2) {
                }
            }
        }
    
        synchronized(obj2) {
            SystemClock.sleep(100)
            synchronized(obj1) {
            }
        }
    }
    
    "main" prio=5 tid=1 Blocked
      | group="main" sCount=1 dsCount=0 flags=1 obj=0x73907540 self=0x725f010800
      | sysTid=19900 nice=-10 cgrp=default sched=0/0 handle=0x72e60080d0
      | state=S schedstat=( 542745832 9516666 182 ) utm=48 stm=5 core=4 HZ=100
      | stack=0x7fca180000-0x7fca182000 stackSize=8192KB
      | held mutexes=
      at com.xfhy.watchsignaldemo.MainActivity.makeAnr(MainActivity.kt:59)
      - waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22   //注释1
      - locked <0x01abeb23> (a java.lang.Object)
      at java.lang.reflect.Method.invoke(Native method)
      at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:441)
      at android.view.View.performClick(View.java:7317)
      at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1219)
      at android.view.View.performClickInternal(View.java:7291)
      at android.view.View.access$3600(View.java:838)
      at android.view.View$PerformClick.run(View.java:28247)
      at android.os.Handler.handleCallback(Handler.java:900)
      at android.os.Handler.dispatchMessage(Handler.java:103)
      at android.os.Looper.loop(Looper.java:219)
      at android.app.ActivityThread.main(ActivityThread.java:8668)
      at java.lang.reflect.Method.invoke(Native method)
      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
    
    "卧槽" prio=5 tid=22 Blocked  //注释2
      | group="main" sCount=1 dsCount=0 flags=1 obj=0x12c8a118 self=0x71d625f800
      | sysTid=20611 nice=0 cgrp=default sched=0/0 handle=0x71d4513d50
      | state=S schedstat=( 486459 0 3 ) utm=0 stm=0 core=4 HZ=100
      | stack=0x71d4411000-0x71d4413000 stackSize=1039KB
      | held mutexes=
      at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:52)
      - waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
      - locked <0x0c6f8c52> (a java.lang.Object)  
      at com.xfhy.watchsignaldemo.MainActivity$makeAnr$1.invoke(MainActivity.kt:49)
      at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
    
    ......
    

    注意看,下面几行:

    "main" prio=5 tid=1 Blocked
      - waiting to lock <0x0c6f8c52> (a java.lang.Object) held by thread 22
      - locked <0x01abeb23> (a java.lang.Object)
    
    "卧槽" prio=5 tid=22 Blocked
      - waiting to lock <0x01abeb23> (a java.lang.Object) held by thread 1
      - locked <0x0c6f8c52> (a java.lang.Object)  
    

    主线程的tid是1,线程状态是Blocked,正在等待0x0c6f8c52这个Object,而这个Object被thread 22这个线程所持有,主线程当前持有的是0x01abeb23的锁。而卧槽的tid是22,也是Blocked状态,它想请求的和已有的锁刚好与主线程相反。这样的话,ANR原因也就找到了:线程22持有了一把锁,并且一直不释放,主线程等待这把锁发生超时。在线上环境,常见因锁而ANR的场景是SharePreference写入。

    4. CPU被抢占

    CPU usage from 0ms to 10625ms later (2020-03-09 14:38:31.633 to 2020-03-09 14:38:42.257):
      543% 2045/com.test.demo: 54% user + 89% kernel / faults: 4608 minor 1 major //注意看这里
      99% 674/android.hardware.camera.provider@2.4-service: 81% user + 18% kernel / faults: 403 minor
      24% 32589/com.wang.test: 22% user + 1.4% kernel / faults: 7432 minor 1 major
      ......
    

    可以看到,该进程占据CPU高达543%,抢占了大部分CPU资源,因为导致发生ANR,这种ANR与我们的app无关。

    5.内存紧张导致ANR

    如果一份ANR日志的CPU和堆栈都很正常,可以考虑是内存紧张。看一下ANR日志里面的内存相关部分。还可以去日志里面搜一下onTrimMemory,如果dump ANR日志的时间附近有相关日志,可能是内存比较紧张了。

    10-31 22:37:19.749 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
    10-31 22:37:33.458 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
    10-31 22:38:00.153 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
    10-31 22:38:58.731 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
    10-31 22:39:02.816 20733 20733 E Runtime : onTrimMemory level:80,pid:com.xxx.xxx:Launcher0
    

    6.系统服务超时导致ANR

    系统服务超时一般会包含BinderProxy.transactNative关键字,来看一段日志:

    "main" prio=5 tid=1 Native
      | group="main" sCount=1 dsCount=0 flags=1 obj=0x727851e8 self=0x78d7060e00
      | sysTid=4894 nice=0 cgrp=default sched=0/0 handle=0x795cc1e9a8
      | state=S schedstat=( 8292806752 1621087524 7167 ) utm=707 stm=122 core=5 HZ=100
      | stack=0x7febb64000-0x7febb66000 stackSize=8MB
      | held mutexes=
      kernel: __switch_to+0x90/0xc4
      kernel: binder_thread_read+0xbd8/0x144c
      kernel: binder_ioctl_write_read.constprop.58+0x20c/0x348
      kernel: binder_ioctl+0x5d4/0x88c
      kernel: do_vfs_ioctl+0xb8/0xb1c
      kernel: SyS_ioctl+0x84/0x98
      kernel: cpu_switch_to+0x34c/0x22c0
      native: #00 pc 000000000007a2ac  /system/lib64/libc.so (__ioctl+4)
      native: #01 pc 00000000000276ec  /system/lib64/libc.so (ioctl+132)
      native: #02 pc 00000000000557d4  /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+252)
      native: #03 pc 0000000000056494  /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+60)
      native: #04 pc 00000000000562d0  /system/lib64/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+216)
      native: #05 pc 000000000004ce1c  /system/lib64/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+72)
      native: #06 pc 00000000001281c8  /system/lib64/libandroid_runtime.so (???)
      native: #07 pc 0000000000947ed4  /system/framework/arm64/boot-framework.oat (Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+196)
      at android.os.BinderProxy.transactNative(Native method) ————————————————关键行!!!
      at android.os.BinderProxy.transact(Binder.java:804)
      at android.net.IConnectivityManager$Stub$Proxy.getActiveNetworkInfo(IConnectivityManager.java:1204)—关键行!
      at android.net.ConnectivityManager.getActiveNetworkInfo(ConnectivityManager.java:800)
      at com.xiaomi.NetworkUtils.getNetworkInfo(NetworkUtils.java:2)
      at com.xiaomi.frameworkbase.utils.NetworkUtils.getNetWorkType(NetworkUtils.java:1)
      at com.xiaomi.frameworkbase.utils.NetworkUtils.isWifiConnected(NetworkUtils.java:1)
    

    从日志堆栈中可以看到是获取网络信息发生了ANR:getActiveNetworkInfo。系统的服务都是Binder机制(16个线程),服务能力也是有限的,有可能系统服务长时间不响应导致ANR。如果其他应用占用了所有Binder线程,那么当前应用只能等待。可进一步搜索:blockUntilThreadAvailable关键字:
    at android.os.Binder.blockUntilThreadAvailable(Native method)
    如果有发现某个线程的堆栈,包含此字样,可进一步看其堆栈,确定是调用了什么系统服务。此类ANR也是属于系统环境的问题,如果某类型手机上频繁发生此问题,应用层可以考虑规避策略。

    还有不足

    当前Trace堆栈所在业务耗时严重。
    当前Trace堆栈所在业务耗时并不严重,但历史调度有一个严重耗时。
    当前Trace堆栈所在业务耗时并不严重,但历史调度有多个消息耗时。
    当前Trace堆栈所在业务耗时并不严重,但是历史调度存在巨量重复消息(业务频繁发送消息)。
    当前Trace堆栈业务逻辑并不耗时,但是其他线程存在严重资源抢占,如IO、Mem、CPU。
    当前Trace堆栈业务逻辑并不耗时,但是其他进程存在严重资源抢占,如IO、Mem、CPU。
    这里的6个影响因素中,除了第一个以外,其他的根据ANR Trace有可能无法进行判别。这就会导致很多时候看到的ANR Trace里面主线程堆栈对应的业务其实并不耗时(因为可能是前面的消息导致的耗时,但它已经执行完了),如何解决这个问题?

    字节跳动内部有一个监控工具:Raster,大致原理:该工具主要是在主线程消息调度过程进行监控。比较耗时的消息会抓取主线从堆栈,这样可以知道那个耗时的消息具体是在干什么,从而针对性优化。同时对应用四大组件消息执行过程进行监控,便于对这类消息的调度及耗时情况进行跟踪和记录。另外对当前正在调度的消息及消息队列中待调度消息进行统计,从而在发生问题时,可以回放主线程的整体调度情况。此外,该库将系统服务的CheckTime机制迁移到应用侧,应用为线程CheckTime机制,以便于系统信息不足时,从线程调度及时性推测过去一段时间系统负载和调度情况。因此该工具用一句话来概括就是:由点到面,回放过去,现在和将来。

    相关文章

      网友评论

          本文标题:39.ANR专题

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