Flutter 源码梳理系列(二十四):PipelineOwner

252 阅读16分钟

 首先我们要有一个意识哦:经过前面学习脏 Element 列表中所有脏 Element 重建的过程,我们知道 Element 重建并不是在当前帧进行的,而是在当前帧会把所有的脏 Element 对象收集起来,然后再通过 WidgetsBinding.drawFrame 的回调,在下一帧然后进行批量重建。

 下面是 WidgetsBinding.drawFrame 函数的内容:

@override
  void drawFrame() {
    try {
      // 首先是对 Element Tree 的根节点 rootElement 下的 BuildScope 进行重建。
      // 此 BuildScope 即 Element Tree 上所有 Element 节点共用的 build scope。
      // 对其中的所有脏 Element 节点进行排序重建。
      if (rootElement != null) {
        buildOwner!.buildScope(rootElement!);
      }
      
      // ⬇️⚠️ 这里便是调用 RendererBinding.drawFrame 函数。
      super.drawFrame();
      
      // Element Tree 中脏 Element 重建完毕后,
      // 对剩余的非活动 Element 节点调用 unmount 进行卸载回收,
      // 它们已经完全不会再被用到了,卸载即可。
      buildOwner!.finalizeTree();
    } finally {
      // ...
    }
  }

 注意到其中的:super.drawFrame() 调用,其实就是调用的:RendererBinding.drawFrame,然后在其内部调用 flushLayout、flushCompositingBits、flushPaint 函数对需要重新布局的、合成更新的、重新绘制的 RenderObject 对象进行批量处理。

 是的,RenderObject 的重新布局和重新绘制等操作是和脏 Element 的重建是一样的,都是在当前帧进行统一收集,然后在下一帧进行批量处理。

 在涉及脏 Element 节点处理重建的时候,我们学习了 BuildOwner,它来负责收集整理标记为脏的 Element 对象。那么到了 RenderObject 这里,是谁来管理这个过程的呢?是的,没错,正是 PipelineOwner,下面我们对 PipelineOwner 的内容进行集中学习。

