flutter应用性能优化最佳实践

11 阅读6分钟

文章参考:docs.flutter.cn/perf/best-p…

  1. 控制 build() 方法的耗时

    1. 避免在 build() 方法中进行重复且耗时的工作,因为当父 widget 重建时,子 Wdiget 的 build() 方法会被频繁地调用。

      • 网络请求 fetchData()
      • 频繁解析JSON jsonDecode(hugeJsonString);
      • 复杂计算,频繁创建大对象和集合 List.generate(10000, (index) => index);
    2. 避免在一个超长的 build() 方法中返回一个过于庞大的 widget。把它们分拆成不同的 widget,并进行封装

      • 局部刷新: 当在 State 对象上调用 setState()时,所有后代 widget 都将重建。因此, setState() 的调用转移到其 UI 实际需要更改的 widget 子树部分。 如果改变的部分仅包含在 widget 树的一小部分中,请避免在 widget 树的更高层级中调用 setState()

        • 将状态推送到叶子节点。 例如,如果你的页面上有一个滴答作响的时钟,与其将状态放在页面顶部,并在时钟每次滴答作响时重建整个页面,不如创建一个专门的时钟小部件,使其仅更新自身。
      • Child缓存:当重新遇到与前一帧相同的子 widget 实例时,将停止遍历。这种技术在框架内部大量使用,用于优化动画不影响子树的动画。请参阅 TransitionBuilder 模式,理解把Child缓存起来。

        • 如果子树没有变化,则缓存代表该子树的 Widget,并在每次可以使用时重用它。 为此,请将 Widget 分配给final状态变量,并在 build 方法中重用它。重用 Widget 比创建一个新的(但配置相同的)Widget 效率更高。另一种缓存策略是将 Widget 的可变部分提取到 接受 child 参数的StatefulWidget中。
      • 尽可能使用 const 小部件。(这相当于缓存小部件并重复使用。)这将让 Flutter 的 widget 重建时间大幅缩短。要自动提醒使用 const

      • 在构建可复用的 UI 代码时,最好使用 StatelessWidget 而不是函数。

      • 最小化重建有状态小部件

        • 尽量减少 build 方法及其创建的任何 Widget 所传递创建的节点数量。 理想情况下,有状态 Widget 只会创建一个 Widget,并且该 Widget 应该是一个RenderObjectWidget。(当然,这并不总是可行的,但 Widget 越接近理想状态,效率就越高。)
        • 避免更改任何已创建子树的深度,或更改子树中任何控件的类型。 例如,与其返回子控件本身或包装在IgnorePointer中的子控件,不如始终将子控件包装在IgnorePointer中并控制IgnorePointer.ignoring 属性。这是因为更改子树的深度需要重建、布局和绘制整个子树,而仅更改属性则对渲染树的更改尽可能少(例如,对于IgnorePointer,根本不需要布局或重绘)。
        • 如果由于某种原因必须更改深度,请考虑将子树的公共部分包装到具有 GlobalKey 的 Widget 中,该 GlobalKey 在有状态 Widget 的整个生命周期内保持一致。
  2. 谨慎使用 saveLayer()

    1. 为什么代价会大?触发了离屏渲染
    2. 为什么时候需要?在运行时,如果你需要动态地显示各种形状效果(例如),每个形状都有一定地透明度,可能(或可能不)重叠,那么你几乎必须使用 saveLayer()
    3. 尽量减少 saveLayer 的调用
  3. 尽量减少使用不透明度和裁剪

    1. 使用透明的颜色比透明的Opacity更快:

      • 例如,Container(color: Color.fromRGBO(255, 0, 0, 0.5))比快得多Opacity(opacity: 0.5, child: Container(color: Colors.red))
    2. Clipping 不会调用 saveLayer() (除非明确使用 Clip.antiAliasWithSaveLayer),因此这些操作没有 Opacity 那么耗时,但仍然很耗时,所以请谨慎使用。

    3. 能不用 Opacity widget,就尽量不要用。有关将透明度直接应用于图像的示例,请查看 Transparent image,这比使用 Opacity widget 更快。

    4. 要在图像中实现淡入淡出,请考虑使用 FadeInImage widget,该 widget 使用 GPU 的片段着色器应用渐变不透明度。

    5. 要创建带圆角的矩形,而不是裁剪矩形来达到圆角的效果,请考虑使用很多 widget 都提供的 borderRadius 属性。

    6. 陷进:

      • 避免使用 Opacity widget,尤其是在动画中避免使用。可以使用 AnimatedOpacityFadeInImage 代替该操作。
      • 使用 AnimatedBuilder 时,请避免在不依赖于动画的 widget 的构造方法中构建 widget 树,不然,动画的每次变动都会重建这个 widget 树,应当将这部分子树作为 child 传递给 AnimatedBuilder,从而只构建一次。更多内容
      • 避免在动画中裁剪,尽可能的在动画开始之前预先裁剪图像。
      • 避免在 Widget 对象上重写 operator ==。虽然这看起来有助于避免不必要的重建,但在实践中,它实际上损害了性能,因为这是 O(N²) 的行为。比较 widget 的属性可能比重建 widget 更加有效,也能更少改变 widget 的配置。即使在这种情况下,最好还要缓存 widget,因为哪怕有一次对 operator == 进行覆盖也会导致全面性能的下降,编译器也会因此不再认为调用总是静态的
  4. 谨慎使用网格列表和列表

    1. 懒加载:如果大多数 children widget 在屏幕上不可见,请避免使用返回具体列表的构造函数(例如 Column()ListView()),以避免构建成本。使用ListView.build()
    2. 不要使用shrinkWrap:当列表数据超过100条,就会卡顿。内部组件列表从 ListView 改为 SliverList,用 SliverChildBuilderDelegate 委托
  5. 避免内部传递

    1. 不要先算子widget的大小,再去计算父组件的高度。例如轮询计算高度

      1. 例如,你想要所有单元格都具有或大或小的效果 (或类似需要轮询所有单元格的计算) 时,就会发生内部传递。
      2. 例如,考虑一个大型的 卡片 网格列表时。一个网格列表应该有统一大小的单元格,所以布局代码执行了一次传递,从网格列表的根部开始(在 widget 树中),要求网格列表中的 每个 卡片(不仅仅是可见的卡片)来返回 内部 尺寸—假设没有任何限制,widget 更喜欢这样的尺寸。有了这些信息,底层框架就确定了一个统一的单元格尺寸,并再次重新访问所有的网格单元,告诉每个卡片应该使用什么尺寸。
  6. 隔离重绘区域:自定义的绘制使用RepaintBoundary包裹