從源碼看flutter(四):Layer篇

從源碼看flutter系列集合canvas

開篇

這一篇,咱們將簡單的瞭解一下 Layer 相關內容,由於其中大部分是與C++交互,因此只是從結構上作一個認識與分析bash

從上一篇中,咱們瞭解到了,若是 RenderObjectisRepaintBoundarytrue 會經過本身的 Layer 對象去渲染,若是沒有爲 RenderObject 手動指定 Layer 的話,默認會是 OffestLayer;爲 false 則經過父節點的 Layer 對象渲染。app

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

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

下面,咱們簡單的看一下 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,看起來像一個雙向鏈表的結構post

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

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

其中 PictureLayout 是主要的圖像繪製層;
TextureLayer 則用於外界紋理的實現,經過它能夠實現諸如相機、視頻播放、OpenGL等相關操做;
ContainerLayout 則是各個 Layer 組成的複合層this

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();
}
複製代碼

效果以下

能夠看到,咱們沒有使用任何 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 Hot Reload(原理篇)

本篇到這裏就結束了,而【從源碼看flutter】 還沒有結束,敬請期待吧

相關文章
相關標籤/搜索