最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • flutter 渲染管道是如何优化的

    正文概述 掘金(罪鱼千百年)   2021-03-02   683

    flutter 渲染管道

    首先从一段代码开始(flutter/rendering/binding.dart):

    void drawFrame() {
        assert(renderView != null);
        pipelineOwner.flushLayout();
        pipelineOwner.flushCompositingBits();
        pipelineOwner.flushPaint();
        if (sendFramesToEngine) {
          renderView.compositeFrame();  
          pipelineOwner.flushSemantics();
          _firstFrameSent = true;
        }
      }
    

    这是 flutter 渲染管道的流程,也就是:

    graph LR
    A[flushLayout] --> B(flushCompositingBits) --> C[flushPaint]
    

    这是我们现在最关心的几个阶段。 单击方法查看对应源码,其实都很相似。

    flushLayout

    flutter/rendering/Object.dart#PipeLineOwner:

     List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
     	 ... (省略)
    	 while (_nodesNeedingLayout.isNotEmpty) {
           final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
           _nodesNeedingLayout = <RenderObject>[];
           for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
              if (node._needsLayout && node.owner == this)
                node._layoutWithoutResize();
            }
          }
         ...
    

    也就是说要重新布局的RenderObject ,是要先添加到这个 List 中才行,而 markNeedsLaout 正是执行此操作的方法,flutter/rendering/Object.dart#RenderObject.markNeedsLaout(简化):

    ...
     if (_relayoutBoundary != this) {
          markParentNeedsLayout();
        } else {
          _needsLayout = true;
          if (owner != null) {
            owner!._nodesNeedingLayout.add(this);
            owner!.requestVisualUpdate();
          }
        }
    ...
    

    如果_relayoutBoundary 不是对象本身而是parent,那就执行markParentNeedsLayout,最后调用parent.markNeedsLayout(),这是在确定由哪一方决定布局的;

    _relayoutBoundary

    _relayoutBoundary 是如何确定的呢,在layout方法中,是由几个条件变量确定的; flutter/rendering/Object.dart#RenderObject.layout(简化):

    ...
      RenderObject? relayoutBoundary;
      if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
        relayoutBoundary = this;
      } else {
        relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
      }
      if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
        return;
      }
      _constraints = constraints;
      if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
        visitChildren(_cleanChildRelayoutBoundary);
      }
      _relayoutBoundary = relayoutBoundary; // 确定了_relayoutBoundary
      if (sizedByParent) {
        try {
          performResize();
        } catch (e, stack) {
          _debugReportException('performResize', e, stack);
        }
      }
      RenderObject? debugPreviousActiveLayout;
      try {
        performLayout();
        markNeedsSemanticsUpdate();
      } catch (e, stack) {
        _debugReportException('performLayout', e, stack);
      }
      _needsLayout = false;
      markNeedsPaint(); // 布局之后是要重新绘制的
    ...
    

    布局方面的优化几乎都在这里说明了:

    • parentUsesSize

    • sizedByParent

    • constraints.isTight

    // Whether the constraints are the only input to the sizing algorithm (in
    // particular, child nodes have no impact).
    //
    // Returning false is always correct, but returning true can be more
    // efficient when computing the size of this render object because we don't
    // need to recompute the size if the constraints don't change.
    //
    // Typically, subclasses will always return the same value. If the value can
    // change, then, when it does change, the subclass should make sure to call
    // [markNeedsLayoutForSizedByParentChange].
    //
    // Subclasses that return true must not change the dimensions of this render
    // object in [performLayout]. Instead, that work should be done by
    // [performResize] or - for subclasses of [RenderBox] - in
    // [RenderBox.computeDryLayout].
    @protected
    bool get sizedByParent => false;
    

    当要重新设计RenderObject时,可以重写sizeByParent。 当表达式返回true时,_relayoutBoundary 为它本身,并且size(RenderBox)是固定的,除非重新布局,这是因为size是要在performResizecomputeDryLayout设置,并且不应在performLayout中设置size,这是因为重新布局时,只会调用performLayout

    flushCompositingBits

    示例:

    context.pushClipRect(needsCompositing, offset, Offset.zero & size,
    	defaultPaint, oldLayer: _clipRectLayer);
    

    调用markNeedsCompositingBitsUpdaterenderObject添加到_nodesNeedingCompositingBitsUpdate,如果needsCompositing发生改变时,将会调用markNeedsPaint; 这个步骤是合成的关键,如果我们深入探索的话,就会发现都是在layer上绘画的。

    flushPaint

    先看markNeedsPaint的源码,flutter/rendering/Object.dart#RenderObject.markNeedsPaint(简化):

    ...
    if (isRepaintBoundary) {
      if (owner != null) {
        owner!._nodesNeedingPaint.add(this);
        owner!.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      final RenderObject parent = this.parent! as RenderObject;
      parent.markNeedsPaint();
    } else {
      if (owner != null)
        owner!.requestVisualUpdate();
    }
    ...
    

    当调用renderObjectmarkNeedsPaint时,会检查isRepaintBoundary的值,如果为true,parent不用重新绘制,也就是把本身标记为dirtyNodes,从而在渲染管道中被处理。

    再从flutter/rendering/Object.dart#PipelineOwner.flushPaint 跟踪代码到

    child._paintWithContext(childContext, Offset.zero);
    

    _paintWithContext内部调用在RenderObject中重写的paint方法,这里就要说一下PaintingContext,在绘制child时是要调用context.paintChild的;

    flutter/rendering/Object.dart#PaintingContext.paintChild:

    ...
    void paintChild(RenderObject child, Offset offset) {
      if (child.isRepaintBoundary) {
      stopRecordingIfNeeded();
      _compositeChild(child, offset);
      } else {
      child._paintWithContext(this, offset);
      }
      ...
    }
    ...
    void _compositeChild(RenderObject child, Offset offset) {
      if (child._needsPaint) {
        repaintCompositedChild(child, debugAlsoPaintedParent: true);
      } else {
      ...
      }
    }
    ...
    

    现在如果我们把它们联系起来,就会发现当parent需要重新绘制时,但是child.isRepaintBoundarytrue,此时只有child._needsPainttrue时,child才会重新绘制,这也就是为什么其他Widget有时要用RepaintBoundary包裹起来的原因。


    起源地下载网 » flutter 渲染管道是如何优化的

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元