Read the Flutter collection from source code

The opening

In this article, we will briefly take a look at the Layer related content, because most of it is interacting with C++, so it is only a structural understanding and analysis

If the RenderObject’s isRepaintBoundary is true, the RenderObject will be rendered using its own Layer object. If the Layer is not specified manually, the RenderObject will be rendered using the RenderObject’s isRepaintBoundary. The default is OffestLayer; False renders through the parent’s Layer object.

The Layer logic for paint is in the PaintingContext, and each paint context creates a new PaintingContext object

At the same time retrieving Canvans from the PaintingContext creates a PictureLayer that is synthesized into the Layer received when the PaintingContext is created

Now, let’s take a quick look at the Layer object

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 ]); . }Copy the code

Both Layer and RenderObject are subclasses of AbstractNode, and both parent objects are ContainerLayer. Layer also has two objects, nextSibling and previousSibling, which looks like a bidirectional list structure

addToScene(…) By subclass implementation, the Layer object is passed to engine, and the logic passed to Engine is in SceneBuilder

Layer has several subclasses that implement different rendering functions

PictureLayout is the main image drawing layer. TextureLayer is used for external texture implementation. It can be used for camera, video playback, OpenGL and other related operations. ContainerLayout is a composite Layer of each Layer

Layer of the refresh

In the previous article, we customized the RenderObject and overwrote its paint(…). Method, we draw the image we want by manipulating the Canvans object, and the Canvans is getting it from the PaintingContext. When we get the Canvans, we actually do a series of operations related to the 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);
  }
  ...
}
Copy the code

So you can see that we’ve created a new PictureLayer and added it to the _containerLayer, so how does our Layer end up being rendered?

The information is still available from the previous article, where we know that RendererBinding’s drawFrame() is used for layout and drawing

  @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; }}Copy the code

The final render is actually done with the compositeFrame(), and the renderView here is our root RenderObject and we can look at what does the compositeFrame() do

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(); . }... }Copy the code

You can see that by buildScene(…) Creates the Scene object, which, according to the comments, is equivalent to a Layer tree

Then render(…) the Window object. Method to render

  void render(Scene scene) native 'Window_render';
Copy the code

It directly calls engine’s method, which renders the image, and we’ll do a test to show what it does

The layer here is the root layer, which is actually a TransformLayer, and we can briefly look at the process of creating it

Root Layer creation process

RenderView is created using initInstances() in RendererBinding

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

RendererBinding -> initRenderView()

  void initRenderView() {
    assert(renderView == null);
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }
Copy the code

The root Layer is created in prepareInitialFrame()

RenderView -> prepareInitialFrame()

  void prepareInitialFrame() {... scheduleInitialLayout(); scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); . }Copy the code

The way to create is _updateMatricesAndCreateNewRootLayer ()

  TransformLayer _updateMatricesAndCreateNewRootLayer() { _rootTransform = configuration.toMatrix(); final TransformLayer rootLayer = TransformLayer(transform: _rootTransform); rootLayer.attach(this); .return rootLayer;
  }
Copy the code

Here we just briefly look at the process of creating the root Layer, which is a TransformLayer object.

We know the creation process, and to understand the refresh process we need to go back to the compositeFrame() method, which is done in buildScene(…). In the

buildScene(…)

As we know from the diagram above, the TransformLayer parent is OffsetLayer, and the OffsetLayer parent is ContainerLayer, and they don’t override buildScene(…). Method, so it will finally call ContainerLayer’s buildScene(…)

ui.Scene buildScene(ui.SceneBuilder builder) { ... updateSubtreeNeedsAddToScene(); addToScene(builder); . _needsAddToScene =false; .return scene;
  }
Copy the code

Can have a look at the first updateSubtreeNeedsAddToScene () method

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 voidupdateSubtreeNeedsAddToScene() {
    _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
  }
Copy the code

ConstraintLayer and Layer are the only Layer classes that have these methods, so we’re essentially iterating through all of the sublayer objects, Call their updateSubtreeNeedsAddToScene () to set the value of _needsAddToScene

As the name implies, this value indicates whether the change Layer needs to be added to the Scene, and if so, it is refreshed. It is set based on _needsAddToScene and alwaysNeedsAddToScene. When markNeedsAddToScene() is called, _needsAddToScene is set to true

UpdateSubtreeNeedsAddToScene () after the execution, then call addToScene (builder) method

addToScene(…)

It happens that the TransformLayer overwrites this method and does not call the superclass method

  @override
  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
    ...
    engineLayer = builder.pushTransform(
      _lastEffectiveTransform.storage,
      oldLayer: _engineLayer as ui.TransformEngineLayer,
    );
    addChildrenToScene(builder);
    builder.pop();
  }
Copy the code

The engineLayer object here is intended for reuse

You can see that the addChildrenToScene(Builder) method is called, which is only in ContainerLayer and has not been overridden

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; }}Copy the code

In this case, we iterate over the child and call their respective implementations of addToScene(…) Method, and whether to add Layer to the judgment of Scene, has been in previous updateSubtreeNeedsAddToScene (in) completed.

Note here that _addToSceneWithRetainedRendering (builder) is used in the reuse of previous _engineLayer when childOffset Offset. Zero

Then the Layer refresh process is finished at this point. Now that this article is almost over, let’s finish what I mentioned above and do a rendering test

Rendering test

You can test it here: 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();
}
Copy the code

Results the following

As you can see, we are displaying a pattern on the device without using any widgets. This is why Flutter is drawn by the SKia engine.

I won’t go into the rest of the Layer here, because it’s C++ after all

This is the last of the four trees we’ve covered, and it’s a very handy way to test an area that we’ve covered in the previous three articles but skipped: hot overloading

Bonus: Thermal overload

When you test through the above use case, click on the hot reload button to see if you get an error:

Error -32601 received from application: Method not found
Copy the code

And the colour of the pattern doesn’t change, which brings us to the method we mentioned earlier: Reassemble ()

The method you often see associated with Element and RenderObject is the core logic used to implement hot overloading

In BindingBase, we can see that a method is found: reassembleApplication(), which does the hot overload control

It calls the performReassemble() method

  @mustCallSuper
  @protected
  Future<void> performReassemble() {
    FlutterError.resetErrorCount();
    return Future<void>.value();
  }
Copy the code

This method is overridden in both WidgetsBinding and RendererBinding, if you’re interested, where they call hot-overload methods for Element and RenderObject

So, we want to implement hot overloading is actually very simple, look at the code:

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();
    returnFuture<void>.value(); }}Copy the code

The thermal overload effect is shown below, and you can test it on your device

That, of course, is the core logic of hot overloading.

Changes to the code file will be checked before the changes are made. For details, see this article:

This article ends here, but flutter is not finished yet, so stay tuned