流畅性三板斧番外之:各大厂与卡顿和ANR的战斗记录

3,666

流畅性三板斧番外之:各大厂与卡顿和ANR的战斗记录

前言

前段时间写了流畅性三板斧的系列文章,比较系统性但不是很精细的梳理了下在Android端如何做帧率监控、主线程耗时监控、以及ANR 的监控。在写作过程中也参考了大厂在这方面对外分享的技术文章,本文就此梳理下这些文章,一是为了更好的吸收消化这些方案从中汲取营养,另一方面也算是致敬这些帮助我们成长的无私分享。

1 今日头条团队对ANR问题对外分享

首先是,2021年3月份,今日头条团队分享的ANR 优化实践系列文章:

1.1 认识ANR

1.1.1 系统如何处理ANR

设计原理和影响因素篇,主要对以下关键问题展开

  • ANR触发的条件以及根本原因

  • 发生ANR之后,系统处理ANR的流程。

  • 应用层如何判定ANR:对ANR的感知,通过监听SIGQUIT信号。

  • 应用层面如何获取有用的信息帮助解决ANR问题。

1.1.2 ANR问题分类

把ANR 产生的影响因素清晰的分为了四个大的类别,基本覆盖了ANR问题产生的原因。

  • 应用内主线程存在耗时任务;

  • 应用主线程处理大量任务;

  • 系统内部其他进程或者资源负载过高;

  • 应用自身其他线程或者负载过高。

系列文章其实就是围绕着这些问题的监控、分析、解决展开的。

2 工具建设-消息调度监控

通过博文得知,今日头条团队,ANR 监控的工具叫 Raster,其最主要的功能就是采集主线程调度。

ANR 产生的原因很多情况下可能是历史耗时问题的累计。因而单纯采集发生ANR 时那一刻的堆栈就会有【堆栈漂移的问题】,也就是采集到的堆栈不是诱发ANR 产生的真正原因。现在ANR 监控的很多框架也是围绕这一问题展开的。

应对方式 对主线程消息调度进行监控记录,包括历史消息,正在执行的消息和将要执行的消息。同时对四大组件执行的消息进行单独的监控,这对我们分析哪个组件产生的ANR 是很重要的参考依据。

这一方案是在网上公开的我能接触到的最早提出的,后面我们会看到很多团队对ANR 问题的监控都是这一方案的变种,或者大同小异。

这一方案的细化,主要包括以下几个方面

2.1 消息聚合

消息统计聚合策略:主线程消息会很多,记录过去5-10秒的消息本身是一个比较重的动作,采用一定的聚合策略是很有必要的。

  • 合并耗时段的多个消息:耗时较小的消息,对ANR 问题的产生影响不大,只记录总耗时和和消息的个数。

  • 独立组件消息:ActivtyThread 组件调度通过Handler我们可以采集到这些调度,单独记录。

  • 独立耗时消息:对超过阈值(比如300ms)的消息单独记录,耗时消息是我们重点关注的对象。

  • 记录IDEL 状态,主线程无消息的时候,会进入IDEL 状态,堵塞在nativePoll 处。这一状态单独统计。

  • 发生ANR 时,采集当前正在进行的任务。

  • ANR 发生时,采集pending消息,根据pending消息中的组件调度消息能让们知道哪个组件触发 ANR,同时根据等待的时常侧面反映系统负载的能力。

2.2 每条消息记录的关键信息

  • 消息调度的时长:cputime 和 walltime,记录两个时间能更好的判断一次消息耗时是执行耗时还是等待或者抢占较多。

  • 消息调度的类型:组件调度,耗时调度,多消息聚合调度

  • 消息堆栈:对消息内具体执行信息,采集其堆栈。 image

2.3 主线程线程堆栈的采样

对耗时的消息,进行采样,采取的策略是超时采样。具体来说,介于大部分的耗时小消息不需要进行堆栈采样,为了避免频繁设置和取消超时任务(也就是采样任务),头条在此处做了一个优化,每次消息开始是并不是重新设置采集超时任务,而是修改目标时间。 image

