美文网首页
深入浅出 Flutter Framework 之 Pipelin

深入浅出 Flutter Framework 之 Pipelin

作者: iOS开发面试总结 | 来源:发表于2020-12-07 15:01 被阅读0次

    Overview


    PipelineOwner在 Rendering Pipeline 中起到重要作用:

    • 随着 UI 的变化而不断收集『 Dirty Render Objects 』
    • 随之驱动 Rendering Pipeline 刷新 UI

    简单讲,PipelineOwner是『RenderObject Tree』与『RendererBinding』间的桥梁,在两者间起到沟通协调的作用。

    关系


    如上图:

    • RendererBinding创建并持有PipelineOwner实例,Code1-第8~12
    • 同时,RendererBinding会创建『RenderObject Tree』的根节点,即:RenderView,并将其赋值给PipelineOwner#rootNode,Code1-第13~24
    • 在『RenderObject Tree』构建过程中,每插入一个新节点,就会将PipelineOwner实例 attach 到该节点上,即『RenderObject Tree』上所有结点共享同一个PipelineOwner实例,Code2-第4
    1 // Code1-RendererBinding#init
    2 // 代码有删减,下同
    3 mixin RendererBinding {
    4   @override
    5   void initInstances() {
    6     super.initInstances();
    7     _instance = this;
    8     _pipelineOwner = PipelineOwner(
    9       onNeedVisualUpdate: ensureVisualUpdate,
    10      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
    11      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    12    );
    13    initRenderView();
    14 addPersistentFrameCallback(_handlePersistentFrameCallback);
    15  }
    16
    17  void initRenderView() {
    18    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    19    renderView.prepareInitialFrame();
    20  }
    21
    22  set renderView(RenderView value) {
    23    _pipelineOwner.rootNode = value;
    24  }
    
    // Code2-RenderObject#adoptChild
    //
    1   void adoptChild(covariant AbstractNode child) {
    2     child._parent = this;
    3     if (attached)
    4       child.attach(_owner);
    5     redepthChild(child);
    6   }
    7
    8   void attach(covariant Object owner) {
    9     _owner = owner;
    10  }
    

    RendererBinding是 mixin,其背后真实的类是WidgetsFlutterBinding

    如上所述,正常情况下在 Flutter 运行过程中只有一个PipelineOwner实例,并由RendererBinding持有,用于管理所有『 on-screen RenderObjects 』。 然而,如果有『 off-screen RenderObjects 』,则可以创建新的PipelineOwner实例来管理它们。 『on-screen PipelineOwner』与 『 off-screen PipelineOwner 』完全独立,后者需要创建者自己维护、驱动。

    mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable 如上,RendererBinding要求其附属类 mixin BindingBaseServicesBindingSchedulerBindingGestureBindingSemanticsBinding以及HitTestable,为了描述方便,文本提到的RendererBinding上的方法也可能来自于其他几个 Binding。

    驱动


    Dirty RenderObjects

    Render Object 有4种『 Dirty State』 需要 PipelineOwner 去维护:

    • Needing Layout:Render Obejct 需要重新 layout
    • Needing Compositing Bits Update:Render Obejct 合成标志位(Compositing)有变化
    • Needing Paint:Render Obejct 需要重新绘制
    • Needing Semantics:Render Object 辅助信息有变化

    如上图:

    • 当 RenderObject 需要重新 layout 时,调用markNeedsLayout方法,该方法会将当前 RenderObject 加入 PipelineOwner#_nodesNeedingLayout或传给父节点去处理;
    • 当 RenderObject 的 Compositing Bits 有变化时,调用markNeedsCompositingBitsUpdate方法,该方法会将当前 RenderObject 加入 PipelineOwner#_nodesNeedingCompositingBitsUpdate或传给父节点去处理;
    • 当 RenderObject 需要重新 paint 时,调用markNeedsPaint方法,该方法会将当前 RenderObject 加入PipelineOwner#_nodesNeedingPaint或传给父节点处理;
    • 当 RenderObject 的辅助信息(Semantics)有变化时,调用markNeedsSemanticsUpdate方法,该方法会将当前 RenderObject 加入 PipelineOwner#_nodesNeedingSemantics或传给父节点去处理

    上述就是 PipelineOwner 不断收集『 Dirty RenderObjects 』的过程。

    RenderObject 内部的逻辑会在后续文章中详细分析。

    Request Visual Update

    上述4个markNeeds*方法,除了markNeedsCompositingBitsUpdate,其他方法最后都会调用PipelineOwner#requestVisualUpdate。 之所以markNeedsCompositingBitsUpdate不会调用PipelineOwner#requestVisualUpdate,是因为其不会单独出现,一定是伴随其他3个之一一起出现的。

    如上图,随着PipelineOwner#requestVisualUpdate->RendererBinding#scheduleFrame->Window#scheduleFrame调用链,UI 需要刷新的信息最终传递到了 Engine 层。 具体讲,Window#scheduleFrame主要是向 Engine 请求在下一帧刷新时调用Window#onBeginFrame以及Window#onDrawFrame方法。

    Window#onBeginFrameWindow#onDrawFrame本质上是 RendererBinding 向其注入的两个回调(_handleBeginFrame_handleDrawFrame):

    // Code3-SchedulerBinding
    //
    1   void ensureFrameCallbacksRegistered() {
    2    window.onBeginFrame ??= _handleBeginFrame;
    3    window.onDrawFrame ??= _handleDrawFrame;
    4  }
    

    Handle Draw Frame

    如上图,Engine 在接收到 UI 需要更新后,在下一帧刷新时会调用Window#onDrawFrame,通过提前注册好的PersistentFrameCallback,最终调用到RendererBinding#drawFrame方法:

    // Code4-RendererBinding#drawFrame
    //
    1   void drawFrame() {
    2     pipelineOwner.flushLayout();
    3     pipelineOwner.flushCompositingBits();
    4     pipelineOwner.flushPaint();
    5     renderView.compositeFrame(); // this sends the bits to the GPU
    6     pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    7  }
    

    如上,RendererBinding#drawFrame依次调用PipelineOwnerflushLayoutflushCompositingBitsflushPaint以及flushSemantics方法,来处理对应状态下的 RenderObject。

    Flush Layout

    // Code5-PipelineOwner#flushLayout
    //
    1   void flushLayout() {
    2     while (_nodesNeedingLayout.isNotEmpty) {
    3       final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
    4       _nodesNeedingLayout = <RenderObject>[];
    5       for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
    6         if (node._needsLayout && node.owner == this)
    7           node._layoutWithoutResize();
    8       }
    9     } 
    10  }
    

    首先,PipelineOwner对于收集到的『 Needing Layout RenderObjects 』按其在『 RenderObject Tree 』上的深度升序排序,主要是为了避免子节点重复 Layout (因为父节点 layout 时,也会递归地对子树进行 layout); 其次,对排好序的且满足条件的 RenderObjects 依次调用_layoutWithoutResize来执行 layout 操作。

    在父节点 layout 完成时,其所有子节点也 layout 完成,它们的_needsLayout标志会被置为flase,因此在 Code5 中需要第6行的判断,避免重复 layout。

    Flush Compositing Bits

    // Code6-PipelineOwner#flushCompositingBits
    //
    1   void flushCompositingBits() {
    2     _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    3     for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
    4       if (node._needsCompositingBitsUpdate && node.owner == this)
    5         node._updateCompositingBits();
    6     }
    7     _nodesNeedingCompositingBitsUpdate.clear();
    8   }
    

    同理,先对『 Needing Compositing Bits RenderObjects 』排序,再调用RenderObjects#_updateCompositingBits

    Flush Paint

    // Code7-PipelineOwner#flushPaint
    //
    1   void flushPaint() {
    2     final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
    3     _nodesNeedingPaint = <RenderObject>[];
    4     // Sort the dirty nodes in reverse order (deepest first).
    5     for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
    6       if (node._needsPaint && node.owner == this) {
    7         if (node._layer.attached) {
    8             PaintingContext.repaintCompositedChild(node);
    9         } 
    10      }
    11    }
    12  }
    

    对于 Paint 操作来说,父节点需要用到子节点绘制的结果,故子节点需要先于父节点被绘制。 因此,不同于前两个 flush 操作,此时需要对『 Needing Paint RenderObjects 』按深度降序排序。 如下图,在深入浅出 Flutter Framework 之 PaintingContext一文中详细分析了从PipelineOwner#flushPaintPaintingContext内部操作的过程,在此不再赘述。

    image

    Flush Semantics

    // Code8-PipelineOwner#flushSemantics
    //
    1   void flushSemantics() {
    2     final List<RenderObject> nodesToProcess = _nodesNeedingSemantics.toList()
    3       ..sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    4     _nodesNeedingSemantics.clear();
    5     for (RenderObject node in nodesToProcess) {
    6       if (node._needsSemanticsUpdate && node.owner == this)
    7         node._updateSemantics();
    8     }
    9     _semanticsOwner.sendSemanticsUpdate();
    10  }
    

    Flush Semantics 所做操作与 Flush Layout 完全相似,不再赘述。

    至此,PipelineOwner 相关的内容就介绍完了。

    小结

    PipelineOwner 作为『 RenderObject Tree』与『 RendererBinding/Window』间的沟通协调桥梁,在整个 Rendering Pipeline 中起到重要作用。 在 Flutter 应用生命周期内,不断收集『 Dirty RenderObjects 』并及时通知 Engine。 在帧刷新时,通过来自 RendererBinding 的回调依次处理收集到的:

    • 『 Needing Layout RenderObjects 』
    • 『 Needing Compositing Bits Update RenderObjects 』
    • 『 Needing Paint RenderObjects 』
    • 『 Needing Semantics RenderObjects 』

    最终完成 UI 的刷新。

    结交人脉

    最后推荐个我的iOS交流群:789143298
    '有一个共同的圈子很重要,结识人脉!里面都是iOS开发,全栈发展,欢迎入驻,共同进步!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

    不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!!!

    作者:峰之巅
    链接:https://juejin.cn/post/6903082090288381965

    相关文章

      网友评论

          本文标题:深入浅出 Flutter Framework 之 Pipelin

          本文链接:https://www.haomeiwen.com/subject/qyqtgktx.html