概述
软件项目的交付是一个复杂且漫长的过程,任何细小的失误都有可能导致交付过程失败。在软件开发过程中,除了代码逻辑的 Bug 和视觉异常这些功能层面的问题之外,移动应用另一类常见的问题是性能问题,比如滑动操作不流畅、页面出现卡顿丢帧现象等。这些问题虽然不至于让移动应用完全不可用,但也很容易引起用户反感,从而对应用质量产生质疑,甚至失去耐心。
那么,对于应用渲染并不流畅,出现了性能问题,我们该如何检测,又该从哪里着手处理呢?和移动开发类似, Flutter 的性能问题主要可以分为 GPU 线程问题和 UI 线程(CPU)问题两类。对于这些问题,有一个通用的套路:首先,都需要先通过性能图层进行初步分析,而一旦确认问题存在,接下来就是利用 Flutter 提供的各类分析工具来进行问题定位。
图层分析
Flutter运行模式
1、Debug
Debug模式可以在真机和模拟器上同时运行,此模式会打开所有的断言,包括debugging信息、debugger aids(比如observatory)和服务扩展。优化了快速develop/run循环,但是没有优化执行速度、二进制大小和部署。命令flutter run就是以这种模式运行的,通过 sky/tools/gn --android
或者 sky/tools/gn --ios
来构建应用的。
2、Release
Release模式只能在真机上运行,不能在模拟器上运行:会关闭所有断言和debugging信息,关闭所有debugger工具。优化了快速启动、快速执行和减小包体积。禁用所有的debugging aids和服务扩展。这个模式是为了部署给最终的用户使用。命令 flutter run --release
就是以这种模式运行的,通过 sky/tools/gn --android --runtime-mode=release
或者 sky/tools/gn --ios --runtime-mode=release
来构建应用。
3、Profile
Profile模式只能在真机上运行,不能在模拟器上运行,基本和Release模式一致,除了启用了服务扩展和tracing,以及一些为了最低限度支持tracing运行的东西(比如可以连接observatory到进程)。命令 flutter run --profile
就是以这种模式运行的,通过 sky/tools/gn --android --runtime-mode=profile
或者 sky/tools/gn --ios --runtime-mode=profile
来构建应用。
4、test
headless test模式只能在桌面上运行,基本和Debug模式一致,除了是headless的而且你能在桌面运行。命令 flutter test
就是以这种模式运行的,通过 sky/tools/gn
来build。
在实际开发中,应该用到上面所说的四种模式又各自分为两种:一种是未优化的模式,供开发人员调试使用;一种是优化过的模式,供最终的开发人员使用。默认情况下是未优化模式,如果要开启优化模式,build的时候在命令行后面添加–unoptimized参数。
不管是移动开发还是前端开发,对于性能问题分析的思路都是先分析并定位问题,Flutter也不例外,借助Flutter 提供的度量性能工具,我们可以快速定位代码中的性能问题,而性能图层就是帮助我们确认问题影响范围的利器,它类似Android的图层分析工具。
为了使用性能图层,Flutter提供了分析(Profile)模式,与调试代码可以通过模拟器在调试模式下找到代码逻辑 Bug 不同,性能问题需要在发布模式下使用真机进行检测。相比发布(Release)模式而言,调试模式增加了很多额外的检查(比如断言),这些检查可能会耗费很多资源;更重要的是,调试模式使用 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 线程的情况显示在下面,蓝色垂直的线条表示已执行的正常帧,绿色的线条代表的是当前帧。
同时,为了保持 60Hz 的刷新频率,GPU 线程与 UI 线程中执行每一帧耗费的时间都应该小于 16ms(1/60 秒)。在这其中有一帧处理时间过长,就会导致界面卡顿,图表中就会展示出一个红色竖条,如下图所示。
如果红色竖条出现在 GPU 线程图表,意味着渲染的图形太复杂,导致无法快速渲染;而如果是出现在了 UI 线程图表,则表示 Dart 代码消耗了大量资源,需要优化代码执行时间。
GPU问题定位
GPU渲染问题主要集中在底层渲染耗时上,有时候 Widget 树虽然构造起来容易,但在 GPU 线程下的渲染却很耗时。例如,涉及 Widget 裁剪、蒙层这类多视图叠加渲染,或是由于缺少缓存导致静态图像的反复绘制,都会明显拖慢 GPU 的渲染速度。
接下来,使用性能图层提供的两项参数,即检查多视图叠加的视图渲染开关 checkerboardOffscreenLayers和检查缓存的图像开关checkerboardRasterCacheImages来检查这两种情况。
checkerboardOffscreenLayers
多视图叠加通常会用到 Canvas 里的 savaLayer 方法,这个方法在实现一些特定的效果(比如半透明)时非常有用,但由于其底层实现会在 GPU 渲染上涉及多图层的反复绘制,因此会带来较大的性能问题。
对于 saveLayer 方法使用情况的检查,我们只需要在 MaterialApp 的初始化方法中,将 checkerboardOffscreenLayers 开关设置为 true,分析工具就会自动帮我们检测多视图叠加的情况。使用了 saveLayer 的 Widget 会自动显示为棋盘格式,并随着页面刷新而闪烁。不过,saveLayer 是一个较为底层的绘制方法,因此我们一般不会直接使用它,而是会通过一些功能性 Widget,在涉及需要剪切或半透明蒙层的场景中间接地使用。所以一旦遇到这种情况,我们需要思考一下是否一定要这么做,能不能通过其他方式来实现呢?
比如下面的例子中,我们使用 CupertinoPageScaffold 与 CupertinoNavigationBar 实现了一个动态模糊的效果,代码如下:
CupertinoPageScaffold(navigationBar: CupertinoNavigationBar(),//动态模糊导航栏child: ListView.builder(itemCount: 100,//为列表创建100个不同颜色的RowItemitemBuilder: (context, index)=>TabRowItem(index: index,lastItem: index == 100 - 1,color: colorItems[index],//设置不同的颜色colorName: colorNameItems[index],))
);
其中,动态模糊的NavigationBar效果如下图所示。
当我们开启checkerboardOffscreenLayers之后,可以看到视图蒙层效果对GPU的渲染压力导致性能视图频繁闪动。如果我们没有对动态模糊效果有特殊需求,则可以使用不带模糊效果的 Scaffold 和白色的 AppBar 实现同样的产品功能,来解决这个性能问题。
Scaffold(//使用普通的白色AppBarappBar: AppBar(title: Text('Home', style: TextStyle(color:Colors.black),),backgroundColor: Colors.white),body: ListView.builder(itemCount: 100,//为列表创建100个不同颜色的RowItemitemBuilder: (context, index)=>TabRowItem(index: index,lastItem: index == 100 - 1,color: colorItems[index],//设置不同的颜色colorName: colorNameItems[index],)),
);
运行一下代码,可以看到,在去掉了动态模糊效果之后,GPU 的渲染压力得到了缓解,checkerboardOffscreenLayers 检测图层也不再频繁闪烁了。