Flutter 应用性能检测与优化

概述

软件项目的交付是一个复杂且漫长的过程,任何细小的失误都有可能致使交付过程失败。在软件开发过程当中,除了代码逻辑的 Bug 和视觉异常这些功能层面的问题以外,移动应用另外一类常见的问题是性能问题,好比滑动操做不流畅、页面出现卡顿丢帧现象等。这些问题虽然不至于让移动应用彻底不可用,但也很容易引发用户反感,从而对应用质量产生质疑,甚至失去耐心。前端

那么,对于应用渲染并不流畅,出现了性能问题,咱们该如何检测,又该从哪里着手处理呢?和移动开发相似, Flutter 的性能问题主要能够分为 GPU 线程问题和 UI 线程(CPU)问题两类。对于这些问题,有一个通用的套路:首先,都须要先经过性能图层进行初步分析,而一旦确认问题存在,接下来就是利用 Flutter 提供的各种分析工具来进行问题定位。android

图层分析

Flutter运行模式

一、Debug

Debug模式能够在真机和模拟器上同时运行,此模式会打开全部的断言,包括debugging信息、debugger aids(好比observatory)和服务扩展。优化了快速develop/run循环,可是没有优化执行速度、二进制大小和部署。命令flutter run就是以这种模式运行的,经过sky/tools/gn --android或者sky/tools/gn --ios来构建应用的。ios

二、Release

Release模式只能在真机上运行,不能在模拟器上运行:会关闭全部断言和debugging信息,关闭全部debugger工具。优化了快速启动、快速执行和减少包体积。禁用全部的debugging aids和服务扩展。这个模式是为了部署给最终的用户使用。命令flutter run --release就是以这种模式运行的,经过sky/tools/gn --android --runtime-mode=release或者sky/tools/gn --ios --runtime-mode=release来构建应用。算法

三、Profile

Profile模式只能在真机上运行,不能在模拟器上运行,基本和Release模式一致,除了启用了服务扩展和tracing,以及一些为了最低限度支持tracing运行的东西(好比能够链接observatory到进程)。命令flutter run --profile就是以这种模式运行的,经过sky/tools/gn --android --runtime-mode=profile或者sky/tools/gn --ios --runtime-mode=profile来构建应用。缓存

四、test

headless test模式只能在桌面上运行,基本和Debug模式一致,除了是headless的并且你能在桌面运行。命令flutter test就是以这种模式运行的,经过sky/tools/gn来build。并发

在实际开发中,应该用到上面所说的四种模式又各自分为两种:一种是未优化的模式,供开发人员调试使用;一种是优化过的模式,供最终的开发人员使用。默认状况下是未优化模式,若是要开启优化模式,build的时候在命令行后面添加--unoptimized参数。app

无论是移动开发仍是前端开发,对于性能问题分析的思路都是先分析并定位问题,Flutter也不例外,借助Flutter 提供的度量性能工具,咱们能够快速定位代码中的性能问题,而性能图层就是帮助咱们确认问题影响范围的利器,它相似Android的图层分析工具。less

为了使用性能图层,Flutter提供了分析(Profile)模式,与调试代码能够经过模拟器在调试模式下找到代码逻辑 Bug 不一样,性能问题须要在发布模式下使用真机进行检测。相比发布(Release)模式而言,调试模式增长了不少额外的检查(好比断言),这些检查可能会耗费不少资源;更重要的是,调试模式使用 JIT (即时编译)模式运行应用,代码执行效率较低。这就使得调试模式运行的应用,没法真实反映出它的性能问题。ide

而另外一方面,模拟器使用的指令集为 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个不一样颜色的RowItem
      itemBuilder: (context, index)=>TabRowItem(
            index: index,
            lastItem: index == 100 - 1,
            color: colorItems[index],//设置不一样的颜色
            colorName: colorNameItems[index],
          )
    )
);

其中,动态模糊的NavigationBar效果以下图所示。
在这里插入图片描述
当咱们开启checkerboardOffscreenLayers以后,能够看到视图蒙层效果对GPU的渲染压力致使性能视图频繁闪动。若是咱们没有对动态模糊效果有特殊需求,则可使用不带模糊效果的 Scaffold 和白色的 AppBar 实现一样的产品功能,来解决这个性能问题。

Scaffold(
  //使用普通的白色AppBar
  appBar: AppBar(title: Text('Home', style: TextStyle(color:Colors.black),),backgroundColor: Colors.white),
  body: ListView.builder(
      itemCount: 100,
      //为列表建立100个不一样颜色的RowItem
      itemBuilder: (context, index)=>TabRowItem(
        index: index,
        lastItem: index == 100 - 1,
        color: colorItems[index],//设置不一样的颜色
        colorName: colorNameItems[index],
      )
  ),
);

运行一下代码,能够看到,在去掉了动态模糊效果以后,GPU 的渲染压力获得了缓解,checkerboardOffscreenLayers 检测图层也再也不频繁闪烁了。
在这里插入图片描述

checkerboardRasterCacheImages

从资源的角度看,另外一类很是消耗性能的操做是渲染图像,由于图像渲染会涉及 I/O、GPU 存储以及不一样通道的数据格式转换,所以渲染过程的构建须要消耗大量资源。为了缓解 GPU 的压力,Flutter 提供了多层次的缓存快照,这样 Widget 重建时就无需从新绘制静态图像了。

