◆◆
引言
◆◆
在移动互联网时代,在满足用户基本需求的前提下,用户体验是一款产品的终极发展,良好的用户体验及服务可以增加用户粘性,加快产品推广,是树立产品品牌的重要因素。去年下半年以来,各大厂都相继开源或上线了各自的移动端性能监控解决方案,有360 Argus,腾讯Matrix,Google Firebase,听云APM,滴滴Boost,百度云APM等,可谓是百家争鸣,移动端性能监控解决方案是移动互联网时代的重要产物,日渐受到开发者的重视。
Shooter是一款致力于打造线上性能数据收集、控制台数据展示分析,线上问题诊断的移动端性能监控平台,用户可以实时了解应用的性能,追踪到线上真实用户的性能体验,从而提升开发者解决移动端性能问题的效率。
◆◆
监控维度及技术实现
◆◆
Shooter性能监控平台主要监控功能包含应用启动监控、卡顿监控、网络监控、webview监控,原生页面监控以及日志上报,安装包分析工具,内存分析工具等。本文将对Android端SDK的技术方案及实现效果进行简要介绍。
Shooter性能监控平台落地的一项目标就是实现集团内其他APP赋能,因此客户端SDK的设计原则就本着 业务无侵入、轻量级、功能线上可控、接入稳定、功能齐全 的多角度出发。
1.1应用启动监控
我们可能都遇到过以下的场景:点击App图标,先出现一个白屏或者黑屏页面,然后过一会才进入主界面。这种应用长时间启动的用户体验是极度糟糕的,Shooter的启动监控的目的就是监控线上用户启动应用的耗时以及定位到时间消耗在了哪些阶段。
Shooter启动监控采用了无侵入式打点方式,记录了应用启动各个阶段的耗时情况,这里将启动过程分为了三个阶段:
第一阶段 : Application attachBaseContext Start -> onCreate End (Applicaiton 启动阶段)
第二阶段 : Application onCreate End -> MainActivity(首页) onCreate Start (Application 跳转首页耗时)
第三阶段 : MainActivity(首页) onCreate Start -> onWindowFocusChanged End (首页加载时长)
启动耗时 = (MainActivity onWindowFocusChanged End) - (Application attachBaseContext Start)
另外,对启动时期的应用的主要生命周期的方法也做了方法耗时数据采集上报,主要包含Application的attachBaseContext,onCreate,以及首页的onCreate方法。
通过线上数据采集,可以对系统的版本进行聚合,统计到某个版本应用按照不同的版本的启动时间分布,从控制台上可以观测到启动时间的系统分布图如下:
主要的方法耗时按照系统的分布图:
此外,控制台还支持启动详情的查询,支持按照特定的机型,设备码,用户账号等维度进行单用户启动详情的查询,这里就不展开介绍了。
1.2卡顿监控
目前大型的Android项目都面临着同一个问题,业务复杂,版本迭代快,历史代码庞大,包含各种第三方库。在出现了卡顿的时候,我们很难定位到底是哪里出现了问题,即使定位到具体界面,动辄数千行的代码,各种复杂的业务逻辑夹杂着各种奇葩的函数名称,程序员看的头晕眼花,随他去吧,结果就是客户端越来越卡,恶性循环。
a. 主线程只有一个looper
Looper.java的源码可以看到,定义了一个静态变量sMainLooper,主线程无论有多少个Handler,但只有这一个looper存在,主线程中执行任何代码都会回到loop()函数中。
b. 将主线程的Printer替换
Looper类中的Printer是可以替换的,谢谢Google大牛们目前留了接口,不过仔细想想,即使没有留接口也可以使用反射替换掉。
替换接口:
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
c.卡顿条件(endTime-startTime > 卡顿阈值)
Printer在每一个message执行前和执行后成对的调用,就可以知道消息开始时间startTime,以及消息的结束时间endTime。如果endTime-startTime > 卡顿阈值,则认为该条消息执行过长,主线程卡顿。
d.采样
在执行主线程的同时,采样线程要对主线程的堆栈、cpu等信息进行周期采样。但是采样线程要先休眠一段时间,这样做的最主要原因就是为了不对主线程大部分的短消息进行打扰,抢夺cpu资源,造成性能降低。
采样示意图:
确认卡顿: 两个相邻的采样时间间隔,取出的主线程堆栈是完全一致的。这种情况就划分到确认卡顿范畴,因为可以明确的判断,该函数在两个时间间隔内还没有执行完。
疑似卡顿: 没有相邻间隔堆栈相同的情况,这种属于疑似卡顿。
客户端数据初步聚合: 两个相邻间隔的堆栈一致,只需要将count字段加1,这样可以减少内存、流量等性能消耗。服务端会根据这个count字段来分析本次执行花费了多少个时间间隔。
应用的的关键代码行: 客户端会过滤堆栈,找到带有关键业务包名的代码行,标记为关键行,这样做可以大大降低服务端的数据聚合压力。
控制台展示效果如下图:
通过卡顿列表排行可以监控到应用的主要耗时方法,方便业务开发人员进行优化。
1.3 网络监控
移动端的网络性能永远是大家比较关注的一个话题,网络加载慢的损失到底有多大?Amazon网站加载慢1秒,一年少赚16亿美元。为了优化移动端的性能,我们使用过很多优化策略,比如HttpDns,弱网降级,拨测,HTTP/2.0等等,然而对于线上网络性能监控这块我们之前只做了异常埋点上报的监控机制,对于线上网络性能的监控还是存在不足。于是在Shooter性能监控中,我们借鉴了听云APM及Google Firebase的监控经验,总结出一套无侵入式移动端网络监控的解决方案。
采用ASM字节码编辑技术Hook到了HttpClient,HttpUrlConnection,Okhttp,Socket,SSLSocket等原生或第三方开源网络组件的网络IO接口。
网络性能维度的采集将网络请求划分为几个阶段进行统计上报,更好的帮助开发及运维同事定位网络的某个阶段问题,具有更好的准确性。
整个网络监控包含了性能数据及异常数据两类数据,满足基本的异常告警监控的同时,具备线上性能数据分析聚合的功能。
a. AOP技术介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。现有的AOP技术主要分为运行时和编译时两大类,其中编译期实现AOP的框架主要包含APT,AspectJ,Javassist,ASM;运行时AOP框架主要有阿里的Dexposed和XPosed框架。
b. ASM框架实现AOP方式
ASM框架作用是在编译器间修改class文件,可以让我们直接修改编译后的class二进制代码,首先我们得知道什么时候编译完成,并且我们要赶在class文件被转化为dex文件之前去修改。
c. 选择ASM框架
最直接的改造 Java 类的方法莫过于直接改写 class 文件。Java 规范详细说明了 class 文件的格式,直接编辑字节码确实可以改变 Java 类的行为。直到今天,还有一些 Java 高手们使用最原始的工具,如 UltraEdit 这样的编辑器对 class 文件动手术。
IBM持续更新开发框架,具备友好的接口设计及更完善的功能
无侵入式,减少接入成本
轻量级,相对AspectJ能够达到同样的效果
网络监控的数据包含了两大类:原生网络监控和图片监控。
原生网络监控: 原生网络监控属于常规监控,主要监控了接口的性能及异常数据,服务端会根据客户端上报的各个参数维度来进行数据聚合展示。
图片监控: 图片监控是在网络监控的基础上新增了CDN节点数据上报,通过CDN节点维度聚合可以得到CDN的性能指标,同时对于图片这个内存消耗大户,控制台也会对客户端下发图片的大小进行监控,能够监控到不合理分辨率的活动图片下发问题。
a. 地域及运营商维度展示网络接口的响应时间及异常率分布
b. 响应时间分解趋势图
c. 接口异常变化曲线图
d. 图片请求监控列表
1.4原生页面性能监控
伴随着App体量的不断增大以及市面上各类手机的千差万别,高端手机配置在一定程度上提升了流畅度,然而线上仍然会收到卡顿一类的反馈,因此对于页面性能的优化仍然是一项长期的工作。
原生页面监控由于是线上性能监控,首要保证的是监控的成本问题,不能给宿主应用带来额外的负担,所以Shooter对于页面性能数据采用较为轻量级的数据采集策略。主要通过运行期间,采集设备的帧率,CPU, 内存,线程数,流量这几种数据,来反应当前页面运行时性能情况。
帧率采集方案是基于Android原生提供的Choreographer.FrameCallback回调实现,这种检测流畅度的方法起源于FaceBook的一次关于UI流畅度的技术分享“The Road to 60FPS“。
帧率采集过程需要注意两点:
依赖于Activity的生命周期函数,在页面onStart函数开启采集页面的刷新帧率,在onPause结束采集;
帧率采集时机问题,我们通过Activity获取页面是否正在绘制的回调,getWindow().getDecorView().getViewTreeObserver().addOnDrawListener,通过该回调可以在页面发起绘制事件时启动帧率数据收集。
2.CPU,内存,线程数据采集
CPU,内存使用情况及线程数量能够反映出原生页面的资源消耗情况,Shooter采用了等时间间隔采样的方式实现样本采集。
CPU监控能够反馈当前页面的CPU使用情况,可以侧面反馈出电量消耗问题;
内存使用能够标明当前页面消耗内存情况及JVM中可用内存大小,从而计算出内存触顶率及页面内存抖动情况;
线程数可以监控到原生页面中是否线程数超标,监控线程数超标OOM问题。
a. 页面平均帧率TOP10分布
b. 某页面平均线程数变化曲线
1.5 webview性能监控
Shooter支持Webview与腾讯X5内核性能监控,监控的核心指标为window.perfromance.timing参数,该参数记录了整个webview加载过程中的阶段性耗时,详见下图:
以下是几项关键性指标的计算方式(对照上图)
totalLoadingTime(页面加载总时间) = loadEventEnd - navigationStart;
networkRequestTime(网络请求耗时) = responseEnd - navigationStart;
domTime(DOM加载时间)= loadEventEnd - responseEnd;
waitingTime(白屏时间即用户等待时间)= domLoading - navigationStart;
a. webview延时地域分布及延时区间分布图
b. 活动页面耗时TOP10变化曲线
1.6 其它
除以上5个线上功能外,Shooter还提供了日志,APK包分析工具,开发阶段内存分析工具等多种实用性的功能模块,目标是对生产及开发环境进行性能监控,尽早地将性能问题暴露在开发及灰度阶段。
◆◆
赋能历程
◆◆
2.1赋能数量及遇到的问题
从2019年上半年开始赋能以来,Shooter目前已经累计接入产品数56款,累计接入了App数量108个。
接入过程中遇到的主要问题有:
包冲突问题: ASM字节码编辑技术与市面上某些使用了不同AOP框架的第三方jar包编译冲突,解决第三方包的冲突通过增加白名单机制,在编译器对第三方的不兼容包进行过滤,保证编译器的正常运行。
编译时间变长问题: ASM字节码编辑技术需要对工程引用的class文件进行扫描,如果工程中涉及到的class文件数量较多,会拖慢编译速率,我们通过控制ASM字节码编辑的范围,集中对于我们的基础业务层进行AOP,可以有效减少编译耗时。
包增量问题: 接入应用时,应用管理员都会询问引入包大小问题,我们对SDK进行了原生化轻量级改造,目前新增包大小150k左右,满足了应用新增SDK大小限制的要求。
2.2典型赋能案例
从对外赋能以来,我们收到了多方的反馈以及改进需求,在满足正常的性能监控功能以外,我们对一些应用存在的问题给出了解决建议。
例如某款商家客户端应用,线上用户反馈页面卡顿问题严重,通过Shooter页面监控功能监控到该页面的平均帧率为31.2fps,然而页面的日平均使用时长为180s,排行所有页面TOP1,体现出该页面是用户热门使用页面,然而帧率远低于60fps,因此引发用户反馈界面卡顿问题较多。
影响页面帧率的原因为在UI线程中存在耗时的操作,通过Shooter卡顿监控,我们排查到导致该页面帧率过低的原因为UI线程中数据库IO操作耗时,需要将这些IO操作移到子线程中处理。
修复之后,热门活动页面帧率提升到了50.85fps,提升63%,用户不再投诉界面卡顿问题。
◆◆
最后
◆◆
Shooter性能监控平台的目标是建立统一的应用性能接入框架,通过各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用,我们仍然在努力完善中。