Android 性能优化——ANR监控与解决_waited 5000ms for focusevent(1)

本文详细解释了Android系统中BroadcastReceiver引发ANR的过程,涉及BroadcastQueue的逻辑、ANR的触发与处理机制,以及应用层如何通过WatchDog和监听SIGQUIT信号监控ANR。还讨论了ANR信息采集的重要性,以及如何通过消息调度监控和更全面的信息收集来定位和解决问题。
摘要由CSDN通过智能技术生成

以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信号。这个很重要

(图来自微信团队)

当应用发生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 监控小结

通过监听系统SIGQUIT信号结合check 当前进程的 NOT_RESPONDING标识和主线程的卡顿状态,综合判定为该进程发生了ANR。

这只是我们知道发生了ANR,知道发生了ANR,进一步知道什么导致了ANR,采集ANR发生时的上下文环境信息,解决ANR更重要。

3 信息的采集监控

3.1 ANR问题定位难点

观察ANR信息采集的难点在于往往信息采集不准确、不全面,当ANR发生的当下采集的信息并不是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启动超时等。

结尾

我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。**

[外链图片转存中…(img-LUGAdXUg-1714320277589)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值