与检查多视图叠加渲染的 checkerboardOffscreenLayers 参数相似,Flutter 提供了检查缓存图像的开关 checkerboardRasterCacheImages,来检测在界面重绘时频繁闪烁的图像。

为了提升静态图像显示性能,咱们能够把须要静态缓存的图像加到 RepaintBoundary 中,RepaintBoundary 能够肯定 Widget 树的重绘边界,若是图像足够复杂,Flutter 引擎会自动将其缓存,从而避免重复刷新。固然,由于缓存资源有限,若是引擎认为图像不够复杂,也可能会忽略 RepaintBoundary。下面的代码展现了经过 RepaintBoundary,将一个静态复合 Widget 加入缓存的具体用法,以下所示。

RepaintBoundary(//设置静态缓存图像
  child: Center(
    child: Container(
      color: Colors.black,
      height: 10.0,
      width: 10.0,
    ),
));

UI 线程问题定位

若是说 GPU 线程问题定位的是渲染引擎底层渲染异常,那么 UI 线程问题发现的则是应用的性能瓶颈。好比在视图构建时,在 build 方法中使用了一些复杂的运算,或是在主 Isolate 中进行了同步的 I/O 操做。这些问题,都会明显增长 CPU 的处理时间,拖慢应用的响应速度。

针对这类问题,咱们可使用 Flutter 提供的 Performance 工具,来记录应用的执行轨迹。Performance 是一个强大的性能分析工具,可以以时间轴的方式展现 CPU 的调用栈和执行时间,去检查代码中可疑的方法调用。

打开 Android Studio 底部工具栏中的“Open DevTools”按钮以后,系统会自动打开 Dart DevTools 的网页,将顶部的 tab 切换到 Performance 后,咱们就能够开始分析代码中的性能问题了。

在这里插入图片描述
在这里插入图片描述
接下来,咱们经过一个在 ListView 中计算 MD5 的例子来演示 Performance 的具体分析过程。考虑到在 build 函数中进行渲染信息的组装是一个常见的操做,为了演示Performance的使用过程,咱们故意放大计算 MD5 的耗时,如循环迭代计算了 1 万次。

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key}) : super(key: key);

  String generateMd5(String data) {
    //MD5固定算法
    var content = new Utf8Encoder().convert(data);
    var digest = md5.convert(content);
    return hex.encode(digest.bytes);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('demo')),
      body: ListView.builder(
          itemCount: 30,// 列表元素个数
          itemBuilder: (context, index) {
            //反复迭代计算MD5
            String str = '1234567890abcdefghijklmnopqrstuvwxyz';
            for(int i = 0;i<10000;i++) {
              str = generateMd5(str);
            }
            return ListTile(title: Text("Index : $index"), subtitle: Text(str));
          }// 列表项建立方法
      ),
    );
  }
}

与性能图层可以自动记录应用执行状况不一样,使用 Performance 来分析代码执行轨迹,咱们须要手动点击【Record】按钮去主动触发,在完成信息的抽样采集后再点击【Stop】按钮结束录制,而后就能够获得在这期间应用的执行状况了。

Performance 记录的应用执行状况叫作 CPU 帧图,又被称为火焰图。火焰图是基于记录代码执行结果所产生的图片,用来展现 CPU 的调用栈,表示的是 CPU 的繁忙程度。因此,咱们要检测 CPU 耗时问题,皆能够查看火焰图底部的哪一个函数占据的宽度最大。只要有“平顶”,就表示该函数可能存在性能问题,以下图所示。
在这里插入图片描述
能够看到,_MyHomePage.generateMd5 函数的执行时间最长,几乎占满了整个火焰图的宽,而这也与代码中存在的问题是一致的。在找到了问题以后,咱们就可使用 Isolate(或 compute)将这些耗时的操做挪到并发主 Isolate 以外去完成了。

总结

在 Flutter 中,性能分析过程能够分为 GPU 线程问题定位和 UI 线程(CPU)问题定位,而它们都须要在真机上以分析模式(Profile)启动应用,并经过性能图层分析大体的渲染问题范围。
一旦确认问题存在,接下来就须要利用 Flutter 所提供的分析工具来定位问题缘由了。关于 GPU 线程渲染问题,咱们能够重点检查应用中是否存在多视图叠加渲染,或是静态图像反复刷新的现象。而 UI 线程渲染问题,咱们则是经过 Performance 工具记录的火焰图(CPU 帧图),分析代码耗时来找出应用执行瓶颈。

总的来讲,因为 Flutter 采用基于声明式的 UI 设计理念,以数据驱动渲染,并采用 Widget->Element->RenderObject 三层结构,屏蔽了无谓的界面刷新,可以保证绝大多数状况下咱们构建的应用都是高性能的,因此在使用分析工具检测出性能问题以后,一般咱们并不须要作太多的细节优化工做,只须要在改造过程当中避开一些常见的坑,就能够得到优异的性能。同时,为了不形成性能问题,还应该从如下几个方面着手:

  • 控制 build 方法耗时,将 Widget 拆小,避免直接返回一个巨大的 Widget,这样 Widget 会享有更细粒度的重建和复用;
  • 尽可能不要为 Widget 设置半透明效果,而是考虑用图片的形式代替,这样被遮挡的 Widget 部分区域就不须要绘制了;
  • 对列表采用懒加载而不是直接一次性建立全部的子 Widget,这样视图的初始化时间就减小了。
相关文章
相关标签/搜索