Flutter 探索系列:布局和渲染(二)

上一篇文章中,我們介紹 Flutter Widget 的設(shè)計(jì)思想、實(shí)現(xiàn)原理,并分析了 Widget、Element 和 RenderObject 的源碼,這篇文章繼續(xù)結(jié)合源碼分析 Flutter 的渲染過程。

實(shí)現(xiàn)原理

1,F(xiàn)lutter 渲染流程是怎樣的?

image

從這張圖上可知,界面顯示到屏幕上,F(xiàn)lutter 經(jīng)過了 Vsync 信號(hào)、動(dòng)畫、build、布局、繪制、合成等渲染過程。

顯示器垂直同步 Vsync 不斷的發(fā)出信號(hào),驅(qū)動(dòng) Flutter Engine 刷新視圖。Flutter 提供 60 FPS,也就是一秒鐘發(fā)出60次信號(hào),觸發(fā)60次重繪。

運(yùn)行動(dòng)畫,每個(gè) Vsync 信號(hào)都會(huì)觸發(fā)動(dòng)畫狀態(tài)改變。

Widget 狀態(tài)改變,觸發(fā)重建一棵新的 Widget 樹。比較新舊 Widget 樹的差異,找出有變動(dòng)的 Widget 節(jié)點(diǎn)和對(duì)應(yīng)的 RenderObject 節(jié)點(diǎn)。詳細(xì)過程請(qǐng)參考上一篇文章。

對(duì)需要更新 RenderObject 節(jié)點(diǎn),更新界面布局、重新繪制。

根據(jù)新的 RenderObject 樹,更新合成圖層。

輸出新的圖層樹。

2,渲染過程中,F(xiàn)lutter 如何更新界面布局?
經(jīng)過 build 環(huán)節(jié)后,找出需要更新的 RenderObject 樹,首先進(jìn)入布局環(huán)節(jié)。上一篇文章中介紹到,element 被標(biāo)記為 dirty 時(shí)便會(huì)重建,同樣的,RenderObject 被標(biāo)記為 dirty 后,放入一個(gè)待處理的數(shù)組中。在布局環(huán)節(jié)中,遍歷該數(shù)組中的元素,按照節(jié)點(diǎn)在 RenderObject 樹上的深度重新布局。

每個(gè)節(jié)點(diǎn) RenderObject 對(duì)象,按照部件的邏輯先計(jì)算自身所占空間的大小、位置,再計(jì)算 child 的,paren 將 size 約束限制傳遞給 child,child 根據(jù)這個(gè)約束計(jì)算自身的所占空間,然后再傳給 child 的 child,如此遞歸完成整個(gè) RenderObject 樹的布局更新。大概過程如下

parent.performLayout() -> child.layout() -> child.performLayout()/child.performResize() -> child.child.layout() -> .....

Flutter 還有一個(gè) RelayoutBoundary,用于確定重繪邊界,可以手動(dòng)指定或自動(dòng)設(shè)置。邊界內(nèi)的對(duì)象重新布局,不會(huì)影響邊界外的對(duì)象。

3,渲染過程中,F(xiàn)lutter 如何繪制界面?
Paint 的過程有點(diǎn)類似于 Layout,同樣將待重新繪制的 RenderObject 標(biāo)記為 dirty,放入一個(gè)數(shù)組中。這個(gè)數(shù)組也是深度優(yōu)先的順序執(zhí)行,先繪制自身,再繪制 child。

isRepaintBoundary 重繪邊界也類似上面的 RelayoutBoundary,重繪邊界內(nèi)的元素及 child,會(huì)一起重新繪制,邊界外的元素不受影響。

源碼分析

我們按照 Flutter 的渲染依次分析 Vsync、build、layout、paint 四個(gè)過程 。

Vsync

垂直同步信號(hào) Vsync 到來后,執(zhí)行一系列動(dòng)作開始界面的重新渲染,那么在哪里監(jiān)聽 Vsync 信號(hào),收到 Vsync 信號(hào)如何通知界面刷新?

