Android 性能测试 - 流畅度

1.前言

在手机App竞争越来越激烈的今天,Android App的各项性能特别是流畅度不如IOS,安卓基于java虚拟机运行,触控响应的延迟和卡顿比IOS系统严重得多。一些下拉上滑、双指缩放快速打字等操作,安卓的流畅度都表现比较糟糕,但是,对于App使用过程是否流畅,一直没有一个可靠的指标将用户的客观感受和数据一一对应。虽然之前有FPS(每秒帧数)作为游戏或视频类App的性能指标,但对于那些界面更新不多的App来说,仍不是一个合适的衡量数据。以下会根据实际app性能测试案例,展开进行app性能评测之流畅度进行原理分析和评测总结。

1.1流畅度相关概念了解

刷新率vs帧率
刷新率:每秒屏幕刷新次数,手机屏幕的刷新率是60HZ
帧率:GPU在一秒内绘制的帧数

撕裂vs掉帧
撕裂
因为屏幕的刷新过程是自上而下、自左向右的,如果帧率>刷新率,当屏幕还没有刷新n-1帧的数据时,就开始生成第n帧的数据了,从上到下,覆盖第n-1帧。如果此时刷新屏幕,就会出现图像的上半部分是第n帧的,下半部分是第n帧的现象。CPU/GPU一直都在渲染。
丢帧
Android系统每隔16ms发出VSYNC信号,触发GPU对UI进行渲染,如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候由于还没有准备好,就无法进行更新任何内容,那么用户在32ms内看到的会是同一帧画面(卡顿现象),即丢帧现象。

1.2 理解FPS原理

FPS即Frames per second>>点击这篇文章,解释的非常清楚。
用过flash的人应该知道动画片其实是由一张张画出来的图片连贯执行产生的效果,当一张张独立的图片切换速度足够快的时候,会欺骗我们的眼睛,以为这是连续的动作。反之类推,当你的图片切换不够快的时候,就会被人眼看穿,反馈给用户的就是所谓的卡顿现象
想要让大脑觉得动作是连续的,至少是每秒10-12帧的速度,而想达到流畅的效果,至少需要每秒24帧。这也是为什么电影片源通常都是24帧的原因,好奇的同学点击
>>知乎
看看大神的解答。不过60帧每秒的流畅度是最佳的,我们的目标就是让程序的流畅度能接近60帧每秒,当然超过60帧速的话大部分人还是会受不了的。

系统获取FPS的原理是:1s 内 SurfaceFLinger 提交到屏幕的帧数。
计算公式:1000ms / 60 frames ≈ 16.67 ms/frames

原来的测试产品的流畅度,FPS是一个重要的指标,但是用了一段时间后,人们就发现了这样两个问题

1)为什么有时候FPS很低,但是我们却不觉得App卡顿?
2)App停止操作之后,FPS还是一直在变化,这样的情况是否会影响FPS的准确度?

后来测试人员分析了系统获取FPS的原理后,找到了那两个问题的答案:

1)有时候FPS很低,我们却感觉不到卡顿,因为本来就用不到那么高的FPS,比如画一个动画只画了0.5秒就画完了,那么FPS最高也只有30帧/秒(标准是60帧/每秒),但这并不代表它是卡顿的,用0.5秒动画就画完了,不能为了凑够60帧/秒,在做个1s的动画吧。而如果屏幕根本没有绘制需求,即屏幕显示的画面是静止的,那FPS就为0。
2)App停止操作后FPS还一直变化,是因为屏幕每一帧的合成都是针对手机里的所有进程,那么即使你的App停止了绘制,手机里其他进程可能还在绘制,比如通知栏的各种消息,这会导致FPS继续变化。

2. 如何计算流畅度

从上一节的原理分析来看,对流畅度的评价没有一个既定的测量标准。不同的应用有相对适应的计算方式,总结如下:

系统合成帧率(FPS):数据形式最为直观(FPS 是最早的显示性能指标,而且在多个平台中都有着类似的定义),且对系统平台的要求最低(API level 1),游戏、视频等连续绘制的应用可以考虑选用,但不适用于绝大多数非连续绘制的应用;
流畅度(SM):数据形式与 FPS 类似,可以很好的弥补 FPS 无法准确刻画非连续绘制的应用显示性能的缺陷;
应用跳帧次数、幅度(Aggregate frame stats):除了对系统平台有较高的要求以外,其采集方式最为简单(系统自带功能);
丢帧(Skipped frames):与 Aggregate frame stats 类似, 信息量相对较少,但可适用范围更广

2.1 FPS获取