3 问题分析&解决

  • 结合结合采集的物料,这些物料包括ANR Info信息,采集的主线程调度信息等给出了分析ANR问题的一般思路。通过trace信息读取是否有明显的耗时调用,通过ANR Info分析系统负载,应用内负载,再结合Raster采集的线程调用,把ANR问题最终归因上上述四大因素上。然后具体分析解决。这里不做展开了,今日头条团队案例分析还是相当精彩的不容错过。

  • 对Barrier泄漏的监控和由SP引发的案例进行详细的分析。

2 微信团队

2021年7月份,微信团队发布在公众号上的两篇文章关于卡顿监控和ANR监控的文章

2.1 首先第一篇《微信Android客户端的卡顿监控方案》

  • 该篇是我接触到的最早也可能是唯一一篇指出使用WatchDog方案漏报卡顿和ANR的问题的文章。文章里甚至还指出了漏报概率公式,以及如何不漏报的方案,如下图

图片

思考这样一个问题,如果我们选择间隔4.5秒去check发送到主线程Looper到Message是否被消费。现在有一个5秒到卡顿,从2秒开始,结束在第7秒耗时5秒,我们间隔5秒能监控到的概率是多少?

答案是只有11%,惊讶不。原因就是在在0-4.5和4.5到9这两个周期内那些空闲的时间,消息都有可能被消费掉!这种情况我们就监控不到了。想想,你再想想,真的是佩服腾讯工程师严谨的工程作风。

  • 另外,该篇全面的的提供了监控主线程卡顿的方案,包括

  • 主线程Message处理的耗时,使用的是设置Looper Printer的方案,Android10以后我们可以通过设置Observer监控,有兴趣的可以找相关资料。

  • IdleHandler耗时的监控,通过hook MessageQueue mIdleHandlers实现。

  • TouchEvent的耗时监控,通过PLT Hook。这一块要注意,TouchEvent的事件分发是不通过Handler机制,而是直接native层调到java层分发给View的,如果这个知识点不清楚的小伙伴注意了,又有新知识学习了。

2.2 第二篇文章

很是精彩,ANR如何产生,系统源码实现,找监控方案直接一套组合拳,可见功底深厚大佬就是大佬。

其中,通过监听SIGQUIT信号并过滤误报最终监控ANR的方案,也是当前主流的正规方案,为什么是加一个正规的修饰词呢,想想ANRWatchDog那种多不正经就知道这个有多正规。

这里面,几个重要的点我列下

  • 监听SIGQUIT信号,需要注意重新发送出去

  • 梳理系统ANR的过程中,发现的ANR Trace 通过hook手段可以拿到

这就是微信团队出的关于Android卡顿和ANR的两篇经典文章,建议大家自信研读,卷起来!

3 钉钉技术团队

2022年12月份,钉钉团队发表的ANR 问题的解决方案系列文章。

3.1 ANR 判定

作为ANR问题不可避免的两个问题

  • 文章也是分析了系统ANR 的流程

  • 判定ANR的方案也是通过监听SIGQUIT 并过滤误报的方式。

3.2 工具建设

监控ANR 的方案思路与今日头条基本一致,采集主线程的调度信息做记录。其中也有一些不同,把主线程调度的分类,以下五类

image

  • 主线程的消息调度

  • IdelHandler 调度

  • IDLE 状态

  • 触摸事件

  • 传感器事件

可见,其在主线程任务调度的方面监控的更加具体,今日头条的Raster工具从博文看只是对主线程的消息调度进行了监控,我们在结合微信团队的卡顿监控方案,其实可以更全面的对主线程任务进行调度,这一点很知道借鉴学习。

此外,博文里还提出有些手机厂商在应用进程会进入冻结状态,APP 回到前台后才继续执行,冻结的过程里会导致任务耗时过长,需要单独记录,不过这一点今日头条的方案,里能够通过CPUTime 和WallTime 识别出来。

其他方面,ANRCanery 采集也会采集过去当下和等待的任务调度,也对会采集的消息进行聚合处理。堆栈采集上,也是采用了时间对齐方案对堆栈进行采样。

3.3 实践分享

  • 结合具体场景分享了一些死锁场景,Barrier消息泄露场景等。

  • 还分享了工具建设的心得,很有启发性。

4 其他团队

4.1 阿里其他技术团队

4.1.2 闲鱼团队

2021年6月份, 《关于闲鱼的ANR治理,我有几条心得》developer.aliyun.com/article/786…

