APM|流畅性三板斧之ANR监控与处理

这是流畅性三板斧的第三篇文章,阅览前两篇会对理解该篇会有协助,建议一起食用

Android 流畅性三板斧之帧率监控(/post/721780…)

Android 流畅性三板斧之卡顿监控(/post/721877…)

Android 流畅性三板斧之ANR监控 (/post/722004…)

1 哪来的ANR

ANR(Application Not responding):假如 Android 使用的界面线程处于阻塞状况的时刻过长,会触发“使用无呼应”(ANR) 过错。假如使用坐落前台,体系会向用户显现一个对话框。ANR 对话框会为用户供给强制退出使用的选项。

ANR 是一个问题,因为负责更新界面的使用主线程无法处理用户输入事情或制作操作,这会引起用户的不满

以上是官方对ANR的描述,ANR的意图是从体系层面对严峻影响用户直接感知的输入和界面制作操作的行为进行的一种自我保护。

1.1 四种条件

在体系层面触发ANR的就只要四个节点。也便是说即时主线程有一个很长的耗时使命,假如没有触发ANR发生的条件也不会发生ANR。

呈现以下任何状况时,体系都会针对您的使用触发 ANR:

  • 输入调度的超时,阈值为5秒
  • Service履行超时,无法在阈值内完成服务的创建和发动也会触发ANR,前台使命的阈值为10s后台使命的阈值为60s
  • ContentProvider调度超时,这个阈值为10s
  • BroadcastReceiver 调度超时,这个阈值为10s

1.2 体系发生ANR大约流程

以BroadcastReceive超时为例,看下体系如何触发ANR

1.2.1 BroadcastReceiver 发生ANR

BroadcastReceiver处理播送的中心逻辑坐落BroadcastQueue

public final class BroadcastQueue {
    final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
     
     setBroadcastTimeoutLocked(timeoutTime);
      ...
     performReceiveLocked(...);//内部终究会调用BroadcastReceiver的onReceiver
          ...
     cancelBroadcastTimeoutLocked();//免除超时
        ..
     }
    // 设置超时
    final void setBroadcastTimeoutLocked(long timeoutTime) {
      if (!mPendingBroadcastTimeoutMessage) {
        Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
        mHandler.sendMessageAtTime(msg, timeoutTime);
        mPendingBroadcastTimeoutMessage = true;
       }
     }
      //免除超时
     final void cancelBroadcastTimeoutLocked() {
    if (mPendingBroadcastTimeoutMessage) {
      mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
      mPendingBroadcastTimeoutMessage = false;
     }
}
​

以上是播送完毕者设置、免除、触发ANR的中心逻辑。经过handler机制延迟发送一个【ANR 使命】,在规则时刻内完成了你播送接收者使命移除ANR使命。否则触发。

1.2.1 体系处理ANR

实际不管何种条件触发了ANR终究都交给AnrHelper处理,这个类中中心处理ANR的逻辑敞开一个名为“AnrConsumer”的线程。履行在ProcessErrorStateRecord中appNotResponding()的办法。

 void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
             String parentShortComponentName, WindowProcessController parentProcess,
             boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
    ArrayList<Integer> firstPids = new ArrayList<>(5);
    SparseArray<Boolean> lastPids = new SparseArray<>(20);
        ...
    setNotResponding(true);//标记ANR标识
       ...
    firstPids.add(pid);
       ...
    isSilentAnr = isSilentAnr();//后台的使用发生ANR
    if (!isSilentAnr && !onlyDumpSelf) {//前台进程和不仅仅dump本身时
      mService.mProcessList.forEachLruProcessesLOSP(false, r -> {
        ...
        firstPids.add(pid);//增加其他进程
       }
     }); 
     ...
    StringBuilder report = new StringBuilder();
    report.append(MemoryPressureUtil.currentPsiState());//内存信息
    ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);//cup信息
     
    nativePids.add(..); //增加native进程
     ...
    File tracesFile =  .. 
    report.append(tracesFileException.getBuffer());
    info.append(processCpuTracker.printCurrentState(anrTime));
​
    if (tracesFile == null) {
      Process.sendSignal(pid, Process.SIGNAL_QUIT); //不dump信息时直接发送SIGNAL_QUIT信号
     }
        ...
    File tracesFile = ActivityManagerService.dumpStackTraces(..); //dump栈
      ...
    mService.mUiHandler.sendMessageDelayed(msg, anrDialogDelayMs);//ANR弹窗
   }
​

