37 | 如何检测并优化Flutter App的整体性能表现?
下载APP
关闭
渠道合作
推荐作者
37 | 如何检测并优化Flutter App的整体性能表现?
2019-09-21 陈航 来自北京
《Flutter核心技术与实战》
课程介绍
讲述:陈航
时长13:38大小12.48M
你好,我是陈航。
在上一篇文章中,我与你分享了调试 Flutter 代码的 3 种基本方式,即输出日志、断点调试与布局调试。
通过可定制打印行为的 debugPrint 函数,我们可以实现生产环境与开发环境不同的日志输出行为,从而保证在开发期打印的调试信息不会被发布至线上;借助于 IDE(Android Studio)所提供的断点调试选项,我们可以不断调整代码执行步长和代码暂停条件,收敛问题发生范围,直至找到问题根源;而如果我们想找出代码中的布局渲染类 Bug,则可以通过 Debug Painting 和 Flutter Inspector 提供的辅助线和视图可视化信息,来更为精准地定位视觉问题。
除了代码逻辑 Bug 和视觉异常这些功能层面的问题之外,移动应用另一类常见的问题是性能问题,比如滑动操作不流畅、页面出现卡顿丢帧现象等。这些问题虽然不至于让移动应用完全不可用,但也很容易引起用户反感,从而对应用质量产生质疑,甚至失去耐心。
那么,如果应用渲染并不流畅,出现了性能问题,我们该如何检测,又该从哪里着手处理呢?
在 Flutter 中,性能问题可以分为 GPU 线程问题和 UI 线程(CPU)问题两类。这些问题的确认都需要先通过性能图层进行初步分析,而一旦确认问题存在,接下来就需要利用 Flutter 提供的各类分析工具来定位问题了。
所以在今天这篇文章中,我会与你一起学习分析 Flutter 应用性能问题的基本思路和工具,以及常见的优化办法。
如何使用性能图层?
要解决问题,我们首先得了解如何去度量问题,性能分析也不例外。Flutter 提供了度量性能问题的工具和手段,来帮助我们快速定位代码中的性能问题,而性能图层就是帮助我们确认问题影响范围的利器。
为了使用性能图层,我们首先需要以分析(Profile)模式启动应用。与调试代码可以通过模拟器在调试模式下找到代码逻辑 Bug 不同,性能问题需要在发布模式下使用真机进行检测。
这是因为,相比发布模式而言,调试模式增加了很多额外的检查(比如断言),这些检查可能会耗费很多资源;更重要的是,调试模式使用 JIT 模式运行应用,代码执行效率较低。这就使得调试模式运行的应用,无法真实反映出它的性能问题。
而另一方面,模拟器使用的指令集为 x86,而真机使用的指令集是 ARM。这两种方式的二进制代码执行行为完全不同,因此模拟器与真机的性能差异较大:一些 x86 指令集擅长的操作模拟器会比真机快,而另一些操作则会比真机慢。这也使得我们无法使用模拟器来评估真机才能出现的性能问题。
为了调试性能问题,我们需要在发布模式的基础之上,为分析工具提供少量必要的应用追踪信息,这就是分析模式。除了一些调试性能问题必须的追踪方法之外,Flutter 应用的分析模式和发布模式的编译和运行是类似的,只是启动参数变成了 profile 而已:我们既可以在 Android Studio 中通过菜单栏点击 Run->Profile ‘main.dart’ 选项启动应用,也可以通过命令行参数 flutter run --profile 运行 Flutter 应用。
分析渲染问题
在完成了应用启动之后,接下来我们就可以利用 Flutter 提供的渲染问题分析工具,即性能图层(Performance Overlay),来分析渲染问题了。
性能图层会在当前应用的最上层,以 Flutter 引擎自绘的方式展示 GPU 与 UI 线程的执行图表,而其中每一张图表都代表当前线程最近 300 帧的表现,如果 UI 产生了卡顿(跳帧),这些图表可以帮助我们分析并找到原因。
下图演示了性能图层的展现样式。其中,GPU 线程的性能情况在上面,UI 线程的情况显示在下面,蓝色垂直的线条表示已执行的正常帧,绿色的线条代表的是当前帧:
图 1 性能图层
为了保持 60Hz 的刷新频率,GPU 线程与 UI 线程中执行每一帧耗费的时间都应该小于 16ms(1/60 秒)。在这其中有一帧处理时间过长,就会导致界面卡顿,图表中就会展示出一个红色竖条。下图演示了应用出现渲染和绘制耗时的情况下,性能图层的展示样式:
图 2 渲染和绘制耗时异常
如果红色竖条出现在 GPU 线程图表,意味着渲染的图形太复杂,导致无法快速渲染;而如果是出现在了 UI 线程图表,则表示 Dart 代码消耗了大量资源,需要优化代码执行时间。
接下来,我们就先看看 GPU 问题定位吧。
GPU 问题定位
GPU 问题主要集中在底层渲染耗时上。有时候 Widget 树虽然构造起来容易,但在 GPU 线程下的渲染却很耗时。涉及 Widget 裁剪、蒙层这类多视图叠加渲染,或是由于缺少缓存导致静态图像的反复绘制,都会明显拖慢 GPU 的渲染速度。
我们可以使用性能图层提供的两项参数,即检查多视图叠加的视图渲染开关 checkerboardOffscreenLayers,和检查缓存的图像开关 checkerboardRasterCacheImages,来检查这两种情况。
checkerboardOffscreenLayers
多视图叠加通常会用到 Canvas 里的 savaLayer 方法,这个方法在实现一些特定的效果(比如半透明)时非常有用,但由于其底层实现会在 GPU 渲染上涉及多图层的反复绘制,因此会带来较大的性能问题。
对于 saveLayer 方法使用情况的检查,我们只要在 MaterialApp 的初始化方法中,将 checkerboardOffscreenLayers 开关设置为 true,分析工具就会自动帮我们检测多视图叠加的情况了:使用了 saveLayer 的 Widget 会自动显示为棋盘格式,并随着页面刷新而闪烁。
不过,saveLayer 是一个较为底层的绘制方法,因此我们一般不会直接使用它,而是会通过一些功能性 Widget,在涉及需要剪切或半透明蒙层的场景中间接地使用。所以一旦遇到这种情况,我们需要思考一下是否一定要这么做,能不能通过其他方式来实现呢。
比如下面的例子中,我们使用 CupertinoPageScaffold 与 CupertinoNavigationBar 实现了一个动态模糊的效果。
图 3 动态模糊效果
由于视图滚动过程中频繁涉及视图蒙层效果的更新,因此 checkerboardOffscreenLayers 检测图层也感受到了对 GPU 的渲染压力,频繁的刷新闪烁。