PipelineOwner

 PipelineOwner 负责管理渲染管线(rendering pipeline)。

 PipelineOwner 提供了一个接口来驱动渲染管线,并存储关于在管线的每个阶段中请求被访问的 RenderObject 的状态。要刷新管线,请按照以下顺序调用这些函数:(即 PipelineOwner 的三个属性:List<RenderObject> _nodesNeedingLayoutList<RenderObject> _nodesNeedingPaintfinal Set<RenderObject> _nodesNeedingSemantics 它们负责存储需要重新布局、需要重新绘制、需要重新语义化的 RenderObject。然后是刷新管线的如下顺序调用的三个函数正是在 RendererBinding.drawFrame 中调用的。RendererBinding.drawFrame 函数如下:)

 RendererBinding.drawFrame 函数内正是下面👇四个函数的调用顺序。

  @protected
  void drawFrame() {
    // 1️⃣ flushLayout
    rootPipelineOwner.flushLayout();
    
    // 2️⃣ flushCompositingBits
    rootPipelineOwner.flushCompositingBits();
    
    // 3️⃣ flushPaint
    rootPipelineOwner.flushPaint();
    
    if (sendFramesToEngine) {
      for (final RenderView renderView in renderViews) {
        // this sends the bits to the GPU
        renderView.compositeFrame();
      }
      
      // this sends the semantics to the OS.
      // 4️⃣ flushSemantics
      rootPipelineOwner.flushSemantics(); 
      
      _firstFrameSent = true;
    }
  }
  1. flushLayout 更新任何需要计算其布局的 RenderObject 对象。在此阶段,计算每个 RenderObject 对象的大小和位置。在这个阶段,RenderObject 对象可能会在其绘制(painting)或合成状态(compositing)中标记为脏。

  2. flushCompositingBits 更新具有脏混合位的任何 RenderObject 对象。在此阶段,每个 RenderObject 对象都会了解其子级是否需要混合。在绘制阶段使用此信息来选择如何实现诸如裁剪之类的视觉效果(看到这里想到了 iOS 的离屏渲染绘制圆角、阴影、mask 等。)。如果一个 RenderObject 对象具有一个合成子级,它需要使用一个 Layer 来创建 clip,以便 clip 应用到合成子级上(后者将绘制到自己的 Layer 中)。

  3. flushPaint 会访问需要绘制的任何 RenderObject 对象。在此阶段,RenderObject 对象有机会将绘制命令记录到 PictureLayer 中,并构造其他合成 Layer。

  4. 最后,如果启用了语义化,flushSemantics 将为 RenderObject 对象编译语义。这些语义信息被辅助技术用于改善 Render Tree 的可访问性。

 RendererBinding 持有屏幕上可见的渲染对象的 PipelineOwner。你可以创建其他 PipelineOwner 来管理屏幕外(off-screen)的对象,这些对象可以独立于屏幕上(on-screen)的渲染对象刷新它们的管线。(即 RendererBinding 的 rootPipelineOwner 属性,Render Tree 上的所有 RenderObject 节点的 PipelineOwner? _owner 属性都指向它。)

 PipelineOwner 可以按树状结构组织,用于管理多个 Render Tree,其中每个 PipelineOwner 负责一个 Render Tree。要构建或修改 PipelineOwner Tree,请调用 adoptChild 或 dropChild。在上面描述的不同 flush 阶段中,PipelineOwner 在调用其子级的相同 flush 方法之前,将首先在其自己的 Render Tree 中管理的 RenderObject 节点上执行该阶段。不应该假设子级 PipelineOwner 的 flush 顺序。(即 PipelineOwner 的 flush 一组函数中,首先要调用自己的收集的 RenderObject 的刷新,然后才是 PipelineOwner 子级的刷新。)

 一个 PipelineOwner 也可以附加(调用:void attach(PipelineManifold manifold) 函数。)到一个 PipelineManifold,这样它就可以访问通常由 binding 暴露的平台功能,而不必将其绑定到特定的 binding 实现。在给定 PipelineOwner Tree 中的所有 PipelineOwner 节点必须附加到相同的 PipelineManifold。这在 adoptChild 中会自动发生。(即 PipelineOwner Tree 中所有的 PipelineOwner 节点的 PipelineManifold? _manifold 属性都指向同一个 PipelineManifold 对象。)

 OK,下面看一下 PipelineOwner 的源码。

Constructors

 创建一个 PipelineOwner。通常由 binding(例如 RendererBinding)创建,但也可以独立于 binding 创建,以通过 rendering pipeline 驱动屏幕外(off-screen)渲染对象。

 参数是四个回调函数。

class PipelineOwner with DiagnosticableTreeMixin {
  PipelineOwner({
    this.onNeedVisualUpdate,
    this.onSemanticsOwnerCreated,
    this.onSemanticsUpdate,
    this.onSemanticsOwnerDisposed,
  }){
    // ...
  }
  
  // ...
}

onNeedVisualUpdate

 每当与此 PipelineOwner 相关联的 RenderObject 对象希望更新其可视外观时调用。

 这个函数的典型实现会安排一个任务来刷新管线的各个阶段。这个函数可能会被快速连续地调用多次。实现应该注意迅速丢弃重复调用。

 当 PipelineOwner 附加到 PipelineManifold 并提供 onNeedVisualUpdate 时, 将调用 onNeedVisualUpdate 回调,而不是调用 PipelineManifold.requestVisualUpdate。

  final VoidCallback? onNeedVisualUpdate;

onSemanticsOwnerCreated

 每当该 PipelineOwner 创建语义(semantics)对象时调用。典型的实现将安排创建初始语义树(initial semantics tree)。(看到此回调执行时会调用:RenderObject.scheduleInitialSemantics 函数。)

  final VoidCallback? onSemanticsOwnerCreated;

onSemanticsUpdate

 每当这个 PipelineOwner 的语义所有者(semantics owner)发出 SemanticsUpdate 时都会调用。

 典型的实现方式会将 SemanticsUpdate 委托给一个能够处理 SemanticsUpdate 的 FlutterView。(看到此回调执行时会调用:RenderView.updateSemantics 函数。)

  final SemanticsUpdateCallback? onSemanticsUpdate;