APP需要尽可能的超过24帧/秒,接近60帧/秒的速度,并且在使用的过程中保持这个速率,因此这意味着我们的程序需要在16.67ms内处理一幅画面内的所有事,并保持住这个状态。
计算公式:1000ms / 60 frames ≈ 16.67 ms/frames

2.1.1 GPU呈现模式分析

操作方法:通过 [设置]->[开发者选项]->[GPU呈现模式分析] ->[在屏幕上显示为条形图] 进行直观的取样,截图如下:


GPU条形图

解读:屏幕下方的柱形图会持续刷新,最上方会有一根绿色的线,代表的是16ms的阈值,超过这个界限表示当前帧绘制的时间出现了延迟,及卡顿现象,后面会详细介绍原因。横坐标表示时间的持续,每一根柱形图表示当前帧的绘制时间。因此我们在使用的过程中,下面的柱形图会一直的刷新,单位是ms。各位看官是否有注意到每一帧的柱形图颜色不一样呢(注:不同手机的颜色不一样,仅限安卓4.0以上版本参考)
优点:可直接在手机上操作,方便直观
缺点:这个方式获取到的渲染时间只是UI主线程上的绘制行为。“GPU呈现模式分析”的数据只能说明个现象,比如上面提到的数据,能说明在实际运行中会有短暂的长时间绘制问题。但造成问题的具体原因并没有说明。
而且“GPU呈现模式分析”显示的是最后128帧的数据,但丢帧也有可能是两帧之间存在长时间的操作而造成的。

GPU渲染卡顿

2.1.2 通过gfxinfo获取

操作:设备连接usb数据线,使用adb调试工具,随后对返回的数据进行适当处理便可以得到此时此刻app的fps。

adb shell dumpsys gfxinfo yourpackagename

解读:
Draw:是消耗在构建java显示列表DisplayList的时间。说白了就是执行每一个View的onDraw方法,创建或者更新每一个View的DisplayList对象的时间。
Process:表示是消耗在Android的2D渲染器执行显示列表的时间,view越多,要执行的绘图命令就越多,时间就越长
Execute:消耗在排列每个发送过来的帧的顺序的时间.或者说是CPU告诉GPU渲染一帧的时间,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复。所以这个时间,一般都很短。

Draw + Prepare+Process + Execute = 完整显示一帧 ,这个时间要小于16ms才能保存每秒60帧。

将数据复制到excel中,然后将数据生成“堆积柱形图”如下:


image
image

缺点: 这种方式是最普遍也是最常用的一种,但在使用上有明显的痛点,一是设备需要连接usb,二是必须是Android M 版本以上才支持,三是adb命令返回的数据并不是实时fps,需要经过处理才能得到,因此不能在测试app的过程中实时显示fps。

2.1.3 通过系统层级SurfaceFlinger获取

原理:在 Android 系统中,SurfaceFlinger 扮演了系统中所有 Surface 的管理者的角色,当应用程序所对应的 Surface 更新之后,绝大多数的 Surface 都将在 SurfaceFlinger 之中完成了合并的工作之后,最终才会在 Screen 上显示出来。
知道android绘制原理的人应该能明白,SurfaceFlinger就是负责绘制Android应用程序UI的服务,所以surfaceFlinger能反应出整体绘制情况,一般正常情况都是连续的,如果出现空档,一种是没有操作或者滑动到头,没东西需要绘制,这种属于正常,另一种就是有问题存在,有其他操作时间过长。
操作:设备连接usb数据线,使用adb调试工具,

adb shell dumpsys SurfaceFlinger packagename

2.2 Aggregate frame stats指标的计算方法

首先需要说明的是 Aggregate frame stats 不是一个指标,而是一系列指标集合。我们来看一个具体的 Aggregate frame stats 的例子:

Stats since: 752958278148ns
Total frames rendered: 82189
Janky frames: 35335 (42.99%)
90th percentile: 34ms
95th percentile: 42ms
99th percentile: 69ms
Number Missed Vsync: 4706
Number High input latency: 142
Number Slow UI thread: 17270
Number Slow bitmap uploads: 1542
Number Slow draw: 23342

以上统计信息的实现可以详见源码:GfxMonitorImpl.java

在 Android M 以上的系统上,上述信息的获取十分方便(事实上也只有这些系统能够获取这些信息)。仅需要执行以下命令即可:

adb shell dumpsys gfxinfo <PACKAGE_NAME>

优点:除了对系统平台有较高的要求以外,其采集方式最为简单(系统自带功能);

2.3 丢帧计算

在一次Loop时如果执行时间超过了16.6ms,那么用多于16.6ms的时间除以16.6ms,即是当前App的丢帧(SF: Skipped Frame)
在16.6ms完成工作却因各种原因没做完,占了后n个16.6ms的时间,相当于丢了n帧
故:

SF=处理帧数 / (处理帧数 + 额外的垂直同步脉冲) * 60 计算(其中处理帧数常为128)
这个指标的就是指当前应用在丢帧发生时的丢帧帧数。

针对 Logcat 方案, 该数值直接在 Logcat 中输出,并且带有时间信息。

04-18 16:31:24.957 I/Choreographer(24164): Skipped 4 frames!  The application may be doing too much work on its main thread.
04-18 16:31:25.009 I/Choreographer(24164): Skipped 2 frames!  The application may be doing too much work on its main thread.

针对 Choreographer.FrameCallback 方案 以及 代码注入方案,我们可能很方便的通过计算前后两帧开始渲染的时间差获得这一数值,同样方便。同样与 Logcat 方案 不同的是,它也是可以设计成实时计算的。
缺点:Android4.2+系统,适用于SW/HW Rendering 及 部分 OpenGL Rendering

2.4 流畅度计算

原理:VSync 机制就像一台转速固定的发动机(60转/s),每一转带动着做一些 UI 相关的事情。有时候因为各种阻力, 某一圈的工作量比较重, 超过了 16.6ms, 那么这一秒内就不是 60 转了。
我们通过测量这个转速,来评判应用的流畅度。
和丢帧相对,在VSync机制中1s内Loop运行的次数。和丢帧相对1s内有60个Loop因为某几次工作时间超过了16.6ms(丢帧),这样Loop就无法运行60次(理论最大值)。当流畅度越小的时候说明当前程序越卡顿。

计算方式:SM = 帧率(60) * (单位时长总帧数 - 单位时长丢帧数) / 单位时长总帧数

操作:
VSync机制客户通过其Loop来了解当前App最高绘制能力,其机制如下:
1)固定每隔16.6ms执行一次;
2)如果没有绘制事件的时候也会运行这样一个Loop;
3)Loop在1s之内运行了多少次,即可以表示当前App绘制的最高能力,也就是App卡顿的程度。
if(存在帧的绘制):

      Loop = 1 帧绘制完成所占用的Vsync间隔

else:

      Loop = 1个Vsync间隔

所以SM计算方法为Loop在1s内运行了多少次(Loops per seconds),那么我们可以直接在App代码中通过Choreographer的回调FrameCallback来计算Loop被运行了几次,从而知道应用的流畅度。但在实际情况下我们不一定能修改代码(实际发布的版本不允许加入测试代码)或者根本拿不到代码(譬如和竞品进行对比)。

优点:数据形式与 FPS 类似,可以很好的弥补 FPS 无法准确刻画非连续绘制的应用显示性能的缺陷;
缺点:Android4.2+系统,适用于SW/HW Rendering 及 部分 OpenGL Rendering

3.卡顿问题分析工具

分析UI卡顿我们一般都借助工具,通过工具一般都可以直观的分析出问题原因,从而反推寻求优化方案,具体如下细说各种强大的工具

3.1 Hierarchy Viewer使用

我们可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度及冗余等分析
通过命令启动HierarchyViewer

Hierarchyviewer

接下来Hierarchy window窗口打开:


Hierarchyviewer

一个Activity的View树,通过这个树可以分析出View嵌套的冗余层级,以及每个View在绘制的使用时长也有表示。

3.2 使用Lint进行资源及冗余UI布局等优化

冗余资源及逻辑等也可能会导致加载和执行缓慢,这可以使用Link工具,发现代码中的流畅度性能问题;
在AndroidStudio 1.4版本中使用Lint最简单的办法:就是将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->点击确认开始检测,等待一下后会发现如下结果:

image

如果存在冗余的UI层级嵌套,会进行高亮显示, 我们根据提示可以点击跳进去进行优化处理掉的。

3.3 Tracer for OpenGL ES

可以记录和分析APP每一帧的绘制过程,以及列出所有乃至的OpenGL ES 的绘制函数和耗时;该工具操作后会生成一份记录App绘制过程和gltrace文件。

3.4 BlockCanary分析android卡顿