上一篇文章介紹了 Widget build 實(shí)現(xiàn)過程,其中提到在 Flutter 應(yīng)用啟動(dòng)時(shí),初始化了一個(gè)單例對(duì)象 WidgetsFlutterBinding,它是連接 Flutter engine sdk 和 Widget 框架的橋梁,它混合了 SchedulerBinding 和 RendererBinding。SchedulerBinding 提供了 window.onBeginFrame 和 window.onDrawFrame 回調(diào),監(jiān)聽刷新事件。

RendererBinding 在初始化方法 initInstances 中,addPersistentFrameCallback 向 persistentCallbacks 隊(duì)列添加了一個(gè)回調(diào) _handlePersistentFrameCallback。

在收到從 Flutter engine 傳來的刷新事件時(shí),調(diào)用 _handlePersistentFrameCallback 回調(diào),也就是執(zhí)行 drawFrame 方法。

// RendererBinding
void initInstances() {
    ...
    addPersistentFrameCallback(_handlePersistentFrameCallback);
}
  
void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
}

// SchedulerBinding
void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
}

那么 persistentCallbacks 隊(duì)列什么時(shí)候被執(zhí)行?

這里先介紹一個(gè)類 Window,它是 Flutter engine 提供的一個(gè)圖形界面相關(guān)的接口,包括了屏幕尺寸、調(diào)度接口、輸入事件回調(diào)、圖形繪制接口和其他一些核心服務(wù)。Window 有一個(gè)繪制的回調(diào)方法 _onDrawFrame

當(dāng) Flutter engine 調(diào)用 _onDrawFrame 時(shí),觸發(fā) SchedulerBinding.handleDrawFrame 方法,這個(gè)方法里面遍歷執(zhí)行已注冊(cè)的回調(diào),即前面注冊(cè)的 drawFrame 方法。

// SchedulerBinding
  void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
  }
  
void handleDrawFrame() {
    ...
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    ...
}

Vsync 信號(hào)到來,F(xiàn)lutter engine 調(diào)用 _onDrawFrame 方法啟動(dòng)渲染流程,開啟一系列的動(dòng)作。

Build

收到刷新事件后,先調(diào)用 WidgetsBinding.drawFame 方法。這個(gè)方法重建 Widget 樹,這一過程上篇文章有詳細(xì)介紹,這里不多做贅述。

//WidgetsBinding
void drawFrame() {
    ...
    if (renderViewElement != null)
      buildOwner.buildScope(renderViewElement);
    super.drawFrame();
    ...
}

Layout

super.drawFrame() 會(huì)進(jìn)入到 RenderBinding.drawFrame 方法,開始重新布局和繪制。

//RenderBinding
void drawFrame() {
  pipelineOwner.flushLayout(); //布局
  pipelineOwner.flushCompositingBits(); //重繪之前的預(yù)處理操作,檢查RenderObject是否需要重繪
  pipelineOwner.flushPaint(); // 重繪
  renderView.compositeFrame(); // 將需要繪制的比特?cái)?shù)據(jù)發(fā)給GPU
  pipelineOwner.flushSemantics(); 
}

flushLayout 方法內(nèi),遍歷 _nodesNeedingLayout 數(shù)組,_nodesNeedingLayout 內(nèi)存放的是被標(biāo)記為 dirty 的 RenderObject 元素。遍歷前先對(duì) _nodesNeedingLayout 數(shù)組排序,按照深度優(yōu)先的順序重新排列,即先處理上層節(jié)點(diǎn)再處理下層節(jié)點(diǎn),然后遍歷每個(gè)元素重新布局。

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

_layoutWithoutResize 調(diào)用 performLayout 方法,每個(gè) RenderObject 子類都有不同的實(shí)現(xiàn),以 RenderView 為例,它讀取配置中的 size,然后調(diào)用 child 的 layout 方法,并把 size 限制傳進(jìn)去。同時(shí)將自身的布局標(biāo)志 _needsLayout 設(shè)置為 false

void _layoutWithoutResize() {
   ...
   performLayout(); 
   markNeedsSemanticsUpdate();
   ...
   _needsLayout = false;
   markNeedsPaint();
}

void performLayout() {
    _size = configuration.size;
    
    if (child != null)
      child.layout(new BoxConstraints.tight(_size));//調(diào)用child的layout
}