文章短小精悍

  • 【监控】监听SIGQUIT信号

  • 【排查】搭建了ANR Info收集和主线程Message监控。

  • 【优化】给出了三个在借助排查工具的优化案例。

3.3 手淘技术团队

《手淘 Android 帧率采集与监控详解》:juejin.cn/post/705151…

手淘团队2022年1月份,提出了Android [滑动帧率]思路并给出了比较详细的监控方案。

4.2 shopee 团队

2022年8月份 LooperMonitor

《Android 卡顿与 ANR 的分析实践》:juejin.cn/post/713600…

  • ANR的判定: 也是通过SIGQUIT +过滤的方式。

  • ANR的监控 :主线程的监控思路与今日头条和字节跳动一致,记录主线程的调度信息,也是有聚合的策略。

值得注意的点:

  • 文章里重点提出了对消息的记录使用了池化技术,减少内存重复分配问题。

  • 采集堆栈的方案上,这里提到了利用Kotlin协程非堵塞式挂起特性实现了高效的采集。该方案并没有具体展开如何实现的。同时采集阈值线性增加。

  • 对Looper Message的监控,在 Android api ≥ 28 时,Looper 中新增了一个 Observer 的接口,采用元反射的方式,减少了通过Looper Printer 拼接Message带来的性能损耗。

4.3 毒物团队

2021年9月份得物团队发表了ANR监控文章

《得物技术 | 得物App ANR监控平台设计》juejin.cn/post/700929…

  • ANR判定使用了爱奇艺的XCarash因而能采集到tomsbtone信息

  • 主线程消息回溯采集思路与其他家类似。

  • 毒物搭建了ANR分析平台,数据可视化,有一定的参考意义。

5 多说一些抓栈问题。

java层面直接通过thread.getStackTrace()获取堆栈信息,是有一定损耗的,对此我们看到大家常规的做法是控制采集的频率,shoppe团队提到的由协程的非堵塞式挂起实现高效的线程堆栈采集,对此我是持怀疑态度

业内确实对高效采集堆栈方向有探索,给出了高效的抓栈方案,妥妥的黑科技看到了比较优秀的两篇供大家参考。

总结

纵观各厂在卡顿和ANR 方面做的探索和方案,我们可以看出,思路上都有重合,在细节方面做了很多针对自身业务和实际情况做的针对性的优化和个性化的开发。总的来说逃不出以下几个步骤

image.png

  • ANR的感知上:业界主流的方案就是监听SIGQUIT 信号+误报过滤。腾讯技术团队,提到的OV 厂商对ANR的处理并不是常规的处理,而是做闪退处理,所以要以check主线程正在处理的 Message,延误时间作为辅助防止漏报。

  • 信息采集上:由于发生ANR 时,主线程正在处理的任务可能并不是发生ANR的真正原因,因而需要对主线程任务过往调度进行统计记录,同时对消息进行监控,监控的类型,其实微信团队卡顿方案监控给出了全面的方案。需要对消息进行聚合处理。任务的执行栈抓取,控制频次,也有团队给出了高效抓栈方案。另外,出了主线程调度消息外,系统负载情况信息对我们分析ANR 很重要,因为ANR 发生的原因可不仅仅是主线程执行了耗时任务。

  • 问题解决:全面准确的信息采集的基础上,会让找出问题解决问题工作变得更简单,各团队也给出了思路和案例,参考性很高。

上述ANR 的监控方案,截止到现在都没有开源,卡顿监控在Matrix上开源了。talk is cheap ,因而我在正在尝试取各家之所长,编写开源一个ANR监控工具。目前正在草稿阶段,把基本框架做完之后会尽快开源出来,希望大家参与进来共同建设。关注作者 别错过更新进度。

ANR问题从监控到采集都使用了各种hook手段黑科技,ANR作为Android一种保护机制, 只提出问题,不解决问题的行为都是xxx,目前还未看到官方对此有何动作。

好了,写文不易,不足之处批评指正,希望各位点赞 评论 鼓励 感谢!!!

另附系列文章:

Android 流畅性三板斧之帧率监控juejin.cn/post/721780…

Android 流畅性三板斧之卡顿监控juejin.cn/post/721877…

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

本文正在参加「金石计划」