從原始碼看flutter(四):Layer篇

安卓小哥發表於2020-04-21

從原始碼看flutter系列集合

開篇

這一篇,我們將簡單的瞭解一下 Layer 相關內容,因為其中大部分是與C++互動,所以只是從結構上做一個認識與分析

從上一篇中,我們瞭解到了,如果 RenderObjectisRepaintBoundarytrue 會通過自己的 Layer 物件去渲染,如果沒有為 RenderObject 手動指定 Layer 的話,預設會是 OffestLayer;為 false 則通過父節點的 Layer 物件渲染。

其中 paint 相關的 Layer 邏輯都在 PaintingContext 中,每次 paint 都會建立一個新的 PaintingContext 物件

同時通過 PaintingContext 獲取 Canvans 時會建立一個 PictureLayer 被合成到 PaintingContext 的建立時所接收的 Layer

下面,我們簡單的看一下 Layer 物件

Layer

abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
  
  @override
  ContainerLayer get parent => super.parent as ContainerLayer;
  ...
  Layer get nextSibling => _nextSibling;
  ...
  Layer get previousSibling => _previousSibling;
  ...
  @protected
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);
  ...
}
複製程式碼

LayerRenderObject 一樣,都是 AbstractNode 的子類,可以看到持有的 parent 物件都是 ContainerLayer,同時 Layer 還有兩個物件 nextSiblingpreviousSibling,看起來像一個雙向連結串列的結構

addToScene(...) 交由子類實現,就是將 Layer 物件交給 engine 去處理,傳遞給 engine 的邏輯都在 SceneBuilder

Layer 有多個子類,分別實現不同的渲染功能

從原始碼看flutter(四):Layer篇

其中 PictureLayout 是主要的影像繪製層;
TextureLayer 則用於外界紋理的實現,通過它可以實現諸如相機、視訊播放、OpenGL等相關操作;
ContainerLayout 則是各個 Layer 組成的複合層

Layer的重新整理

上一篇中,我們自定義了 RenderObject 並且重寫了它的 paint(...) 方法,通過對 Canvans 物件進行操作,我們繪製了自己想要的影像,而 Canvans 是從 PaintingContext 中獲取的,在獲取 Canvans 時,其實做了和 Layer 有關的一系列操作

class PaintingContext extends ClipContext {
  ...
  @override
  Canvas get canvas {
    if (_canvas == null)
      _startRecording();
    return _canvas;
  }

  void _startRecording() {
    assert(!_isRecording);
    _currentLayer = PictureLayer(estimatedBounds);
    _recorder = ui.PictureRecorder();
    _canvas = Canvas(_recorder);
    _containerLayer.append(_currentLayer);
  }
  ...
}
複製程式碼

可以看到,在這裡建立了一個新的 PictureLayer 被新增到了 _containerLayer 中,我們的 Layer 最終是如何被渲染的呢?

資訊還是可以從上一篇獲得,我們知道 RendererBindingdrawFrame() 中進行了佈局與繪製操作

  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true;
    }
  }
複製程式碼

最終的渲染其實是通過 compositeFrame() 來進行的,而這裡的 renderView 就是我們的根RenderObject 我們可以看一下 compositeFrame() 做了些什麼

class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
  ...
  void compositeFrame() {
    ...
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
    ...
  }
  ...
}
複製程式碼

可以看到,這裡通過 buildScene(...) 建立了 Scene 物件,根據註釋來看,它相當於一顆 Layer

之後通過 Window 物件的 render(...) 方法來進行渲染

  void render(Scene scene) native 'Window_render';
複製程式碼

它直接呼叫的是 engine 的方法,通過這個方法,就可以對影像進行渲染,後面我們會進行一個測試來展示它的作用

這裡的 layer 就是根 Layer ,它其實是一個 TransformLayer,我們可以簡單看一下它的建立流程

根Layer建立流程

RendererBindinginitInstances() 中通過 initRenderView() 進行了 RenderView 的建立

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  ...
  @override
  void initInstances() {
    super.initInstances();
    ...
    initRenderView();
    ...
  }
  ...
}