值得留意的点

  • 缄默沉静的ANR”,前台ANR会弹无呼应的Dialog,后台ANR会直接杀死进程**。
  • dump信息时优先dump发生ANR进程的信息,条件允许dump其他关联进程和native进程。假如体系进程有很多ANR需求处理,且耗时现已超越60s或者是缄默沉静进程就只会dump发生ANR进程信息
  • dump全部进程的总时刻不能超越20秒,假如超越了,立刻返回,保证ANR弹窗能够及时的弹出(或者被kill掉)
  • Process.sendSignal(pid, Process.SIGNAL_QUIT);体系会宣布Process.SIGNAL_QUIT信号。这个很重要

APM|流畅性三板斧之ANR监控与解决

(图来自微信团队)

当使用发生ANR之后,体系会搜集许多进程,来dump仓库,然后生成ANR Trace文件,搜集的第一个,也是必定会被搜集到的进程,便是发生ANR的进程,接着体系开端向这些使用进程发送SIGQUIT信号,使用进程收到SIGQUIT后开端dump仓库。

2 使用层怎么监控

Android M(6.0) 版本之后,使用侧不能直接经过监听 data/anr/trace 文件,监控是否发生 ANR。

2.1 WatchDog方案

该方案咱们在卡顿监控文章里也介绍过,主要思路便是超时检测,检测主线程MessageQueue在规则时刻(5s)内是否处理了给定的音讯。假如规则时刻内没有处理掉给定的音讯就以为发生了ANR。

该方案用于检测ANR的坏处:

  • 不精确:该方案触发了超时条件纷歧定会发生ANR。5秒超时仅仅ToucEvent 未被消费发生ANR的条件。其他的发生ANR的条件的并不是5s;
  • 不优雅:该方案会一直让主线程音讯调度一直处于“繁忙状况”,对使用功耗和负载有不必要的影响。

2.2 监听信号方案(SIGQUIT

体系发生ANR的时分,宣布SIGQUIT信号,经过监听这一信号,咱们能够判别ANR的发生。该方案也是市面上监听ANR的主要方案。

除Zygote进程外,每个进程有SignalCatcher线程,捕获SIGQUIT信号然后做相应的动作。Android默认把SIGQUIT设置为BLOCKED,这意味着只能只要SignalCatcher线程能监听到SIGQUIT信号,咱们注册sigaction监听不到。咱们把SIGQUIT设置为UNBLOCK这样就可能收到信号。但要留意,需求讲信号从头发送出去,不损坏体系的机制。

2.2.1 误报 & 完善

体系宣布SIGQUIT 信号纷歧定该使用发生了ANR,其他状况下也会宣布’SIGQUIT’信号,比方其他进程发生了ANR

源码里找答案

  private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg) {
    setNotResponding(true);
    // mAppErrors can be null if the AMS is constructed with injector only. This will only
    // happen in tests.
    if (mService.mAppErrors != null) {
      mNotRespondingReport = mService.mAppErrors.generateProcessError(mApp,
          ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, //把发生ANR的进程
          activity, shortMsg, longMsg, null);
     }
    startAppProblemLSP();
    mApp.getWindowProcessController().stopFreezingActivities();
   }
​

发生ANR时体系会把发生ANR的进程标记 NOT_RESPONDING,咱们能够在使用层经过ActivityManager check该状况,代码乳如下:

private static boolean checkErrorState() {
  try {
   
    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){
   }
  return false;
}

当收到SIGQUIT信号之后,在一个时刻段内不断的check该状况,假如获取到了该标识,就能够以为当时进程发生了ANR。

2.2.2 漏报&完善

有些ANR发生不会把进程不会设置NOT_RESPONDING标识

  • 缄默沉静ANR(SilentAnr),SilentANR 会直接kill进程,不会设置该标识。
  • 闪退ANR,OPPO VIVO 的机型ANR之后,会直接闪退,也不会设置该标识。

应对方案:结合主线程的卡顿状况。

反射获取主线程MessageQueuemMessages 对象,此对象的when变量时预期该音讯被处理的时刻,该变量和当时时刻做差就能得到该音讯等候的时刻,被耽搁的耗时,假如该耗过长,就说明主线程‘卡顿’了。

收到了SIGQUIT且当时主线程卡顿了,就以为发生了 ANR。

2.2.3 ANR 监控小结

APM|流畅性三板斧之ANR监控与解决

经过监听体系SIGQUIT信号结合check 当时进程的 NOT_RESPONDING标识和主线程的卡顿状况,归纳判定为该进程发生了ANR。

这仅仅咱们知道发生了ANR,知道发生了ANR,进一步知道什么导致了ANR,收集ANR发生时的上下文环境信息,处理ANR更重要。

3 信息的收集监控

3.1 ANR问题定位难点

调查ANR信息收集的难点在于往往信息收集不精确、不全面,当ANR发生的当下收集的信息并不是ANR的真正诱因,因而收集的信息对排查问题的参考价值大折扣。

APM|流畅性三板斧之ANR监控与解决

如上图所示,在主线程耗时的使命现已履行完毕,service发动使命在超越了规则的阈值发生了ANR,此时收集的信息是一个正常的使命调用信息。