onSemanticsOwnerDisposed

 每当这个 PipelineOwner 销毁其语义所有者(semantics owner)时都会调用此方法。典型的实现将拆除语义树(semantics tree)。(看到此回调执行时会调用:RenderObject.clearSemantics 函数。)

  final VoidCallback? onSemanticsOwnerDisposed;

requestVisualUpdate

 如果 onNeedVisualUpdate 不为 null,则调用 onNeedVisualUpdate。用于通知 PipelineOwner,关联的 RenderObject 希望更新其视觉外观。

  void requestVisualUpdate() {
    if (onNeedVisualUpdate != null) {
      onNeedVisualUpdate!();
    } else {
      // 正常情况下会调用这里。
      _manifold?.requestVisualUpdate();
    }
  }

rootNode

 由该 PipelineOwner 管理且没有父级(parent 指向为 null)的唯一对象(RenderObject)。(即:当前 PipelineOwner 管理的 Render Tree 的根节点。)

  RenderObject? get rootNode => _rootNode;
  RenderObject? _rootNode;
  
  set rootNode(RenderObject? value) {
    if (_rootNode == value) {
      return;
    }
    
    // 首先进行一个清理释放之前的引用后,再进行新的赋值。
    // 内部时把之前的旧 _rootNode 引用的 owner 置为 null,
    // 即之前的旧 _rootNode 不再引用当前的 PipelineOwner 对象了。
    _rootNode?.detach();
    
    // 然后把新的 RenderObject 对象赋值给当前 PipelineOwner 对象的 rootNode 属性
    _rootNode = value;
    
    // 把 _rootNode 的 owner 属性指向当前这个 PipelineOwner 对象
    _rootNode?.attach(this);
  }

_shouldMergeDirtyNodes

 在继续处理 dirty relayout boundaries(脏重新布局边界)之前,当前的 flushLayout 调用是否应该暂停以将 _nodesNeedingLayout 中的 RenderObject 合并到当前的脏列表中。(具体细节在 flushLayout 函数内,打断当前 for 循环,重新进入 while 循环。)

 当一个 RenderObject.invokeLayoutCallback 返回时(就表示有新的标记为需要重新布局的 RenderObject 对象添加到了 _nodesNeedingLayout 列表中),这个标志被设置为 true,以避免以错误的顺序布局 dirty relayout boundaries(脏重新布局边界),并导致它们在一个帧内被布局多次。

 在调用 RenderObject.invokeLayoutCallback 后,新的脏节点不会立即合并,因为在处理单个 relayout boundary(重新布局边界)时,可能会遇到多次这样的调用。批量处理新的脏节点可以减少 flushLayout 需要执行的合并次数。

  bool _shouldMergeDirtyNodes = false;

_nodesNeedingLayout

 超级重要的一个 List,在一个批次内所有的位于同一个 PipelineOwner 中需要被重新布局的 RenderObject 都被收集在这里。

  List<RenderObject> _nodesNeedingLayout = <RenderObject>[];

