前语

了解Flutter的同学应该都或多或少知道Flutter中的三棵树(Widget,Element,RenderObject),其中RenderObject负责制作逻辑,RenderObject中的paint办法类似于Android中View的Draw办法

Flutter状况更新

Flutter中按有无状况更新可分为两类:StatelessWidget(无状况)和StatefulWidget(有状况),StatefulWidget中创立一个State,在State内部调用setState,等到下一次Vsycn信号过来就会重建更新状况了。

Flutter渲染优化之RepaintBoundary
Flutter的烘托流程

在Flutter三棵树中Widget和Element的节点是一一对应,而RenderObject是少于或等于Widget的数量的。当Widget是RenderObjectWidget的派生类的时分才有对应的RenderObject。Element和RenderObject在某些条件下是能够复用的,

Flutter烘托流程

烘托的耗时包含两部分

Flutter渲染优化之RepaintBoundary

从上图能够看出Flutter制作一帧的任务会先构建三棵树然后再去制作,由于Element复用的原因所以页面改写的时分Widget和Element的生命办法并不会重复调用(解决构建耗时功能问题),可是在不使用RepaintBoundary的情况下RenderObject中的paint办法会被频频调用,接下来我们学习一下Flutter是怎样提高制作功能的。

RepaintBoundary

RepaintBoundary是集承继 SingleChildRenderObjectWidget,也属于RenderObjectWidget的派生类,所以RepaintBoundary也会有对应的RenderObject。

class RepaintBoundary extends SingleChildRenderObjectWidget {
  const RepaintBoundary({ Key? key, Widget? child }) : super(key: key, child: child);
  factory RepaintBoundary.wrap(Widget child, int childIndex) {
    final Key key = child.key != null ? ValueKey<Key>(child.key!) : ValueKey<int>(childIndex);
    return RepaintBoundary(key: key, child: child);
  }
  static List<RepaintBoundary> wrapAll(List<Widget> widgets) => <RepaintBoundary>[
    for (int i = 0; i < widgets.length; ++i) RepaintBoundary.wrap(widgets[i], i),
  ];
  @override
  RenderRepaintBoundary createRenderObject(BuildContext context) => RenderRepaintBoundary();
}

RepaintBoundary中创立的RenderObject是RenderRepaintBoundary,下面是RenderRepaintBoundary的代码

class RenderRepaintBoundary extends RenderProxyBox {
  RenderRepaintBoundary({ RenderBox? child }) : super(child);
	//isRepaintBoundary默许是回来false,RenderRepaintBoundary中回来的是true
  @override
  bool get isRepaintBoundary => true;
	//,,,省掉无关代码
}

isRepaintBoundary在RenderObject中默许是回来false,RenderRepaintBoundary中回来的是true

RenderObject中isRepaintBoundary的效果

当RenderObject中isRepaintBoundary回来时true时当时节点的RenderObject(以及子节点)的制作会在新创立Layer完结,这样就和其他Layer做了隔离,由于Layer是能够复用的,这样帧改写的时分就不需求把每个RenderObject的paint办法都履行一遍。关于Layer的介绍可参考 初识Flutter中的Layer,下面我们是看看isRepaintBoundary回来true时是怎样创立Layer的。

核心代码如下:

  void paintChild(RenderObject child, Offset offset) {
  //1,isRepaintBoundary = true
    if (child.isRepaintBoundary) {
    	//2,结束当时layer的制作
      stopRecordingIfNeeded();
     //3,
      _compositeChild(child, offset);
    } else {
      child._paintWithContext(this, offset);
    }
  }
  	//3,组成child
    void _compositeChild(RenderObject child, Offset offset) {
    // Create a layer for our child, and paint the child into it.
    if (child._needsPaint) {
    	//4,假如child需求被制作(_needsPaint=true代表当时节点或许当时节点子孩子被PipelineOwer符号出需求被重绘)
      repaintCompositedChild(child, debugAlsoPaintedParent: true);
    } else {
    }
    final OffsetLayer childOffsetLayer = child._layer! as OffsetLayer;
    childOffsetLayer.offset = offset;
    appendLayer(child._layer!);
  }
  	//4,
    static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
  	//重新制作
    _repaintCompositedChild(
      child,
      debugAlsoPaintedParent: debugAlsoPaintedParent,
    );
  }
    static void _repaintCompositedChild( RenderObject child, {bool debugAlsoPaintedParent = false,
    PaintingContext? childContext,}) {
    OffsetLayer? childLayer = child._layer as OffsetLayer?;
    if (childLayer == null) {
      child._layer = childLayer = OffsetLayer();
    } else {
      childLayer.removeAllChildren();
    }
	 //创立新的PaintingContext,新的PaintingContext会创立新的PictureLayer
    childContext ??= PaintingContext(child._layer!, child.paintBounds);
    child._paintWithContext(childContext, Offset.zero);
    childContext.stopRecordingIfNeeded();
  }

