App性能如何量化
如何衡量一个APP性能好坏?直观感受就是:启动快、流畅、不闪退、耗电少等感官指标,反应到技术层面包装下就是:
- FPS(帧率)、
- 界面渲染速度、
- Crash率、
- 网络、
- CPU使用率、
- 电量损耗速度
等,一般挑其中几个关键指标作为APP质量的标尺。目前也有多种开源APM监控方案,但大部分偏向离线检测,对于线上监测而言显得太重,可能会适得其反,方案简单对比如下:
SDK |
现状与问题 |
是否推荐直接线上使用 |
---|---|---|
腾讯matrix |
功能全,但是重,而且运行测试期间经常Crash |
否 |
腾讯GT |
2018年之后没更新,关注度低,本身功能挺多,也挺重性价比还不如matrix |
否 |
网易Emmagee |
2018年之后没更新,几乎没有关注度,重 |
否 |
听云App |
适合监测网络跟启动,场景受限 |
否 |
还有其他多种APM检测工具,功能复杂多样,但其实很多指标并不是特别重要,实现越复杂,线上风险越大,因此,并不建议直接使用。而且,分析多家APP的实现原理,其核心思路基本相同,且门槛也并不是特别高,建议自研一套,在灵活性、安全性上更有保障,更容易做到轻量级。本文主旨就是围绕几个关键指标:FPS、内存(内存泄漏)、界面启动、流量等,实现轻量级的线上监测。
核心性能指标拆解
- 稳定性:Crash统计
Crash统计与聚合有比较通用的策略,比如Firebase、Bugly等,不在本文讨论范围
- 网络请求
每个APP的网络请求一般都存在统一的Hook点,门槛很低,且各家请求协议与SDK有别,很难实现统一的网络请求监测,其次,想要真正定位网络请求问题,可能牵扯整个请求的链路,更适合做一套网络全链路监控APM,也不在讨论范围。
- 冷启动时间及各个Activity页面启动时间 (存在统一方案)
- 页面FPS、卡顿、ANR (存在统一方案)
- 内存统计及内存泄露侦测 (存在统一方案)
- 流量消耗 (存在统一方案)
- 电量 (存在统一方案)
- CPU使用率(CPU):还没想好咋么用,7.0之后实现机制也变了,先不考虑
线上监测的重点就聚焦后面几个,下面逐个拆解如何实现。
启动耗时
直观上说界面启动就是:从点击一个图标到看到下一个界面首帧,如果这个过程耗时较长,用户会会感受到顿挫,影响体验。从场景上说,启动耗时间简单分两种:
- 冷启动耗时:在APP未启动的情况从,从点击桌面icon 到看到闪屏Activity的首帧(非默认背景)
- 界面启动耗:APP启动后,从上一个界面pause,到下一个界面首帧可见,
本文粒度较粗,主要聚焦Activity,这里有个比较核心的时机:Activity首帧可见点,这个点究竟在什么时候?经分析测试发现,不同版本表现不一,在Android 10 之前这个点与onWindowFocusChanged回调点基本吻合,在Android 10 之后,系统做了优化,将首帧可见的时机提前到onWindowFocusChanged之前,可以简单看做onResume(或者onAttachedToWindow)之后,对于一开始点击icon的点,可以约等于APP进程启动的点,拿到了上面两个时间点,就可以得到冷启动耗时。
APP进程启动的点可以通过加载一个空的ContentProvider来记录,因为ContentProvider的加载时机比较靠前,早于Application的onCreate之前,相对更准确一点,很多SDK的初始也采用这种方式,实现如下:
public class LauncherHelpProvider extends ContentProvider { // 用来记录启动时间 public static long sStartUpTimeStamp = SystemClock.uptimeMillis(); ... }
这样就得到了冷启动的开始时间,如何得到第一个Activity界面可见的时间呢?比较简单的做法是在SplashActivity中进行打点,对于Android 10 以前的,可以在onWindowFocusChanged中打点,在Android 10以后,可以在onResume之后进行打点。不过,做SDK需要减少对业务的入侵,可以借助Applicattion监听Activity Lifecycle无入侵获取这个时间点。对于Android 10之前系统, 可以利用ViewTreeObserve监听nWindowFocusChange回调,达到无入侵获取onWindowFocusChanged调用点,示意代码如下
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { .... @Override public void onActivityResumed(@NonNull final Activity activity) { super.onActivityResumed(activity); launcherFlag |= resumeFlag; <!--添加onWindowFocusChanged 监听--> activity.getWindow().getDecorView().getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() { <!--onWindowFocusChanged回调--> @Override public void onWindowFocusChanged(boolean b) { if (b && (launcherFlag ^ startFlag) == 0) { <!--判断是不是首个Activity--> final