欢迎你们前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~git
本文由 likunhuang发表于 云+社区专栏
实现背景shell
应用的使用流畅度,是衡量用户体验的重要标准之一。Android 因为机型配置和系统的不一样,项目复杂App场景丰富,代码多人参与迭代历史较久,代码可能会存在不少UI线程耗时的操做,实际测试时候也会偶尔发现某些业务场景发生卡顿的现象,用户也常常反馈和投诉App使用遇到卡顿。所以,咱们愈来愈关注和提高用户体验的流畅度问题。小程序
已有方案服务器
在这以前,咱们将反馈的常见卡顿场景,或测试过程当中常见的测试场景使用UI自动化来重复操做,用adb系统工具观察App的卡顿数据状况,试图重现场景来定位问题。微信
经常使用的方式是使用adb SurfaceFlinger服务和adb gfxinfo功能,在自动化操做app的过程当中,使用adb获取数据来监控app的流畅状况,发现出现出现卡顿的时间段,寻找出现卡顿的场景和操做。app
方式1:adb shell dumpsys SurfaceFlinger框架
使用‘adb shell dumpsys SurfaceFlinger’命令便可获取最近127帧的数据,经过按期执行adb命令,获取帧数来计算出帧率FPS。机器学习
优势:命令简单,获取方便,动态页面下数据直观显示页面的流畅度;函数
缺点:对于静态页面,没法感知它的卡顿状况。使用FPS在静态页面状况下,因为获取数据不变,计算结果为0,没法有效地衡量静态页面卡顿程度;工具
经过外部adb命令取得的数据信息衡量app页面卡顿状况的同时,app层面没法在运行时判断是否卡顿,也就没法记录下当时运行状态和现场信息。
方式2:adb shell dumpsys gfxinfo
使用‘adb shell dumpsys gfxinfo’命令便可获取最新128帧的绘制信息,详细包括每一帧绘制的Draw,Process,Execute三个过程的耗时,若是这三个时间总和超过16.6ms即认为是发生了卡顿。
优势:命令简单,获取方便,不只能够计算帧率,还能够观察卡顿时每一帧的瓶颈处于哪一个维度(onDraw,onProcess,onExecute);
缺点:同方式1拥有同样的缺点,没法衡量静态页面下的卡顿程度;app层面依然没法在发生卡顿时获取运行状态和信息,致使跟进和重现困难。
已有的两种方案比较适合衡量回归卡顿问题的修复效果和判断某些特定场景下是否有卡顿状况,然而,这样的方式有几个明显的不足:
一、通常很难构造实际用户卡顿的环境来重现;
二、这种方式操做起来比较麻烦,需编写自动化用例,没法覆盖大量的可疑场景,测试重现耗时耗人力;
三、没法衡量静态页面的卡顿状况;
四、出现卡顿的时候app没法及时获取运行状态和信息,开发定位困难。
全新方案
基于这样的痛点,咱们但愿能使用一套有效的检测机制,可以覆盖各类可能出现的卡顿场景,一旦发生卡顿,能帮助咱们更方便地定位耗时卡顿发生的地方,记录下具体的信息和堆栈,直接从代码程度给到开发定位卡顿问题。咱们设想的Android卡顿监控系统须要达到几项基本功能:
一、如何有效地监控到App发生卡顿,同时在发生卡顿时正确记录app的状态,如堆栈信息,CPU占用,内存占用,IO使用状况等等;
二、统计到的卡顿信息上报到监控平台,须要处理分析分类上报内容,并经过平台Web直观简便地展现,供开发跟进处理。
如何从App层面监控卡顿?
咱们的思路是,通常主线程过多的UI绘制、大量的IO操做或是大量的计算操做占用CPU,致使App界面卡顿。只要咱们能在发生卡顿的时候,捕捉到主线程的堆栈信息和系统的资源使用信息,便可准确分析卡顿发生在什么函数,资源占用状况如何。那么问题就是如何有效检测Android主线程的卡顿发生,目前业界两种主流有效的app监控方式以下,在《Android卡顿监控方式实现》这篇文章中我将分别详细阐述这二者的特色和实现。
一、利用UI线程的Looper打印的日志匹配;
二、使用Choreographer.FrameCallback
方式3: 利用UI线程的Looper打印的日志匹配判断是否卡顿
Android主线程更新UI。若是界面1秒钟刷新少于60次,即FPS小于60,用户就会产生卡顿感受。简单来讲,Android使用消息机制进行UI更新,UI线程有个Looper,在其loop方法中会不断取出message,调用其绑定的Handler在UI线程执行。若是在handler的dispatchMesaage方法里有耗时操做,就会发生卡顿。
只要检测msg.target.dispatchMessage(msg) 的执行时间,就能检测到部分UI线程是否有耗时的操做,从而判断是否发生了卡顿,并打印UI线程的堆栈信息。
优势:用户使用app或者测试过程当中都能从app层面来监控卡顿状况,一旦出现卡顿能记录app状态和信息, 只要dispatchMesaage执行耗时过大都会记录下来,再也不有前面两种adb方式面临的问题与不足。
缺点:需另开子线程获取堆栈信息,会消耗少许系统资源。
方式4: 利用Choreographer.FrameCallback监控卡顿
咱们知道, Android系统每隔16ms发出VSYNC信号,来通知界面进行重绘、渲染,每一次同步的周期为16.6ms,表明一帧的刷新频率。SDK中包含了一个相关类,以及相关回调。理论上来讲两次回调的时间周期应该在16ms,若是超过了16ms咱们则认为发生了卡顿,利用两次回调间的时间周期来判断是否发生卡顿(这个方案是Android 4.1 API 16以上才支持)。
这个方案的原理主要是经过Choreographer类设置它的FrameCallback函数,当每一帧被渲染时会触发回调FrameCallback, FrameCallback回调void doFrame (long frameTimeNanos)函数。一次界面渲染会回调doFrame方法,若是两次doFrame之间的间隔大于16.6ms说明发生了卡顿。
优势:不只可用来从app层面来监控卡顿,同时能够实时计算帧率和掉帧数,实时监测App页面的帧率数据,一旦发现帧率太低,可自动保存现场堆栈信息。
缺点:需另开子线程获取堆栈信息,会消耗少许系统资源。
总结下上述四种方案的对比状况:
SurfaceFlinger | gfxinfo | Looper.loop | Choreographer.FrameCallback | |
---|---|---|---|---|
监控是否卡顿 | √ | √ | √ | √ |
支持静态页面卡顿检测 | × | × | √ | √ |
支持计算帧率 | √ | √ | × | √ |
支持获取App运行信息 | × | × | √ | √ |
实际项目使用中,咱们一开始两种监控方式都用上,上报的两种方式收集到的卡顿信息咱们分开处理,发现卡顿的监控效果基本至关。同一个卡顿发生时,两种监控方式都能记录下来。 因为Choreographer.FrameCallback的监控方式不只用来监控卡顿,也方便用来计算实时帧率,所以咱们如今只使用Choreographer.FrameCallback来监控app卡顿状况。
痛点1:如何保证捕获卡顿堆栈的准确性?
细心的同窗能够发现,咱们经过上述两种方案(Looper.loop和Choreographer.FrameCallback)能够判断是当前主线程是否发生了卡顿,进而在计算发现卡顿后的时刻dump下了主线程的堆栈信息。实际上,经过一个子线程,监控主线程的活动状况,计算发现超过阈值后dump下主线程的堆栈,那么生成的堆栈文件只是捕捉了一个时刻的现场快照。打个不太恰当的比方,至关于闭路电视监控只拍下了凶案发生后的惨状,而并无录下这个案件发生的过程,那么做为警察的你只看到告终局,依然很难判断案情和凶手。在实际的运用中,咱们也发现这种方式下获取到的堆栈状况,查看相关的代码和函数,常常已经不是发生卡顿的代码了。
如图所示,主线程在T1~T2时间段内发生卡顿,上述方案中获取卡顿堆栈的时机已是T2时刻。实际卡顿多是这段时间内某个函数的耗时过大致使卡顿,而不必定是T2时刻的问题,如此捕获的卡顿信息就没法如实反应卡顿的现场。
咱们看看在这以前微信iOS主线程卡顿监控系统是如何实现的捕获堆栈。微信iOS的方案是起检测线程每1秒检查一次,若是检测到主线程卡顿,就将全部线程的函数调用堆栈dump到内存中。本质上,微信iOS方案的计时起点是固定的,检查次数也是固定的。若是任务1执行花费了较长的时间致使卡顿,但因为监控线程是隔1秒扫一次的,可能到了任务N才发现并dump下来堆栈,并不能抓到关键任务1的堆栈。这样的状况的确是存在的,只不过现上监控量大走人海战术,经过几率分布抓到卡顿点,但依然不是最佳的捕获方案。
所以,摆在咱们面前的是如何更加精准地获取卡顿堆栈。为了卡顿堆栈的准确度,咱们想要能获取一段时间内的堆栈,而不是一个点的堆栈,以下图:
咱们采用高频采集的方案来获取一段卡顿时间内的多个堆栈,而再也不是只有一个点的堆栈。这样的方案的优势是保证了监控的完备性,整个卡顿过程的堆栈都得以采样、收集和落地。
具体作法是在子线程监控的过程当中,每一轮log输出或是每一帧开始启动monitor时,咱们便已经开启了高频采样收集主线程堆栈的工做了。当下一轮log或者下一帧结束monitor时,咱们判断是否发生卡顿(计算耗时是否超过阈值),来决定是否将内存中的这段堆栈集合落地到文件存储。也就是说,每一次卡顿的发生,咱们记录了整个卡顿过程的多个高频采样堆栈。由此精确地记录下整个凶案发生的详细过程,供上报后分析处理(后文会阐述如何从一次卡顿中多个堆栈信息中提取出关键堆栈)。
采样频率与性能消耗
目前咱们的策略是判断一个卡顿是否发生的耗时阈值是80ms(5*16.6ms),当一个卡顿达80ms的耗时,采集1~2个堆栈基本能够定位到耗时的堆栈。所以采样堆栈的频率咱们设为52ms(经验值)。
固然,高频采集堆栈的方案,必然会致使app性能上带来的影响。为此,为了评估对App的性能影响,在上述默认设置的状况下,咱们作一个简单的测试实验观察。实验方法:ViVoX9 上运行微信读书App,使用卡顿监控与高频采样,和不使用卡顿监控的状况下,保持两次的操做动做相同,分析性能差别,数据以下:
关闭监控 | 打开监控 | 对比状况(上涨) | ||
---|---|---|---|---|
CPU | 1.07% | 1.15% | 0.08% | |
Memory | Native Heap | 38794 | 38894 | 100 kB |
Dalvik Heap | 25889 | 26984 | 1095 kB | |
Dalvik Other | 2983 | 3099 | 116 kB | |
.so mmap | 38644 | 38744 | 100 kB |
没有线程快照 | 加上线程快照 | |||
---|---|---|---|---|
性能指标 | 2.4.5.368.91225 | 2.4.8.376.91678 | 上涨 | |
CPU | CPU | 63 | 64 | 0.97% |
流量KB | Flow | 28624 | 28516 | |
内存KB | NativeHeap | 59438 | 60183 | 1.25% |
DalvikHeap | 7066 | 7109 | 0.61% | |
DalvikOther | 6965 | 6992 | 0.40% | |
Sommap | 22206 | 22164 | ||
日志大小KB | file size | 294893 | 1561891 | 430% |
压缩包大小KB | zip size | 15 | 46 | 206% |
从实验结果可知,高频采样对性能消耗很小,能够不影响用户体验。
监控使用Choreographer.FrameCallback, 采样频率设52ms),最终结果是性能消耗带来的影响很小,可忽略:
1)监控代码自己对主线程有必定的耗时,但影响很小,约0.1ms/S;
2)卡顿监控开启后,增长0.1%的CPU使用;
3)卡顿监控开启后,增长Davilk Heap内存约1MB;
4)对于流量,文件可按天写入,压缩文件最大约100KB,一天上传一次
痛点2:海量卡顿堆栈后该如何处理?
卡顿堆栈上报到平台后,须要对上报的文件进行分析,提取和聚类过程,最终展现到卡顿平台。前面咱们提到,每一次卡顿发生时,会高频采样到多个堆栈信息描述着这一个卡顿。作个最小的估算,天天上报收集2000个用户卡顿文件,每一个卡顿文件dump下了用户遇到的10个卡顿,每一个卡顿高频收集到30个堆栈,这就已经产生20001030=60W个堆栈。按照这个量级发展,一个月可产生上千万的堆栈信息,每一个堆栈仍是几十行的函数调用关系。这么大量的信息对存储,分析,页面展现等均带来至关大的压力。很快就能撑爆存储层,平台没法展现这么大量的数据,开发更是没办法处理这些多的堆栈问题。于是,海量卡顿堆栈成为咱们另一个面对的难题。
在一个卡顿过程当中,通常卡顿发生在某个函数的调用上,在这多个堆栈列表中,咱们把每一个堆栈都作一次hash处理后进行排重分析,有很大的概率会是dump到同一个堆栈hash,以下图:
咱们对一个卡顿中多个堆栈进行统计,去重后找出最高重复次数的堆栈,发现堆栈C出现了3次,此次卡顿颇有可能就是卡在堆栈3反映的函数调用上。因为采样频率不低,所以出现卡顿后通常都有很多的卡顿,如此可找出重复次数最高的堆栈,做为重点分析卡顿问题,从而进行修复。
举个实际上报数据例子,能够由下图看到,一个卡顿如序号3,在T1~T2时间段共收集到62个堆栈,咱们发现大部分堆栈都是同样的,因而咱们把堆栈hash后尝试去重,发现排重后只有2个堆栈,而其中某个堆栈重复了59次,咱们能够重点关注和处理这个堆栈反映出的卡顿问题。
把一个卡顿抽离成一个关键的堆栈的思路,能够大大下降了数据量, 前面说起60W个堆栈就能够缩减为2W个堆栈(2000101=2W)。
按照这个方法,处理后的每一个卡顿只剩下一个堆栈,进而每一个卡顿都有惟一的标识(hash)。到此,咱们还能够对卡顿进行聚类操做,进一步排重和缩小数据量。分类前对每一个堆栈,根据业务的不一样设置好过滤关键字,提取出感兴趣的代码行,去除其余冗余的系统函数后进行归类。目前主要有两种方式的分类:
一、按堆栈最外层分类,这种分类方法把一样入口的函数致使的卡顿收拢到一块儿,开发修复对应入口的函数来解决卡顿,然而这种方式有必定的风险,可能一样入口但最终调用不一样的函数致使的卡顿则会被忽略;
二、按堆栈最内层分类,这种分类方法能收拢一样根源问题的卡顿,缺点就是可能忽略调用方可能有多个业务入口,会形成fix不全面。
固然,这两种方式的聚类,从必定程度上分类大量的卡顿,但不太好控制的是,究竟要取堆栈的多少层做为识别分类。层数越多,则聚类结果变多,分类更细,问题零碎;层数越少,则聚类结果变少,达不到分类的效果。这是一个权衡的过程,实际则按照必定的尝试效果后去划分层数,如微信iOS卡顿监控采用的策略是一级分类按最内层倒数2层分类,二级分类按最内层倒数4层。
对于咱们产品,目前咱们没有按层数最内或最外来划分,直接过滤出感兴趣的关键字的代码后直接分类。这样的分类效果下来数据量级在承受范围内,如以前的2W堆栈可聚类剩下大约2000个(视具体聚类结果)。同时,天天新上报的堆栈都跟历史数据对比聚合,只过滤出未重复的堆栈,更进一步地缩减上报堆栈的真正存储量。
卡顿监控系统的处理流程
用户上报
目前咱们的策略是:
一、经过后台配置下发,灰度0.2%的用户量进行卡顿监控和上报;
二、若是用户反馈有卡顿问题,也可实时捞取卡顿日志来分析;
三、天天灰度的用户一个机器上报一次,上报后删除文件不影响存储空间。
后台解析
一、主要负责处理上报的卡顿文件,过滤、去重、分类、反解堆栈、入库等流程;
二、自动回归修复好的卡顿问题,读取tapd 卡顿bug单的修复结果,更新平台展现,计算修复好的卡顿问题,后续版本是否从新出现(修复不完全)
平台展现
上报处理后的卡顿展现平台
http://test.itil.rdgz.org/wel...
主要展现卡顿处理后的数据:
一、以版本为维度展现卡顿问题列表,按照卡顿上报重复的次数降序列出;
二、归类后展现每一个卡顿的关键耗时代码,也可查看所有堆栈内容;
三、支持操做卡顿记录,如搜索卡顿,提tapd单,标注已解决等;
四、展现每一个版本的卡顿问题修复数据状况,版本分布,监控修复后是否重现等。
自动提单
实际使用中,为了加强跟进效果,咱们设立一些规则,好比卡顿重复上报超过100次,卡顿耗时达到1000ms等,自动提tapd bug单给开发处理,系统也会自动更新卡顿问题的修复状况和数据,开发只需按期review tapd bug单处理修复卡顿问题便可,整个卡顿系统从监控,上报,分析,聚类,展现,提单到回归,整个流程自动化实现,再也不须要人工介入。
实际应用效果
一、接入产品:微信读书,企业微信,QQ邮箱
二、应用场景:现网用户的监控,发布前测试的监控,天天自动化运行的监控
三、发现问题:三个多月时间,归类后的卡顿过万,提bug单约500,开发已解决超过200个卡顿问题
卡顿监控的组件化
考虑到Android卡顿监控的通用性,除了应用于Android WeRead中,咱们也推广到广研的其余产品中,如企业微信,QQ邮箱。所以,在开发GG的努力下,推出了卡顿监控库http://git.code.oa.com/moai/m... ,其余Android产品可快速接入卡顿监控的SDK来监控app卡顿状况。
目前monitor卡顿监控库主要有监控主线程卡顿状况,获取平均帧率使用状况,高频采样和获取卡顿信息等基本功能。这里要注意几点:
一、采样堆栈信息的频率和卡顿耗时的阈值都可在SDK中设置;
二、SDK默认判断一个卡顿是否发生的耗时阈值是80ms(5*16.6ms)
三、采样堆栈的频率是52ms(约3帧+,尽可能错开系统帧率的节奏,堆栈可尽可能落到绘制帧过程当中)
四、启动监控后,卡顿日志就会不断经过内部的writer输出,实现MonitorLogWriter.setDelegate才能获取这些日志,具体的日志落地和上报策略由于各个App不一样因此没有集成到SDK中
五、monitor start后一直监控主线程, 包括切换到后台时也会,直到主动stop或者app被kill。因此在切后台时要主动stop monitor,切前台时要从新start
1.组件引入方式
2.主线程卡顿监控的使用方式
1)启动监控
2)中止监控
3)获取卡顿信息
app中加入监控卡顿SDK后,会实时输出卡顿的时间点和堆栈信息,咱们将这些信息写入日志文件落地,同时天天固定场景上报到服务器,如天天上报一次,用户打开app后进行上报等策略。收集不一样用户不一样手机不一样场景下的全部卡顿堆栈信息,可供分析,定位和优化问题。
特别致谢
此文最后特别感谢阳经理(ayangxu)、豪哥(veruszhong)、cginechen对Android卡顿监控组件化的鼎力支持,感谢姑姑(janetjiang)悉心指导与提议!但愿卡顿监控系统能愈来愈多地暴露卡顿问题,在你们的共同努力下不断提高App的流畅体验!
相关阅读
Javascript框架设计思路图
小程序优化36计
【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识
此文已由做者受权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在云加社区!