複製程式碼

RendererBinding -> initRenderView()

  void initRenderView() {
    assert(renderView == null);
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }
複製程式碼

Layer 就是在 prepareInitialFrame() 中建立的

RenderView -> prepareInitialFrame()

  void prepareInitialFrame() {
    ...
    scheduleInitialLayout();
    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
    ...
  }
複製程式碼

建立的方法就是 _updateMatricesAndCreateNewRootLayer()

  TransformLayer _updateMatricesAndCreateNewRootLayer() {
    _rootTransform = configuration.toMatrix();
    final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
    rootLayer.attach(this);
    ...
    return rootLayer;
  }
複製程式碼

這裡我們只是簡單的瞭解一下根 Layer 的建立流程,它就是一個 TransformLayer 物件。

建立流程我們知道了,而想要了解重新整理流程,我們需要回到 compositeFrame() 方法中,執行重新整理的方法就在 buildScene(...)

buildScene(...)

從前面的關係圖我們知道,TransformLayer 的父類是 OffsetLayer,而 OffsetLayer 的父類是 ContainerLayer,它們都沒有重寫 buildScene(...) 方法,所以最後會呼叫 ContainerLayerbuildScene(...)

  ui.Scene buildScene(ui.SceneBuilder builder) {
    ...
    updateSubtreeNeedsAddToScene();
    addToScene(builder);
    ...
    _needsAddToScene = false;
    ...
    return scene;
  }
複製程式碼

可以先看一下 updateSubtreeNeedsAddToScene() 方法

updateSubtreeNeedsAddToScene()

  ///ConstraintLayer
  @override
  void updateSubtreeNeedsAddToScene() {
    super.updateSubtreeNeedsAddToScene();
    Layer child = firstChild;
    while (child != null) {
      child.updateSubtreeNeedsAddToScene();
      _needsAddToScene = _needsAddToScene || child._needsAddToScene;
      child = child.nextSibling;
    }
  }
  
  ///Layer
  @protected
  @visibleForTesting
  void updateSubtreeNeedsAddToScene() {
    _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
  }
複製程式碼

其實所有 Layer 類中,只有 ConstraintLayerLayer 具備這兩個方法,這裡其實就是遍歷所有子 Layer 物件,呼叫他們的 updateSubtreeNeedsAddToScene() 來設定 _needsAddToScene 的值

這個值顧名思義,就是表示是否需要將改 Layer 新增到 Scene 中,如果需要新增,則就是進行重新整理了。它根據 _needsAddToScenealwaysNeedsAddToScene 來設定,當呼叫 markNeedsAddToScene() 方法的時候, _needsAddToScene 就會被設定為 true

updateSubtreeNeedsAddToScene() 執行結束後,接下來會呼叫 addToScene(builder) 方法

addToScene(...)

正好 TransformLayer 重寫了這個方法,並且沒有呼叫父類的方法

  @override
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
    ...
    engineLayer = builder.pushTransform(
      _lastEffectiveTransform.storage,
      oldLayer: _engineLayer as ui.TransformEngineLayer,
    );
    addChildrenToScene(builder);
    builder.pop();
  }
複製程式碼

這裡的 engineLayer 物件是用於進行復用的

可以看到這裡呼叫了 addChildrenToScene(builder) 方法,這個方法只在 ContainerLayer 中,且沒有被重寫

addChildrenToScene(...)

  void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
    Layer child = firstChild;
    while (child != null) {
      if (childOffset == Offset.zero) {
        child._addToSceneWithRetainedRendering(builder);
      } else {
        child.addToScene(builder, childOffset);
      }
      child = child.nextSibling;
    }
  }
複製程式碼

在這裡就是遍歷 child ,然後呼叫它們各自實現的 addToScene(...) 方法,而是否要將 Layer 新增到 Scene 的判斷依據,已經在之前的 updateSubtreeNeedsAddToScene() 中完成了。

這裡需要注意一下, _addToSceneWithRetainedRendering(builder) 就是用於對之前的 _engineLayer 進行復用,當 childOffsetOffset.zero

