Flutter性能优化初探

本文目的

  • 介绍应用流畅性的检测和优化策略
  • 介绍内存的检测和优化策略

目录结构

  • Flutter的绘图原理和UI的基本流程
  • 流畅性
  • 内存优化
    性能优化涉及了应用的方法面面,很难一言以蔽之。本文我们主要讨论性能优化的两大主题 —— 流畅性和内存优化,并分别介绍了他们的检测方法和优化策略。另外,我们在优化的同时要用数据来证明优化的效果。关于如何检测应用流畅性和获取内存状态,Dart 提供了一个性能检测工具Observatory,参考使用介绍性能检测工具 Observatory 、DevTools的基础使用及测试用例

Flutter的绘图原理和UI的基本流程

Flutter的绘图原理

从图中可以看到,当GPU发出vsync信号时,会执行Dart代码绘制新UI,Dart-code会被执行为Layer Tree,然后经过Compositor合成后交由Skia引擎渲染处理为GPU数据,最后通过GL/Vulkan发给GPU。 而我们要分析的地方就在Dart->Layer Tree这里。

UI的基本流程

比如用户一个输入操作,可以理解发出为Vsync信号,这时,flutter会先做Animation相关工作,然后Build当前UI,之后视图开始布局和绘制。生成视图数据,但是只会生成Layer Tree,并不能直接使用,还是需要Composite合成为一个Layer进行Rasterize光栅化处理。层级合并的原因是因为一般flutter的层级很多,直接把每一层传给GPU传递,效率很低,所以会先做Composite,提高效率。 光栅化之后才会给Flutter-Engine处理,这里只是Framework层面的工作,所以看不到Engine,而我们分析的也只是Framework中的一小部分。

流畅性

App 流畅性的关键指标有 UI帧率,GPU帧率,我们期望它能达到 60fps,也就是16ms每帧。

以 profile / release 模式运行

为了获取最接近生产环境的数据,我们应该选择一台尽可能低端的真机,并且以 profile 模式或者 release 模式下运行app。

  1. 因为 debug 模式会有一些额外的检查工作,比如assert()
  2. 为了加速开发效率,debug 模式是以 JIT(Just in time)模式编译 dart 代码的,而 profile 和 release 是提前编译为机器码 AOT(Ahead Of Time),所以 debug 会慢很多
  1. 在 Android Studio中, 在菜单栏中点击 Run > Flutter Run 'main.dart' in Profile Mode
  2. 在 VS Code中可以用命令行启动: $ flutter run --profile

检测帧率

Flutter 给我们提供了 Performance Overlay,有三种开启方式:

  1. 在Android Studio中: 选中 View > Tool Windows > Flutter Inspector. 点击下面这个按钮。
  2. 在 VS Code中 选中 View > Command Palette… 会显示一个 command 面板,在命令面板中输入 performance 并选择 Toggle Performance Overlay ,如果命令显示为不可用,需要检查 app 是否正在运行.
  3. 代码中打开 在MaterialApp 或者 WidgetsApp的构造函数中设置showPerformanceOverlay 属性为 true :
class  MyApp  extends  StatelessWidget {
      @override
      Widget build(BuildContext context) {
          return MaterialApp(
              showPerformanceOverlay: true, // 开启 
              title: 'My Awesome App',
              home: MyHomePage(title: 'My Awesome App'),
           );
  }
}

然后就是动手操作 app,并观察图表上是否出现红色线条。绿色代表当前渲染帧,当页面有变动,图表会不断绘制。蒙版上有2个图表,每个图表上有三横格,每个横格代表16ms。如果大多数帧都在第一格,说明达到了期望的帧率。

图表分别体现了 UI帧率 和 GPU帧率。如果出现了红色,说明对应的线程有太多work要做。那先来了解一下 Flutter 中的4个主要线程分别承担了什么职责。

  • Platform线程:插件代码运行的线程;即Android/iOS的主线程,
  • UI线程:在Dart虚拟机中执行Dart代码。作用是创建视图树,然后将它发送给GPU。注意不要阻塞此线程!
  • GPU线程:把上面提到的视图树渲染出来,虽然我们在flutter中不能直接访问GPU线程和数据,但是Dart代码可能导致此线程变慢
  • I/O线程:执行比较耗时的任务
    在运行app的过程中,观察爆红的地方和触发场景,进行分析。

分析思路

  • 如果是UI报红:那么可能是执行了某个较耗时的函数?或者函数调用过多?算法复杂度高?
  • 如果只是 GPU 报红:那么可能是要绘制的图形过于复杂?或者执行了过多GPU操作?
    • 比如要实现一个混合图层的半透明效果:如果把透明度设置在顶层控件上,CPU会把每个子控件图层渲染出来,再执行saveLayer操作保存为一个图层,最后给这个图层设置透明度。而saveLayer开销很大,这里官方给出了一个建议:首先确认这些效果是否真的有必要;如果有必要,我们可以把透明度设置到每个子控件上,而不是父控件。裁剪操作也是类似。
    • 还有一个拖慢GPU渲染速度的是没有给静态图像做缓存,导致每次build都会重新绘制。我们可以把静态图形加到RepaintBoundry控件中,引擎会自动判断图像是否复杂到需要用repaint boundary,不需要的话也会忽略。
    • 开启saveLayer和图形缓存的检查
    MaterialApp(
    showPerformanceOverlay: true,
    checkerboardOffscreenLayers: true, // 使用了saveLayer的图形会显示为棋盘格式并随着页面刷新而闪烁
    checkerboardRasterCacheImages: true, // 做了缓存的静态图片在刷新页面时不会改变棋盘格的颜色;如果棋盘格颜色变了说明被重新缓存了,这是我们要避免的
    );

提高流畅性的策略

  • 代码调用时机是否可以延后?如底部导航栏式的页面,没有必要第一次进入就把每个子Page都创建出来
  • 尽量做到局部刷新,例如globalkey的使用,参照用例Flutter局部刷新方法
  • 把耗时的计算放到独立的isolate去执行
  • 检查不必要的 saveLayer
  • 检查静态图片是否添加缓存

内存优化

在内存优化方面,我们的目标是希望减少应用内存占用,减少被系统杀死的概率,同时尽可能的避免内存泄露,减少内存碎片化。
内存占用情况检测
内存优化策略
  • 加载对象是否过大?如图片质量和尺寸不做限制就加载
  • 加载对象是否过多?如加载长列表;在调用频率很高的方法中创建对象
  • 合理设置缓存大小/长度
  • 在内存不足时或离开页面时清空缓存数据
  • 使用ListView.build()来复用子控件
  • 自定义绘图中避免在onDraw中做创建对象操作,或者相同的参数设置
  • 复用系统提供的资源,比如字符串、图片、动画、样式、颜色、简单布局,在应用中直接引用
  • 是否存在内存泄露的问题?比如dispose需要销毁的listener等
  • 不可见的视图是否也在build?
  • 页面离开后的网络请求是否取消?

参考

  • Flutter性能优化Tips

你可能感兴趣的:(Flutter性能优化初探)