深刻淺出 Flutter Framework 之 PipelineOwner

本文是『 深刻淺出 Flutter Framework 』系列文章的第六篇,詳細介紹了 PipelineOwner 在整個 Rendering Pipeline 中是如何協助『 RenderObject Tree 』、『 RendererBinding』以及『 Window』完成 UI 刷新。node

本文同時發表於個人我的博客git

本系列文章將深刻 Flutter Framework 內部逐步去分析其核心概念和流程,主要包括:github

Overview


PipelineOwner在 Rendering Pipeline 中起到重要做用:markdown

  • 隨着 UI 的變化而不斷收集『 Dirty Render Objects 』
  • 隨之驅動 Rendering Pipeline 刷新 UI

簡單講,PipelineOwner是『RenderObject Tree』與『RendererBinding』間的橋樑,在二者間起到溝通協調的做用。ide

關係


如上圖:oop

  • 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,其背後真實的類是WidgetsFlutterBindingpost

如上所述,正常狀況下在 Flutter 運行過程當中只有一個PipelineOwner實例,並由RendererBinding持有,用於管理全部『 on-screen RenderObjects 』。 然而,若是有『 off-screen RenderObjects 』,則能夠建立新的PipelineOwner實例來管理它們。 『on-screen PipelineOwner』與 『 off-screen PipelineOwner 』徹底獨立,後者須要建立者本身維護、驅動。ui

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable 如上,RendererBinding要求其附屬類 mixin BindingBaseServicesBindingSchedulerBindingGestureBindingSemanticsBinding以及HitTestable,爲了描述方便,文本提到的RendererBinding上的方法也可能來自於其餘幾個 Binding。this

驅動


Dirty RenderObjects

Render Object 有4種『 Dirty State』 須要 PipelineOwner 去維護:spa

  • 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內部操做的過程,在此再也不贅述。

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 的刷新。

相關文章
相關標籤/搜索