那麼到這裡 Layer 的重新整理流程就結束了。而本篇文章差不多也快到頭了,接下來我們完成上面提到過的,進行一個渲染測試

渲染測試

可以在這裡進行測試:dartpad.dev/

import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';


void main(){
    final OffsetLayer rootLayer = new OffsetLayer();
    final PictureLayer pictureLayer = new PictureLayer(Rect.zero);
    rootLayer.append(pictureLayer);

    PictureRecorder recorder = PictureRecorder();
    Canvas canvas = Canvas(recorder);

    Paint paint = Paint();
    paint.color = Colors.primaries[Random().nextInt(Colors.primaries.length)];

    canvas.drawRect(Rect.fromLTWH(0, 0, 300, 300), paint);
    pictureLayer.picture = recorder.endRecording();
    
    SceneBuilder sceneBuilder = SceneBuilder();
    rootLayer.addToScene(sceneBuilder);

    Scene scene = sceneBuilder.build();
    window.onDrawFrame = (){
      window.render(scene);
    };
    window.scheduleFrame();
}
複製程式碼

效果如下

從原始碼看flutter(四):Layer篇

可以看到,我們沒有使用任何 Widget 就在裝置上展示了一個圖案。所以其實從這裡就可以瞭解到為什麼說 Flutter是通過skia引擎去繪製的了。

關於 Layer 的其他內容,這裡也不再深入了,畢竟再深入就是C++了

本篇是我們講過的四棵樹中最後的一顆,而這裡非常方便用於測試一個我們前三篇都遇到了但是都略過了的部分,那就是 熱過載

額外部分:熱過載

當你通過上面的用例進行測試的時候,點選一下熱過載按鈕,是不是發現會報錯:

Error -32601 received from application: Method not found
複製程式碼

並且圖案的顏色並不會更改,這就涉及到我們之前提到過的一個方法了:reassemble()

ElementRenderObject 中你經常能看到與之相關的方法,它就是用於實現熱過載的核心邏輯

BindingBase 中,我們可以看到找到這樣一個方法:reassembleApplication() ,就是它來進行熱過載控制的

它會呼叫 performReassemble() 方法

  @mustCallSuper
  @protected
  Future<void> performReassemble() {
    FlutterError.resetErrorCount();
    return Future<void>.value();
  }
複製程式碼

WidgetsBindingRendererBinding 都重寫了這個方法,如果感興趣的話,可以去看一下,他們分在其中呼叫了讓 ElementRenderObject 進行熱過載的方法

那麼,我們想要實現實現熱過載其實就很簡單了,看程式碼:

import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/foundation.dart';

void main() => TestBinding();

class TestBinding extends BindingBase{

  @override
  Future<void> performReassemble(){
    final OffsetLayer rootLayer = new OffsetLayer();
    final PictureLayer pictureLayer = new PictureLayer(Rect.zero);
    rootLayer.append(pictureLayer);

    PictureRecorder recorder = PictureRecorder();
    Canvas canvas = Canvas(recorder);

    Paint paint = Paint();
    paint.color = Colors.primaries[Random().nextInt(Colors.primaries.length)];

    canvas.drawRect(Rect.fromLTWH(0, 0, 300, 300), paint);
    pictureLayer.picture = recorder.endRecording();

    SceneBuilder sceneBuilder = SceneBuilder();
    rootLayer.addToScene(sceneBuilder);

    Scene scene = sceneBuilder.build();
    window.onDrawFrame = (){
      window.render(scene);
    };
    window.scheduleFrame();
    super.performReassemble();
    return Future<void>.value();
  }
}
複製程式碼

熱過載效果如下,大家可以在裝置上進行測試

從原始碼看flutter(四):Layer篇

當然,熱過載的核心邏輯就是這個了。

不過此前會進行程式碼檔案的變更檢查等,詳情可以看這一篇文章:揭祕Flutter Hot Reload(原理篇)

本篇到這裡就結束了,而【從原始碼看flutter】 尚未結束,敬請期待吧

相關文章