flushLayout

 更新所有需要重新渲染的渲染对象的布局信息。(_nodesNeedingLayout 列表中收集的。)

 这个函数是渲染流水线(rendering pipeline)中的核心阶段之一。在绘制之前,布局信息会被清理,以确保渲染对象以其最新位置显示在屏幕上。

 可以查看 RendererBinding 以了解此函数的使用示例。(即在 RendererBinding.drawFrame 函数中被调用,在新的一帧中刷新渲染对象的布局信息。)

  void flushLayout() {
    try {
      while (_nodesNeedingLayout.isNotEmpty) {
        // 临时变量,记录当前需要重新布局的脏 RenderObject 对象
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        
        // 把当前 PipelineOwner 下的,记录脏 RenderObject 对象的列表置空,
        // 后续有新的脏 RenderObject 的话还会被添加进来。
        _nodesNeedingLayout = <RenderObject>[];
        
        // 还记得在 BuildOwner 中记录的所有的标记为脏的 Element,
        // 它们在统一进行重建之前会进行排序吗?父级排在子级前面去。
        
        // 这里也一样,对脏 RenderObject 对象根据它们的 depth 值进行排序,
        // 把 depth 小的排在前面,即父级 RenderObject 在子级 RenderObject 前面去,
        // 防止子级 RenderObject 被重叠布局。
        dirtyNodes.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
        
        // 遍历已排序的 dirtyNodes 中记录的脏 RenderObject 对象,
        // 对它们执行重新布局 
        for (int i = 0; i < dirtyNodes.length; i++) {
        
          // 如果 _shouldMergeDirtyNodes 为 true 表示有新的 RenderObject 被加入到 _nodesNeedingLayout 中了,
          // 当前正在遍历 dirtyNodes 中脏 RenderObject 进行重新布局过程中,
          // 又有新的脏 RenderObject 被加入到 _nodesNeedingLayout 中的话,
          // 那么把 dirtyNodes 中剩余的未重新布局完成的脏 RenderObject 全部转移到 _nodesNeedingLayout 中,
          // 并跳出当前 for 循环,重新最外层的 while 循环,对所有的脏 RenderObject 对象进行重新布局。
          if (_shouldMergeDirtyNodes) {
            _shouldMergeDirtyNodes = false;
            
            // 在依次对现有的脏 RenderObject 进行重新布局时,
            // 又有新的 RenderObject 被标记需要重新布局,
            // 它们被添加进 _nodesNeedingLayout 中
            if (_nodesNeedingLayout.isNotEmpty) {
            
              // 把 dirtyNodes 中剩余的未来的及重新布局的脏 RenderObject 添加到 _nodesNeedingLayout 中去
              _nodesNeedingLayout.addAll(dirtyNodes.getRange(i, dirtyNodes.length));
              
              // 然后跳出这个 for 循环,回到前面的 while 循环
              break;
            }
          }
          
          // 每次取出一个脏 RenderObject,对它进行重新布局
          final RenderObject node = dirtyNodes[i];
          
          // 这里的 node._needsLayout 判断并不是多余,
          // 防止子级已经跟随父级重新 layout 过了,这里再重复 layout,
          // 因为当父级 layout 时,是会对它下面的 RenderObject 子树上的所有节点进行 layout 的。
          if (node._needsLayout && node.owner == this) {
            // 执行 RenderObject._layoutWithoutResize 函数。
            // 执行 performLayout 执行布局,
            // 并被标记需要语义更新,并被标记需要重绘,
            // 并把 _needsLayout 置为 false,表示当前不再需要被重新布局了
            node._layoutWithoutResize();
          }
        }
        
        // 不需要将上一次 relayout boundary 处理后新生成的脏 RenderObject 进行合并
        _shouldMergeDirtyNodes = false;
      }
      
      // 对当前 PipelineOwner 中的子级 PipelineOwner 进行重新布局刷新。
      for (final PipelineOwner child in _children) {
        child.flushLayout();
      }
      
    } finally {
      // 脏 RenderObject 合并标识置为 false 
      _shouldMergeDirtyNodes = false;
    }
  }

_nodesNeedingCompositingBitsUpdate

 超级重要的一个 List,同 _nodesNeedingLayout

 在一个批次内所有的位于同一个 PipelineOwner 中需要进行合成更新的 RenderObject 都被收集在这里。

  final List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];

flushCompositingBits

 更新 RenderObject.needsCompositing bits。在 flushLayout 之后和 flushPaint 之前作为渲染流水线(rendering pipeline)的一部分被调用。

  void flushCompositingBits() {
    // 同样的首先进行排序,父级在前面子级在后面
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    
    // 遍历对所有需要合成更新的 RenderObject 进行合成更新
    for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      
      // 同上面,
      // node._needsCompositingBitsUpdate 判断,防止子级被重复进行合成位更新操作。
      if (node._needsCompositingBitsUpdate && node.owner == this) {
        // 执行合成更新
        node._updateCompositingBits();
      }
    }
    
    // 合成更新完毕了,可以清空 _nodesNeedingCompositingBitsUpdate 列表了
    _nodesNeedingCompositingBitsUpdate.clear();
    
    // 对当前 PipelineOwner 的子级 PipelineOwner 进行合成更新刷新。
    for (final PipelineOwner child in _children) {
      child.flushCompositingBits();
    }
  }

