您的位置首页  家电知识  评测

Android App性能评测分析-流畅度篇

  • 来源:互联网
  • |
  • 2019-10-18
  • |
  • 0 条评论
  • |
  • |
  • T小字 T大字

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

  因为屏幕的刷新过程是自上而下、自左向右的,如果帧率刷新率,当屏幕还没有刷新n-1帧的数据时,就开始生成第n帧的数据了,从上到下,覆盖第n-1帧。如果此时刷新屏幕,就会出现图像的上半部分是第n帧的,下半部分是第n帧的现象。CPU/GPU一直都在渲染。

  Android系统每隔16ms发出VSYNC信号,触发GPU对UI进行渲染,如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候由于还没有准备好,就无法进行更新任何内容,那么用户在32ms内看到的会是同一帧画面(卡顿现象),即丢帧现象。

  GPU向缓存中写入数据,屏幕从缓存中读取数据,刷新后显示。由于刷新率和帧率并不总是一致的,很可能导致撕裂的现象。为了解决单缓存的画面撕裂问题,出现了双缓存和 VSync 。

  双缓存使用了两个缓存区: Back Buffer 、 Frame Buffer。当写入下一帧时,GPU会先填充 Back Buffer 中,当刷新屏幕时,屏幕从 Frame Buffer 中读数据。VSYNC 主要是完成帧的复制,开始下一帧的渲染。

  当帧率大于刷新频率时,通过使帧率跟刷新频率保持同步,从而避免画面撕裂的现象(只有当 VSync 信号产生时, CPU/GPU 才会开始绘制)。当VSync 信号产生时,先完成Back Buffer 到 Frame Buffer的复制操作(通过交换内存地址),然后通知 CPU/GPU 绘制下一帧图像。也只有VSync 信号发生时,才绘制下一帧。

  当刷新频率帧率时,此时刷新屏幕,发出VSYNC 信号,由于CPU/GPU的渲染操作还没有完成,就不把Back Buffer的数据复制到 Frame Buffer,此时就从Frame Buffer去取旧数据,这样在两个刷新周期里,显示的是同一帧数据。

  双重缓存的缺陷在于:当 CPU/GPU 绘制一帧的时间超过 16 ms 时,会产生 Jank。更要命的是,产生 Jank 的那一帧的显示期间,GPU/CPU 都是在闲置的。

  如果有第三个 Buffer 能让 CPU/GPU 在这个时候继续工作,那就完全可以避免第二个 Jank 的发生了!

  VSync是VerticalSynchronization(垂直同步)的缩写,是一种在PC上很早就广泛使用的技术,可以简单的把它认为是一种定时中断。而在Android 4.1(JB)中已经开始引入VSync机制。CPU和GPU的处理时间都少于一个VSync的间隔,即16.6ms。如果每个间隔都有绘制的情况下,当前的FPS即为60帧。VSync机制就像是播放动画片(60帧/s)。每次都会播放画面,有的时候有人偷懒了,机器坏了,就会出现播放速度降低的状况。我们把这个播放速度叫做流畅度。

  用过flash的人应该知道动画片其实是由一张张画出来的图片连贯执行产生的效果,当一张张独立的图片切换速度足够快的时候,会欺骗我们的眼睛,以为这是连续的动作。反之类推,当你的图片切换不够快的时候,就会被人眼看穿,反馈给用户的就是所谓的卡顿现象。

  想要让大脑觉得动作是连续的,至少是每秒10-12帧的速度,而想达到流畅的效果,至少需要每秒24帧。这也是为什么电影片源通常都是24帧的原因,好奇的同学点击知乎高知

  看看大神的解答。不过60帧每秒的流畅度是最佳的,我们的目标就是让程序的流畅度能接近60帧每秒,当然超过60帧速的话大部分人还是会受不了的。

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

  1)有时候FPS很低,我们却感觉不到卡顿,因为本来就用不到那么高的FPS,比如画一个动画只画了0.5秒就画完了,那么FPS最高也只有30帧/秒(标准是60帧/每秒),但这并不代表它是卡顿的,用0.5秒动画就画完了,不能为了凑够60帧/秒,在做个1s的动画吧。而如果屏幕根本没有绘制需求,即屏幕显示的画面是静止的,那FPS就为0。

  2)App停止操作后FPS还一直变化,是因为屏幕每一帧的合成都是针对手机里的所有进程,那么即使你的App停止了绘制,手机里其他进程可能还在绘制,比如通知栏的各种消息,这会导致FPS继续变化。

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

  系统合成帧率(FPS):数据形式最为直观(FPS 是最早的显示性能指标,而且在多个平台中都有着类似的定义),且对系统平台的要求最低(API level 1),游戏、视频等连续绘制的应用可以考虑选用,但不适用于绝大多数非连续绘制的应用;

  流畅度(SM):数据形式与 FPS 类似,可以很好的弥补 FPS 无法准确刻画非连续绘制的应用显示性能的缺陷;

  应用跳帧次数、幅度(Aggregate frame stats):除了对系统平台有较高的要求以外,其采集方式最为简单(系统自带功能);

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

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

  解读:屏幕下方的柱形图会持续刷新,最上方会有一根绿色的线ms的阈值,超过这个界限表示当前帧绘制的时间出现了延迟,及卡顿现象,后面会详细介绍原因。横坐标表示时间的持续,每一根柱形图表示当前帧的绘制时间。因此我们在使用的过程中,下面的柱形图会一直的刷新,单位是ms。各位看官是否有注意到每一帧的柱形图颜色不一样呢(注:不同手机的颜色不一样,仅限安卓4.0以上版本参考)

  缺点:这个方式获取到的渲染时间只是UI主线程上的绘制行为。“GPU呈现模式分析”的数据只能说明个现象,比如上面提到的数据,能说明在实际运行中会有短暂的长时间绘制问题。但造成问题的具体原因并没有说明。

  而且“GPU呈现模式分析”显示的是最后128帧的数据,但丢帧也有可能是两帧之间存在长时间的操作而造成的。

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

  Process:表示是消耗在Android的2D渲染器执行显示列表的时间,view越多,要执行的绘图命令就越多,时间就越长

  Execute:消耗在排列每个发送过来的帧的顺序的时间.或者说是CPU告诉GPU渲染一帧的时间,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复。所以这个时间,一般都很短。

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

  知道android绘制原理的人应该能明白,SurfaceFlinger就是负责绘制Android应用程序UI的服务,所以surfaceFlinger能反应出整体绘制情况,一般正常情况都是连续的,如果出现空档,一种是没有操作或者滑动到头,没东西需要绘制,这种属于正常,另一种就是有问题存在,有其他操作时间过长。

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

  SF=处理帧数 / (处理帧数 + 额外的垂直同步脉冲) * 60 计算(其中处理帧数常为128)

  针对 Choreographer.FrameCallback 方案 以及 代码注入方案,我们可能很方便的通过计算前后两帧开始渲染的时间差获得这一数值,同样方便。同样与 Logcat 方案 不同的是,它也是可以设计成实时计算的。

  原理:VSync 机制就像一台转速固定的发动机(60转/s),每一转带动着做一些 UI 相关的事情。有时候因为各种阻力, 某一圈的工作量比较重, 超过了 16.6ms, 那么这一秒内就不是 60 转了。

  和丢帧相对,在VSync机制中1s内Loop运行的次数。和丢帧相对1s内有60个Loop因为某几次工作时间超过了16.6ms(丢帧),这样Loop就无法运行60次(理论最大值)。当流畅度越小的时候说明当前程序越卡顿。

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

  3)Loop在1s之内运行了多少次,即可以表示当前App绘制的最高能力,也就是App卡顿的程度。

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

  优点:数据形式与 FPS 类似,可以很好的弥补 FPS 无法准确刻画非连续绘制的应用显示性能的缺陷;

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

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

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

  冗余资源及逻辑等也可能会导致加载和执行缓慢,这可以使用Link工具,发现代码中的流畅度性能问题;

  由于Android系统会依据内存中不同的内存数据类型分别执行不同的GC操作,常见应用开发中导致GC频繁执行的原因主要可能是因为短时间内有大量频繁的对象创建与释放操作,也就是俗称的内存抖动现象,或者短时间内已经存在大量内存暂用介于阈值边缘,接着每当有新对象创建时都会导致超越阈值触发GC操作

  如果看到,这种不停的大面积打印GC导致所有线程暂停的操作必定会导致UI视觉的卡顿,所以我们要避免此类问题的出现,具体的常见优化方式如下:

  避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等);

  有了上面说明GC导致的性能后我们就该定位分析问题了,我们可以通过运行DDMS-Allocation Tracker标签打开一个新窗口,然后点击Start Tracing按钮,接着运行你想分析的代码,运行完毕后点击GetAllocations按钮就能够看见一个已分配对象的列表,如下:

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

  在复杂的项目环境中,由于历史代码庞大,业务复杂,包含各种第三方库,所以在出现了卡顿的时候,我们很难定位到底是哪里出现了问题,即便知道是哪一个Activity/Fragment,也仍然需要进去里面一行一行看,动辄数千行的类再加上跳来跳去调来调去的,结果就是不了了之随它去了,实在不行了再优化吧。于是一拖再拖,最后可能压根就改不动了,客户端越来越卡。

  Android应用卡顿是非常普遍的现象,偶尔出现ANR。只有当APP出现ANR,我们才能得到当前堆栈信息。当应用只是卡顿或只是不太流畅的时候,我们能不能找出卡顿元凶呢?不依赖Debug和源码的情况,能不能找出卡顿的堆栈信息呢?我们需要找到一种方法来检测哪些函数可能会使应用发生ANR,在开发阶段就能找出卡顿元凶,提高应用流畅度。

  BlockCanary就是来解决这个问题的。告别打点,告别Debug,哪里卡顿,一目了然,让优化代码变得有的放矢。

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

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

  从首页启动到加载完成场景分析,【福建农信】实际启动到首页场景只有一个简单的未登录页,相比于丰富多样的【榕商Bank】来说属于非常简单的页面,但是它的掉帧率与丰富资源的【榕商Bank】比较相差不远。

  单纯从页面表象观察,【福建农信】启动时,未登录页是从APP背景页下方飘进渐渐上升在页面中间,然后抖动一下再静止,有一种PPT飞入的动态效果。

  通过深入分析得出【福建农信】应用交互中主线程存在卡顿,存在 Activity(LoginActivity)切换过慢的现象:

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

  定位及解决:TraceView 寻找卡住主线程的地方,Systrace 获取 app 运行是线程的信息以及 API 的执行情况,避免在主线程执行 IO 操作。

  问题:不合理的布局虽然可以完成功能,但随着控件数量越多、布局嵌套层次越深,展开布局花费的时间几乎是线性增长,性能也就越差;

  定位及解决: 避免OverDraw导致的性能损耗;可以参考《Android性能优化(二)之布局优化面面观》

  问题:内存抖动、内存泄漏都会导致:GC的次数越多、消耗在GC上的时间越长,CPU花在界面绘制上的时间相应越短;

  解决:节省内存的分配空间,尽可能的降低GC的频率,缩短GC的平均时间;CPU不被占用,卡顿的几率就会更低; 可以参考《Android性能优化(四)之内存优化实战》

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

  插播:金融壹账通质量开放平台现提供测试一站式解决方案,包括UI自动化、测试过程管理、app评测、接口自动化、接口压测、舆情监控等测试服务,欢迎访问:。

免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186
友荐云推荐