流程图如下:

Flutter渲染优化之RepaintBoundary

从上述流程能够看出当isRepaintBoundary=false时,就会触发paint的办法,我们假定下图一切RenderObject的isRepaintBoundary=false且其中RenderObject4被符号需求改写

Flutter渲染优化之RepaintBoundary

RenderObject4会自下而上寻觅自己的父亲节点,直到找到父节点为isRepaintBoundary=true为止,然后把父节点顺次符号需求改写(_needsPaint = true),如下图所示:

Flutter渲染优化之RepaintBoundary

找到最顶层的父节点,然后履行paint办法,最终的成果就是遍历履行了一切RenderObject的paint办法,如下图所示:

Flutter渲染优化之RepaintBoundary

假如RenderObject4的上一级父节点就是isRepaintBoundary=true,那么流程就如下

Flutter渲染优化之RepaintBoundary

寻觅父节点isRepaintBoundary=true

Flutter渲染优化之RepaintBoundary

假如RenderObject4被符号需求改写,RenderObject1和RenderObject4需求履行paint办法:

Flutter渲染优化之RepaintBoundary

假如是RenderObject2,RenderObject3,RenderObject5,RenderObject6,RenderObject7中有一个需求改写,右边标色彩的节点会履行paint办法

Flutter渲染优化之RepaintBoundary

综上流程分析,假定场景是RenderObject4的制作很耗可是是改写不频频,RenderObject5,RenderObject6,RenderObject7的改写很频频,我们使用RepaintBoundary对RenderObject4对应的Widget包一层这样能够缩短烘托时制作阶段的耗时然后下降卡顿问题。

使用到RepaintBoundary的当地

在Flutter framework中的有些Widget就使用到RepaintBoundary了

Flowl

流式布局每个child都是独立的layer烘托

  Flow({
    Key? key,
    required this.delegate,
    List<Widget> children = const <Widget>[],
    this.clipBehavior = Clip.hardEdge,
  }) : assert(delegate != null),
       assert(clipBehavior != null),
       super(key: key, children: RepaintBoundary.wrapAll(children));

RepaintBoundary中的源码:

 factory RepaintBoundary.wrap(Widget child, int childIndex) {
    assert(child != null);
    final Key key = child.key != null ? ValueKey<Key>(child.key!) : ValueKey<int>(childIndex);
    return RepaintBoundary(key: key, child: child);
  }
  /// Wraps each of the given children in [RepaintBoundary]s.
  ///
  /// The key for each [RepaintBoundary] is derived either from the wrapped
  /// child's key (if the wrapped child has a non-null key) or from the wrapped
  /// child's index in the list.
  static List<RepaintBoundary> wrapAll(List<Widget> widgets) => <RepaintBoundary>[
    for (int i = 0; i < widgets.length; ++i) RepaintBoundary.wrap(widgets[i], i),
  ];
SliverChildBuilderDelegate

SliverChildBuilderDelegate这个是ListView.builder的时分内部会创立SliverChildBuilderDelegate,列表大量item彼此之间独立layer烘托

  @override
  Widget? build(BuildContext context, int index) {
    assert(builder != null);
    if (addRepaintBoundaries)
      child = RepaintBoundary(child: child);
    if (addSemanticIndexes) {
      final int? semanticIndex = semanticIndexCallback(child, index);
      if (semanticIndex != null)
        child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
    }
    if (addAutomaticKeepAlives)
      child = AutomaticKeepAlive(child: child);
    return KeyedSubtree(child: child, key: key);
  }

总结

以上是对RepaintBoundary的效果分析,期望经过此篇文章帮助到我们提高的对Flutter烘托机制的认识。