总的来说,诱发ANR的原因分主线程履行耗时过大和体系负载过重。

主线程使命履行耗时过大又可大约分为一下几种

  • 前史音讯里有多个耗时较重,触发了ANR。
  • 前史音讯里有一个耗时极端重的音讯。
  • 极端多个耗时小音讯履行归纳起来耗时严峻,触发ANR。

体系负载过重,包含体系内存缺乏,cpu负载,导致使命得不到履行。

假如能较完整记载过完一段时刻段内主线程前史音讯使命、当时执使命和即将履行的使命以及体系负载状况,对咱们更为精确的诊断ANR问题有十分重要的意义。

3.2 音讯调度监控

主线程中记载监控Looper音讯履行方案,咱们天然的把目光搬运到了Looper的Printer方案上。关于这个在三板斧的前两篇文章都介绍过这儿不展开。

Looper分发音讯履行的时分,前后都打印音讯信息,咱们依此能够获取到音讯使命的相关信息,包活Message的targetwhatcallback、音讯履行的耗时等。

音讯耗时,需求收集主线程的WallTime和ThreadTime。

  • WallTime:使命占用的时刻,包含等候锁,线程休眠时刻片流转的时刻。
  • ThreadTime(CpuTime)时线程实在履行的时刻,不包含等候锁等时刻。依次咱们能够在旁边面推断体系负载状况。

大部分状况下,音讯履行都耗时较短,Looper也会有Idel状况,即无音讯履行的状况,咱们需求对这些音讯进行聚合处理。

此外,三板斧系列文章主线程耗时监控里有介绍主线程处理音讯除了Looper正常的分发的音讯需求监控外,IdleHandler、TouchEvent音讯也要归入到计算记载里才更为完整。

3.2.1 音讯聚合以及分类

  • 接连的耗时较小的音讯聚合计算,接连小于50ms 的音讯,聚合成一条记载,该记载里存储音讯的数量,和总耗时信息等
  • 超越阈值的音讯单做一条记载计算。
  • 体系调用音讯计算(ActivityThread.H Activity 、Service、 ContentProvider),这些对咱们剖析ANR问题很重要。
  • IDLE状况音讯独自计算。
  • IdleHandler 的计算。
  • TouchEvent 等native 层触发的主线程调度使命计算。

归纳来说,把音讯分类型,聚合类型(Agree),接连的耗时较少的音讯。耗时类型(Huge):超越50ms的音讯。体系调用音讯(SYSTEM)

3.2.2 音讯仓库收集

除了,计算记载Looper Messge的what、callback和耗时之外,每个音讯内究竟履行了什么哪些动作也需求收集,这就需求收集每个音讯的履行的栈,频繁的收集履行栈对功能影响较大,要进行有战略的收集。

  • 敞开子线程收集主线程的仓库。
  • 耗时较少的音讯使命不收集。
  • 超越必定阈值还未履行完毕的音讯使命进行一次收集,再隔一段时刻假如本次音讯使命还未履行完,再进行收集,间隔时刻是依此线性增加。
  • 【shoppe的非堵塞式高效抓栈】

3.2.3 正在履行的音讯和pending音讯计算

除了监控 ANR 发生之前主线程前史音讯调度及耗时之外,也需求知道 ANR 发生时正在调度的音讯及其耗时,以便于在看到 ANR 的 Trace 仓库时,能够明晰的知道当时 Trace 逻辑究竟履行了多长时刻

MessageQueue中等候履行的音讯也很有必要计算

  • 对咱们知道什么组件诱发了ANR的发生
  • 能够计算等候履行的音讯等候了多久。判别音讯队列繁忙程度。

3.3 更全面的信息收集

以上咱们比较全面的监控计算主线程调度使命的耗时,

3.3.1 获取ANRInfo

使用层可经过AcivityManager获取ANRInfo

  val am = application.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
  val processesInErrorStates = am.processesInErrorState

ProcessesInErrorState中可获取到

 shortMsg: ANR Inputdispatching timed out ...
  • shortMessage:ANR 发生的直接原因,TouchEvent超时,Servcie发动超时等。
Reason: Input dispatching timed out
xxx is not responding. Waited 5003ms for FocusEvent(hasFocus=false))                                                                                     
Load: 3.19 / 2.46 / 2.42
​
----- Output from /proc/pressure/memory -----
some avg10=0.00 avg60=0.00 avg300=0.00 total=144741615
full avg10=0.00 avg60=0.00 avg300=0.00 total=29370150
----- End output from /proc/pressure/memory -----
 