layout 方法中,傳入的兩個(gè)參數(shù):constraints 表示 parent 對(duì) child 的大小限制,parentUsesSize 表示 child 布局變化是否影響 parent,如果為 true,當(dāng) child 布局變化時(shí),parent 會(huì)被標(biāo)記為需要重新布局。

void layout(Constraints constraints, { bool parentUsesSize: false }) {
    ...
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      final RenderObject parent = this.parent;
      relayoutBoundary = parent._relayoutBoundary;
    }
    
    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
      return;
    }
    _constraints = constraints;
    _relayoutBoundary = relayoutBoundary;
    
    if (sizedByParent) {
        performResize(); 
    }
    
    performLayout();
    ...
}

sizedByParent 表示 child size 完成由 parent 決定,所以當(dāng) sizedByParent 為 true 時(shí),child size 在 performResize 中確定。當(dāng) sizedByParent 為 false 時(shí),執(zhí)行 performLayout 計(jì)算自身 size,并調(diào)用自身的 child 布局,最終調(diào)用鏈就變成:

parent.performLayout() -> child.layout() -> child.performLayout()/child.performResize() -> child.child.layout() -> .....

RelayoutBoundary,用于確定重繪邊界。邊界內(nèi)的對(duì)象重新布局,不會(huì)影響邊界外的對(duì)象。在 RenderObject 的 markNeedsLayout 方法中,從自身開始向 parent 查找到 relayoutBoundary,然后把它添加到待布局 _nodesNeedingLayout 數(shù)組中,等下次 Vsnc 信號(hào)到來時(shí)重新布局。

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

  void markParentNeedsLayout() {
    _needsLayout = true;
    final RenderObject parent = this.parent;
    if (!_doingThisLayoutWithCallback) {
      parent.markNeedsLayout();
    } else {
      assert(parent._debugDoingThisLayout);
    }
  }

Paint

布局完成后開始繪制,繪制的入口是 flushPaint。類似于布局,將需要重新繪制的 RenderObject 標(biāo)記為 dirty,同樣按照深度優(yōu)先的順序遍歷 _nodesNeedingPaint 數(shù)組,每個(gè)元素都重新繪制。

void flushPaint() {
  final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
  _nodesNeedingPaint = <RenderObject>[];
  // Sort the dirty nodes in reverse order (deepest first).
  for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
    if (node._needsPaint && node.owner == this) {
      if (node._layer.attached) {
        PaintingContext.repaintCompositedChild(node);
      } else {
        node._skippedPaintingOnLayer();
      }
    }
  }
  ...
}

paint 由具體的 RenderObject 類重寫,每個(gè)實(shí)現(xiàn)都不一樣。如果 RenderObject 有 child,執(zhí)行自身的 paint 后,再執(zhí)行 paintChild,調(diào)用鏈: paint() -> paintChild() -> paint() ...

static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent: false }) {
    ...
    OffsetLayer childLayer = child._layer;
    if (childLayer == null) {  
      child._layer = childLayer = OffsetLayer();
    } else {
      childLayer.removeAllChildren();
    }
    
    final PaintingContext childContext = PaintingContext(child._layer, child.paintBounds);
    child._paintWithContext(childContext, Offset.zero);
    childContext._stopRecordingIfNeeded();
}

void _paintWithContext(PaintingContext context, Offset offset) {
  ...
  paint(context, offset); 
  ...
}


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

isRepaintBoundary 類似于上面布局中的 RepaintBoundary,它決定是否自身是否獨(dú)立繪制,如果為 true,則獨(dú)立繪制,否則隨 parent 一塊繪制。

最后將所有l(wèi)ayer組合成Scene,然后通過 ui.window.render 方法,把 scene 提交給Flutter Engine

void compositeFrame() {
  ...
  try {
    final ui.SceneBuilder builder = ui.SceneBuilder();
    final ui.Scene scene = layer.buildScene(builder);
    if (automaticSystemUiAdjustment)
      _updateSystemChrome();
    ui.window.render(scene);
    scene.dispose(); 
  } finally {
    Timeline.finishSync();
  }
}

參考資料

Flutter
Flutter框架分析(四)-- Flutter框架的運(yùn)行
Flutter運(yùn)行機(jī)制-從啟動(dòng)到顯示

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容