_nodesNeedingPaint

 超级重要的一个 List,同 _nodesNeedingLayout

 在一个批次内所有的位于同一个 PipelineOwner 中需要被重新绘制的 RenderObject 都被收集在这里。

  List<RenderObject> _nodesNeedingPaint = <RenderObject>[];

flushPaint

 更新所有渲染对象的显示列表(display lists)。

 该函数是渲染流水线(rendering pipeline)的核心阶段之一。绘制发生在布局之后、场景重新组合之前,以便将场景与每个渲染对象的最新显示列表(up-to-date display lists)一起合成。

 可以查看 RendererBinding 以了解此函数的使用示例。(即在 RendererBinding.drawFrame 函数中被调用,在新的一帧中刷新渲染对象的绘制信息,且调用位置在 flushLayout、flushCompositingBits 后面。)

  void flushPaint() {
    try {
      // 临时变量记录下当前需要进行重新绘制的 RenderObject
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      
      // 把 _nodesNeedingPaint 置为空,可以开始记录下一个批次的需要重新绘制的渲染对象了
      _nodesNeedingPaint = <RenderObject>[];

      // 以相反的顺序对脏 RenderObject 排序(最深的优先)。
      // 即子级 RenderObject 在父级 RenderObject 的前面。
      // 注意,和👆上面的重新布局和合成更新的排序是刚好相反的!
      for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
      
        // 需要重绘或者合成层需要更新的话,并且是位于当前 PipelineOwner 下。
        
        // 同上面,node._needsPaint || node._needsCompositedLayerUpdate 判断,
        // 防止子级被重复进行。
        if ((node._needsPaint || node._needsCompositedLayerUpdate) && node.owner == this) {
        
          // node._layerHandle.layer 是否已经被附加了
          if (node._layerHandle.layer!.attached) {
            
            // 能进到这里的,就说明 RenderObject 需要重新绘制或者是合成层更新
            if (node._needsPaint) {
              // 1️⃣ 需要的是重绘
              PaintingContext.repaintCompositedChild(node);
            } else {
              // 2️⃣ 需要的是更新层
              PaintingContext.updateLayerProperties(node);
            }
          } else {
            // node._layerHandle.layer 如果未被附加的话,
            
            // 当 flushPaint() 试图让我们绘制,但我们的层被分离时调用。
            node._skippedPaintingOnLayer();
          }
        }
      }
      
      // 对当前 PipelineOwner 中的子级 PipelineOwner 进行刷新重绘
      for (final PipelineOwner child in _children) {
        child.flushPaint();
      }
    } finally {
      // ...
    }
  }

semanticsOwner

 如果有的话,该对象负责管理这个 PipelineOwner 的语义(semantics)。

 owner 是通过 ensureSemantics 创建的,或者当该 owner 连接的 PipelineManifold 设置 PipelineManifold.semanticsEnabled 为 true 时创建。只要 PipelineManifold.semanticsEnabled 保持为 true,或者在调用 ensureSemantics 时存在未解决的 SemanticsHandle,owner 就是有效的。一旦这两个条件不再满足,semanticsOwner 字段将重新变为 null。

 当 semanticsOwner 为 null 时,PipelineOwner 将跳过与语义相关的所有步骤。

  SemanticsOwner? get semanticsOwner => _semanticsOwner;
  SemanticsOwner? _semanticsOwner;

_updateSemanticsOwner

 更新 _semanticsOwner 属性,赋值或者置为 null。

  void _updateSemanticsOwner() {
    if ((_manifold?.semanticsEnabled ?? false) || _outstandingSemanticsHandles > 0) {
      if (_semanticsOwner == null) {
        // 创建一个 SemanticsOwner
        _semanticsOwner = SemanticsOwner(onSemanticsUpdate: onSemanticsUpdate!);
        
        // 回调 SemanticsOwner 新建了,
        // 会回调到 RenderObject.scheduleInitialSemantics 函数。
        onSemanticsOwnerCreated?.call();
      }
    } else if (_semanticsOwner != null) {
      _semanticsOwner?.dispose();
      _semanticsOwner = null;
      
      // 回调 SemanticsOwner 销毁了,
      // 会回调到 RenderObject.clearSemantics 函数。
      onSemanticsOwnerDisposed?.call();
    }
  }