CPU usage from 0ms to xxx ms later with 99% awake:
​
TOTAL: 6.4% user + 8.2% kernel + 0% iowait + 0.6% irq + 0.1% softirq
​
CPU usage from 0ms to xxx ms later with 99% awake:
​
27% TOTAL: 10% user + 14% kernel + 0.3% iowait + 0.9% irq + 0.3% softirq
                                                    
  • longMessage:包含体系负载,cup使用状况,IO状况等体系状况。

3.3.2 Logcat 日志

使用运行时收集logcat日志,留意需求控制量有相应的战略,定时整理。

String cmd = "logcat -f " + file.getAbsolutePath();
Runtime.getRuntime().exec(cmd);

3.3.3 当时进程其他线程的仓库信息

Java 层获取各线程仓库,或经过反射方法获取到虚拟机内部 Dump 线程仓库的接口,在内存映射的函数地址,强制调用该接口,并将数据重定向输出到本地。

这样当ANR发生时咱们就有丰厚的信息供咱们参考,包含主线程曩昔现在和未来的调度信息,体系的信息,线程信息,Logcat信息。

4 问题剖析定位和处理

4.1 剖析主线程调度状况

检查咱们主线程使命调度,是否有显着的耗时使命履行,诱发ANR发生。

  • 剖析咱们记载的音讯调度的wallTime和cputime
  • 定位记载的耗时音讯仓库定位出问题
  • 留意有可能呈现接连的十分多的耗时小的音讯也会形成ANR

4.2 ANR Info解读

4.2.1 体系负载状况

Load: xx/xx/xx

ANR发生的前1分钟 5分钟和15分钟时刻段内的CPU负载,数值代表着等候体系调度的使命数,数值过高,意味着体系有CPU和IO竞赛剧烈,咱们的使用进程可能收到影响

4.2.2 CPU占用状况

CPU usage from 0ms to xxx ms later with xx% awake:

14% 1673/system_server: 8% user + 6.7% kernel / faults: 12746 minor
13% 30829/tv.danmaku.bili: 7.3% user + 6.2% kernel / faults: 24286 minor
6.6% 31147/tv.danmaku.bili:ijkservice: 3.7% user + 2.8% kernel / faults: 11880 minor
6% 574/logd: 2.1% user + 3.8% kernel / faults: 64 minor
..
TOTAL: 6.4% user + 8.2% kernel + 0% iowait + 0.6% irq + 0.1% softirq

CPU usage from xxms to xxxms later 
73% 1673/system_server: 49% user + 24% kernel / faults: 1695 minor
  33% 2330/AnrConsumer: 12% user + 21% kernel
  15% 1683/HeapTaskDaemon: 15% user + 0% kernel
  9.2% 7013/Binder:1673_12: 6.1% user + 3% kernel
  6.1% 1685/ReferenceQueueD: 6.1% user + 0% kernel
  6.1% 2715/HwBinder:1673_5: 6.1% user + 0% kernel
  3% 2529/PhotonicModulat: 0% user + 3% kernel
25% 30829/tv.danmaku.bili: 4.2% user + 21% kernel / faults: 423 minor
  25% 31050/thread_ad: 4.2% user + 21% kernel
 ...
 ...                                                  
27% TOTAL: 10% user + 14% kernel + 0.3% iowait + 0.9% irq + 0.3% softirq

如上,表示ANR发生前后的CPU占用状况,以及这些进程具体占用状况。

  • user:用户态
  • kernel:内核态
  • iowait:io等候。过高可能发生文件读写或内存严重的状况。
  • irq:硬中止
  • softirq:软中止 占比

留意:单进程CPU的负载并不是以100%为上限,而是有几个核,就有百分之几百,如8核上限为800%

别的,kswapdmmcqd体系关键线程CPU线程过大,往往伴随体系收回资源,影响到使用进程

经过对ANR信息的解读能够更佳全面的协助咱们定位ANR问题。

4.3 Logcat音讯

线上假如记载了Logcat打印音讯,着重从以下方面去看问题

  • onTrimeMemory:接连的onTrimMemory 往往说明APP的内存缺乏可能体系资源缺乏形成ANR
  • Slow operation Slow delivery 呈现该状况体系功能受限。

5 结

从ANR发生的原因,体系处理ANR,使用层监听ANR,应对ANR使用侧比较全面的监控主线程的使命调度以及为了处理ANR收集体系信息最终给出了剖析处理ANR问题的整体思路。

针对ANR问题,这些还远远不够,这儿仅仅一个整体的框架,期望本文更全面的看待处理ANR问题有协助。

到这儿Android流程性三板斧就就完毕了。

Android 流畅性三板斧之帧率监控(/post/721780…)

Android 流畅性三板斧之卡顿监控(/post/721877…)

Android 流畅性三板斧之ANR监控 (/post/722004…)

期望对你有协助,缺乏过错之处海涵指正.

最终必定要点赞 评论保藏鼓舞 谢谢!!

本文正在参与「金石方案」