在复杂的项目环境中,由于历史代码庞大,业务复杂,包含各种第三方库,所以在出现了卡顿的时候,我们很难定位到底是哪里出现了问题,即便知道是哪一个Activity/Fragment,也仍然需要进去里面一行一行看,动辄数千行的类再加上跳来跳去调来调去的,结果就是不了了之随它去了,实在不行了再优化吧。于是一拖再拖,最后可能压根就改不动了,客户端越来越卡。
Android应用卡顿是非常普遍的现象,偶尔出现ANR。只有当APP出现ANR,我们才能得到当前堆栈信息。当应用只是卡顿或只是不太流畅的时候,我们能不能找出卡顿元凶呢?不依赖Debug和源码的情况,能不能找出卡顿的堆栈信息呢?我们需要找到一种方法来检测哪些函数可能会使应用发生ANR,在开发阶段就能找出卡顿元凶,提高应用流畅度。
BlockCanary就是来解决这个问题的。告别打点,告别Debug,哪里卡顿,一目了然,让优化代码变得有的放矢。
具体使用方法请点击:

BlockCanary介绍 地址
BlockCanary — 轻松找出Android App界面卡顿元凶

4.XX银行-性能评测对比掉帧率案例

4.1 总览

此次质量开放平台-评测中心(http://fit-stg1.jryzt.com/Hyperion-server/html/index.html)的性能测试的流畅度测试主要是针对场景页面的掉帧率数据采集进行对比分析, 原理公式为:掉帧率=处理帧数 / (处理帧数 + 额外的垂直同步脉冲) * 60 计算(其中处理帧数常为128)。一般掉帧率超过10%,我们就认为存在卡顿有必要进行分析定位。

4.2 掉帧率对比分析案例

这里选取了同一家银行的两个APP与行业竞品进行掉帧率对比分析,从掉帧率对比看,行业竞品均值为4.1%,90分位约13.1%,75分位约27.5%,中位数约39.6%,25分位约59.9%。

掉帧率对比

【福建农信】掉帧率为1.783%,表现良好,打败了行业90%以上的竞品
【榕商Bank】掉帧率为6.244%,表现良好,打败了行业90%以上的竞品
整体得分对比分析:
从首页启动到加载完成场景分析,【福建农信】实际启动到首页场景只有一个简单的未登录页,相比于丰富多样的【榕商Bank】来说属于非常简单的页面,但是它的掉帧率与丰富资源的【榕商Bank】比较相差不远。

福建农信】首页掉帧率问题分析:
单纯从页面表象观察,【福建农信】启动时,未登录页是从APP背景页下方飘进渐渐上升在页面中间,然后抖动一下再静止,有一种PPT飞入的动态效果。
通过深入分析得出【福建农信】应用交互中主线程存在卡顿,存在 Activity(LoginActivity)切换过慢的现象:


image

cn.com.fjnx.mobilebank.per.activity.account.LoginActivity.onCreate(阻塞1639 ms)
com.yitong.fjnx.mbank.android.Splash.onCreate(阻塞1717 ms)
建议【福建农信】优化启动时未登录页进入的方式

【榕商Bank】首页掉帧率问题分析:
首页加载掉帧率为8.2%,通过GPU过度绘制调试发现:com.pingan.fstandard.activity.MainActivity存在过度绘制。实际上是因为运营Banner位有轮播动态效果,轮播间隔时间设置的比较长,导致评测时掉帧率偏高,但是这是合理的产品设计,而且也不影响用户体验。

综上对比,【榕商Bank】流畅度表现优于【福建农信】,【福建农信】掉帧率仍然有优化空间。

5.App端卡顿问题排查思路

1)UI线程卡顿

问题:UI线程中有I/O读写、数据库访问等耗时操作,导致UI线程卡顿;
定位及解决:TraceView 寻找卡住主线程的地方,Systrace 获取 app 运行是线程的信息以及 API 的执行情况,避免在主线程执行 IO 操作。

2) 复杂、不合理的布局或过度绘制

问题:不合理的布局虽然可以完成功能,但随着控件数量越多、布局嵌套层次越深,展开布局花费的时间几乎是线性增长,性能也就越差;
定位及解决: 避免OverDraw导致的性能损耗;可以参考《Android性能优化(二)之布局优化面面观》

3)同一时间动画执行的次数过多

问题:同一时间动画执行的次数过多,导致CPU或GPU负载过重;

4) 内存使用异常导致卡顿

问题:内存抖动、内存泄漏都会导致:GC的次数越多、消耗在GC上的时间越长,CPU花在界面绘制上的时间相应越短;
解决:节省内存的分配空间,尽可能的降低GC的频率,缩短GC的平均时间;CPU不被占用,卡顿的几率就会更低; 可以参考《Android性能优化(四)之内存优化实战》

5) 冗余资源及逻辑等导致加载和执行缓慢;

问题:对线程开启方式的不同选择以及不同配置都可能导致卡顿的发生;

解决:任何耗时操作正确的移到异步里,类如I/O读写、数据库访问等都应该采用异步的方式,不能有“只是一个很小的文件”之类的想法,防微杜渐;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容