_nodesNeedingSemantics

 超级重要的一个 List,同 _nodesNeedingLayout

 在一个批次内所有的位于同一个 PipelineOwner 中需要被语义更新的 RenderObject 都被收集在这里。

  final Set<RenderObject> _nodesNeedingSemantics = <RenderObject>{};

flushSemantics

 更新被标记为需要语义更新的 RenderObject 的语义。

 最初,只有通过 RenderObject.scheduleInitialSemantics 安排的根节点需要进行语义更新。

 这个函数是渲染管线的核心阶段之一。语义是在绘制之后编制的,只有在调用 RenderObject.scheduleInitialSemantics 之后才会进行。

  void flushSemantics() {
    // 如果当前没有 _semanticsOwner 则直接返回即可。
    if (_semanticsOwner == null) {
      return;
    }
    
    try {
      // 临时变量 nodesToProcess 列表中记录已根据 depth 排序的 _nodesNeedingSemantics 中的 RenderObject,
      // 父级在前面,子级在后面
      final List<RenderObject> nodesToProcess = _nodesNeedingSemantics.toList()
        ..sort((RenderObject a, RenderObject b) => a.depth - b.depth);
      
      // 把原 _nodesNeedingSemantics 清空即可
      _nodesNeedingSemantics.clear();
      
      // 循环对 nodesToProcess 中的 RenderObject 对象调用 _updateSemantics
      for (final RenderObject node in nodesToProcess) {
      
        // 同上面,node._needsSemanticsUpdate 防止子级被重复语义更新。
        if (node._needsSemanticsUpdate && node.owner == this) {
          node._updateSemantics();
        }
      }
      
      _semanticsOwner!.sendSemanticsUpdate();
      
      // 对当前 PipelineOwner 中的子级 PipelineOwner 进行语义更新
      for (final PipelineOwner child in _children) {
        child.flushSemantics();
      }
    } finally {
      // ...
    }
  }

_children

 当前 PipelineOwner 的所有子级 PipelineOwner。

  final Set<PipelineOwner> _children = <PipelineOwner>{};

_manifold

 管理当前 PipelineOwner Tree 的 PipelineManifold 对象。

  PipelineManifold? _manifold;

attach

 将此 PipelineOwner 标记为已附加到给定的 PipelineManifold。

 通常,这只会直接在根 PipelineOwner 上调用。当调用 adoptChild 时,子级 PipelineOwner 会自动附加到其父 PipelineManifold。

  void attach(PipelineManifold manifold) {
    // 当前 PipelineOwner 的 _manifold 指向此 manifold
    _manifold = manifold;
    
    _manifold!.addListener(_updateSemanticsOwner);
    _updateSemanticsOwner();

    // 所有的子级 PipelineOwner 的 _manifold 属性都指向此 manifold
    for (final PipelineOwner child in _children) {
      child.attach(manifold);
    }
  }

detach

 将该 PipelineOwner 标记为已分离状态。通常,这只会直接在根 PipelineOwner 上调用。当调用 dropChild 时,子级 PipelineOwner 会自动从其父 PipelineManifold 中分离。

  void detach() {
    _manifold!.removeListener(_updateSemanticsOwner);
    _manifold = null;
    
    // 为了避免在 re-attached 时干扰任何客户端,此处不会更新 semantics owner。
    // 如果必要,在 "attach" 中将更新 semantics owner,或者在 "dispose" 中将其处置,如果不被重新附加的话。
    
    for (final PipelineOwner child in _children) {
      child.detach();
    }
  }

