1. Flutter 性能概述
1.1 Flutter 基本渲染原理
在我们讨论如何对 Flutter 进行性能优化之前,首先得掌握 Flutter 的渲染原理,这样才能更好的对症下药。本文将主要讲讨论 UI 线程中的性能优化,由于 GPU 线程涉及底层 Skia 图形引擎的调用,相较于 UI 线程而言更加繁琐,对其感兴趣的同学可以观看 Google 官方的《深入了解 Flutter 的高性能图形渲染》。
根据渲染流程图,我们可知 Flutter 的主要渲染流程:在初次渲染时,我们会根据我们自己的业务代码,分别构建 Widget、 Element 以及 RenderObject 三棵树,其次对 RenderObjective Tree 的每个节点进行遍历 ,再对发生改变的节点处进行标脏处理,执行 paint 操作,形成一个 Layer Tree,最后把形成好的 Layer Tree 发送给 GPU 线程,GPU 线程 在接收到 Layer Tree 之后,将 Layer Tree 转成为 GPU 的可执行指令。
1.2 Flutter 性能调试
我们在命令行中输入flutter run --profile
的指令,即可在 profile 模式下对我们的应用进行调试。这里推荐使用 appuploader 工具来辅助调试,它可以帮助开发者快速打包和上传测试版本,方便性能测试。
UI 线程和 GPU 线程观测所进行的操作是不同的:
- UI 线程:通过 timeline 进行调试,Record Streams Profile 值选择 Flutter Developer 即可
- GPU 线程:输入
flutter run --profile --trace-skia
运行应用,Record Streams Profile 的值换成 All
1.3 为什么我们说 Flutter 是高性能前端框架?
Flutter 框架可以直接调用 Skia 图形引擎,这也是 Flutter 性能能够媲美原生的重要原因;而不是像 react-native 那样需要先通过 JSBridge 调用 Java 代码,然后再通过 Java 代码去调用 Skia 图形引擎。
2 build 阶段的性能优化
2.1 build 更新具体过程
Element Tree 中的 Element 主要涉及到两种类型:
- ComponentElement
- RenderObjectElement
2.2 如何提高 build 的效率
我们提高 build 效率的核心本质是:
- 降低我们开始遍历的节点
- 提前结束树的遍历
在具体的实际业务开发中,我们可以在代码的任意处加上debugProfileBuildsEnabled = true
,这可以帮助我们通过 timeline 发现 build 过程中的具体性能瓶颈。
2.3 具体优化方法
2.3.1 降低开始遍历的节点
通过将 Widget 的粒度细化,能够有效地降低遍历的起点位置。当 Widget 数过于复杂时,我们应该尽量将 Widget 抽离出来,单个 Widget 树最好不要太多。
2.3.2 提前结束子树的遍历
在使用 Provider 的 Selector 类时,其 build 的 child 参数就是通过提前结束子树的遍历来进行性能优化的。
3. layout 阶段的性能优化
3.1 layout 的具体过程
layout 的过程主要是为了计算出节点真正所占的大小。在建立 layout tree 的过程中,首先父节点会给出一个宽高大小的限制,然后子节点再来决定自己的大小。
3.2 具体代码演示
在使用 ListView 这样的滑动组件时,我们应该给出滑块的高度,即 itemExtend 的值,这样在滑动的时候,UI 线程不会花费大量的时间在计算高度上。
4. paint 阶段的性能优化
4.1 paint 的具体过程
在 RenderObject 标脏后,paint 会对已经标脏的 RenderObject 图层重新进行绘制。这里和 Layout 相似,存在一个 Repaint boundary 的概念。
4.2 具体代码演示
如果页面是频繁更新的页面,例如包含定时器的页面,在使用倒计时这样的控件时,我们可以在最小控件范围外包一层 RepaintBoundary 来与周围图层进行隔离。
写在最后
Flutter 性能优化涉及到方方面面,本文从渲染原理的角度进行切入讲解其优化手段。在实际开发中,可以使用 appuploader 这样的工具来辅助性能测试和优化,它能帮助开发者快速打包和上传测试版本,方便进行性能分析和优化。
对于 Opacity 这样大量消耗性能的 Widget 最好尽量少用,因为它会调用saveLayer()
方法,这个方法它会很大程度上影响 GPU 线程的效率。