adoptChild

 将 child 子级 PipelineOwner 添加到此 PipelineOwner 中。

 在帧生成的阶段(RendererBinding.drawFrame),父 PipelineOwner 将在调用其子 PipelineOwner 上当前阶段对应的 flush 方法之前,完成自己拥有的节点的阶段。例如,在布局期间,父 PipelineOwner 将首先布局自己的节点(RenderObject),然后调用其子级 PipelineOwner 上的 flushLayout。在绘制过程中,它会先绘制自己的节点(RenderObject),然后再调用子级 PipelineOwner 的 flushPaint 方法。这种顺序在所有其他阶段也适用。

 不应假设子级 PipelineOwner 被刷新的顺序。

 在 PipelineOwner 开始在任何子级 PipelineOwner 上调用 flushLayout 后,直到当前帧结束,不得添加新的子级 PipelineOwner。

 要移除子节点,请调用 dropChild。

  void adoptChild(PipelineOwner child) {
    // 把 child 添加到自己的 _children 集合中
    _children.add(child);
    
    // 如果 _manifold 不为 null,
    // 则 child 的 _manifold 以及 child 的子级 PipelineOwner 也指向此 _manifold,
    // 即 PipelineOwner Tree 上的所有节点的 _manifold 都是指向同一个。
    if (_manifold != null) {
      child.attach(_manifold!);
    }
  }

dropChild

 移除之前通过 adoptChild 添加的子级 PipelineOwner。

 在生成帧期间,该 PipelineOwner 将停止调用子级上的 flush 方法。

 在 PipelineOwner 开始调用任何子级 PipelineOwner 的 flushLayout 后,直到当前帧结束之前,不能移除任何子级 PipelineOwner。

  void dropChild(PipelineOwner child) {
    // 把此 child 从当前 PipelineOwner 的 _children 集合中移除
    _children.remove(child);
    
    // 把 child 的 _manifold 以及 child 的所有子级 PipelineOwner 的 _manifold 指向都置为 null
    if (_manifold != null) {
      child.detach();
    }
  }

visitChildren

 为该 PipelineOwner 的每个直接子节点调用 visitor。

  void visitChildren(PipelineOwnerVisitor visitor) {
    _children.forEach(visitor);
  }

dispose

 释放由此 PipelineOwner 持有的任何资源。

 在调用此方法之前,此 PipelineOwner 必须从 PipelineOwner Tree 中移除,也就是说它必须既没有父级也没有任何子级。它还必须与任何 PipelineManifold 分离。

 调用 dispose 后,该对象不再可用。

  void dispose() {
    _semanticsOwner?.dispose();
    _semanticsOwner = null;
    
    _nodesNeedingLayout.clear();
    _nodesNeedingCompositingBitsUpdate.clear();
    _nodesNeedingPaint.clear();
    _nodesNeedingSemantics.clear();
  }

PipelineOwner 总结

 不出意外的我们又学了一颗新树:PipelineOwner Tree。至此我们已经学习了:Widget Tree、Element Tree、Notification Tree、Render Tree、PipelineOwner Tree 五颗树🌲了。

 通过四个属性可以看出 PipelineOwner 是:需要重新布局、需要合成更新、需要重绘、需要语义化的 RenderObject 的管理者。

  • List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
  • final List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
  • List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
  • final Set<RenderObject> _nodesNeedingSemantics = <RenderObject>{};

 当前 Render Tree 上的所有 RenderObject 对象的 PipelineOwner? _owner 都指向 RendererBinding 的 pipelineOwner 属性。

 然后在 RendererBinding.drawFrame 回调函数中,即在新的一帧中对前一帧分别收集到的上述四个属性中的 RenderObject 进行刷新。并会根据 depth 进行升序排序,即父级靠前,子级靠后,防止子级 RenderObject 被重复操作,flushPaint 中则相反,会根据 depth 进行降序排序,即子级靠前,父级靠后,这是因为子级需要先 paint,因为父级要用它 paint 的结果。四个刷新操作依次是:

  1. rootPipelineOwner.flushLayout();
  2. rootPipelineOwner.flushCompositingBits();
  3. rootPipelineOwner.flushPaint();
  4. rootPipelineOwner.flushSemantics();

 OK,PipelineOwner 的内容就到这里吧,下面直击 RenderObject class。

参考链接

参考链接:🔗