其他分享
首页 > 其他分享> > 深入Flutter的Rendering层(一)--- 从runApp到三棵树的构建

深入Flutter的Rendering层(一)--- 从runApp到三棵树的构建

作者:互联网

本文所有源码版本为Flutter 1.9.1,部分源码会删除assert和debug部分
转载请注明出处,谢谢

#0、本系列文章

  1. 深入Flutter的Rendering层(一)— 从runApp到三棵树的构建
  2. 深入Flutter的Rendering层(二)— 布局layout与绘制paint

#1、前言
使用Flutter的Widget写了一段时间,用是挺好用,但就是越写心里越没底,感到疑惑的问题越来越多,例如:

如果你也有这些问题,本系列文章可以解答以上所有疑惑。本系列会沿着Widget层,往更深的Rendering层,从源码+一些个人理解,清晰地分析从Widget到渲染整个链路。

在分析过程中,我们也会接触到很多Rendering层才接触到的概念,例如RenderView、Layer、PaintingContext、PipelineOwner、BuildOwner、Window等等,这些概念会在解析过程中结合代码逐一说明。

#2、分层
下图是简化后的Flutter分层架构图:
架构图

一般Flutter开发时,只会用到上图中的Widgets以及其上面的Material & Cupertino,只做开发的话熟悉这块就足够了。但是如果想深入学习其原理,还需要往底层研究。本系列会大部分地涉猎到Rendering层的代码,也会少量地关联到dart:ui。

关于这个图的更多说明,请看这篇文章:https://www.jianshu.com/p/8e714a204898

#3、关于Widget、Element和RenderObject
我们都知道Widget都有一个对应的Element和RenderObject。Widget类里有一个必须实现的方法是createElement,通过这个方法我们就可以知道Widget对应的Element是哪个;

那么Widget对应的RenderObject呢?

图源:https://zhuanlan.zhihu.com/p/36577285

从上图可以知道:

同样,下文所说的Widget对应的Element和RenderObject就是上述的情况。
下面就从源码分析Rendering层。

#4、从runApp切入
“我写的Widget是怎么渲染出来呢?” — 接下来我们会围绕着这个问题去分析源码。

其实网上也有不少分析Flutter应用启动的高质量文章,如这篇http://gityuan.com/2019/06/29/flutter_run_app/
本文也有类似分析流程,但是会更多关注渲染层,并且一些相关的概念也会更深说明解释。

##4.1 入口

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Container(),
    );
  }
}

这是最常见的Flutter应用代码,其中MyApp Widget就是我们写的Widget。

##4.2 runApp

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()    //[见小节4.3]
    ..attachRootWidget(app)    //[见小节4.4]
    ..scheduleWarmUpFrame();    //[见小节4.5]
}

可以看到runApp只要调用了三个函数,下面都有详细分析。

##4.3 ensureInitialized

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
abstract class BindingBase {
  BindingBase() {
    ...
    initInstances();
    ...
  }
}

WidgetsFlutterBinding继承抽象类BindingBase,并且有7个mixin,因此在构造函数时会调用最后一个mixin类的initInstances()方法。同时由于每个mixin类都会调用super.initInstances(),所以最终会按倒序调用所有mixin类的initInstances(),最先调用是WidgetsBinding ,最后是GestureBinding。

###4.3.1 时序图
下面是ensureInitialized()方法的时序图,里面只画出了渲染相关的函数调用:
ensureInitialized 时序图

###4.3.2 RendererBinding.initInstances

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(      // 注释①
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    window    // 注释②
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();      // [见小节4.3.3]
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);    // 注释③
    _mouseTracker = _createMouseTracker();
  }
}

####注释①
构建一个PipelineOwner对象,并且传递一些回调函数作为入参。那么问题来了,PipelineOwner是什么东西?先看看源码的注释:

/// The pipeline owner manages the rendering pipeline.

源码注释很长,这里只截取了关键部分。从说明来看可以知道它是管理渲染通道的,那么怎么管理呢?可以看看调用PipelineOwner最核心的代码:
[源码路径:flutter/lib/src/rendering/binding.dart]

  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();    // 触发RenderObject执行布局
    pipelineOwner.flushCompositingBits();    //绘制之前的预处理操作
    pipelineOwner.flushPaint();    // 触发RenderObject执行绘制
    renderView.compositeFrame(); // 将需要绘制的比特数据发给GPU
    pipelineOwner.flushSemantics(); // 发送语义化给系统,用于辅助功能等
  }

从函数名drawFrame大概就可以知道PipelineOwner的核心作用就是将Widget对应RenderObject的布局绘制结果发送给GPU。这个方法会在下一篇文章进行分析。

一个Flutter App只有这里构建了PipelineOwner,全局只有一个PipelineOwner实例。

####注释②
这里使用了window变量,这个变量其实是dart:ui库暴露出来的一个全局变量,其类型为Window。同样看看源码注释:

[源码路径:flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart]
/// The most basic interface to the host operating system's user interface.

可以看出,Window是Flutter Framework连接宿主操作系统的接口。这句话有点抽象,看看它部分定义就能大概知道该类的作用:

class Window {

  // 当前设备的DPI,即一个逻辑像素显示多少物理像素,数字越大,显示效果就越精细保真。
  // DPI是设备屏幕的固件属性,如Nexus 6的屏幕DPI为3.5 
  double get devicePixelRatio => _devicePixelRatio;

  // Flutter UI绘制区域的大小
  Size get physicalSize => _physicalSize;

  // 当前系统默认的语言Locale
  Locale get locale;

  // 当前系统字体缩放比例。  
  double get textScaleFactor => _textScaleFactor;  

  // 当绘制区域大小改变回调
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Locale发生变化回调
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // 系统字体缩放变化回调
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  // 绘制前回调,一般会受显示器的垂直同步信号VSync驱动,当屏幕刷新时就会被调用
  FrameCallback get onBeginFrame => _onBeginFrame;
  // 绘制回调  
  VoidCallback get onDrawFrame => _onDrawFrame;
  // 点击或指针事件回调
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  // 调度Frame,该方法执行后,onBeginFrame和onDrawFrame将紧接着会在合适时机被调用,
  // 此方法会直接调用Flutter engine的Window_scheduleFrame方法
  void scheduleFrame() native 'Window_scheduleFrame';
  // 更新应用在GPU上的渲染,此方法会直接调用Flutter engine的Window_render方法
  void render(Scene scene) native 'Window_render';

  // 发送平台消息
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) ;
  // 平台通道消息处理回调  
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;

  ... //其它属性及回调

}

Window类包含了当前设备和系统的一些信息以及Flutter Engine的一些回调。

####注释③
addPersistentFrameCallback方法估计大家都有用过,就是注册一个持久的监听,当Flutter接收到一个"Vsync"信号时,最终会触发调用注册的PersistentFrameCallback。当然这里也一样。
[源码路径:flutter/lib/src/rendering/binding.dart]

void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
  }

因此,注释③这行代码的意思就是,当接收到"Vsync"信号并可以执行下一帧的时候,调用RendererBinding.drawFrame()。该方法的执行内容将在下一篇文章就行分析。

###4.3.3 initRenderView
继续回到源码分析;

[源码路径:flutter/lib/src/rendering/binding.dart]
  void initRenderView() {
    assert(renderView == null);
    renderView = RenderView(configuration: createViewConfiguration(), window: window);   
     // 注释①
    renderView.scheduleInitialFrame();      // [见小节4.3.4]
  }

  set renderView(RenderView value) {
    assert(value != null);
    _pipelineOwner.rootNode = value;     // 注释②
  }
[源码路径:flutter/lib/src/rendering/object.dart]
  AbstractNode get rootNode => _rootNode;
  AbstractNode _rootNode;
  set rootNode(AbstractNode value) {
    if (_rootNode == value)
      return;
    _rootNode?.detach();
    _rootNode = value;
    _rootNode?.attach(this);     // 注释③
  }

####注释①
这里创建了一个RenderView实例。你没猜错,这个实例也是全局只有一个的。那么RenderView是啥玩意呢?

[源码路径:/flutter/lib/src/rendering/view.dart]
/// The root of the render tree.
///
/// The view represents the total output surface of the render tree and handles
/// bootstrapping the rendering pipeline. The view has a unique child
/// [RenderBox], which is required to fill the entire output surface.
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
  RenderView({
    RenderBox child,
    @required ViewConfiguration configuration,
    @required ui.Window window,
  }) : assert(configuration != null),
       _configuration = configuration,
       _window = window {
    this.child = child;
  }
}

注释第一行已经表明身份,没错它就是我们RenderObject树的根节点,它也是集成于RenderObject。我们写的Widget对应的RenderObject就是挂在这个RenderView下,就是它的那个child属性(不是在这里挂上去的,挂上去的代码在下一节)

一个Flutter App只有这里构建了RenderView,当然全局只有一个RenderView实例。
####注释②
注释①赋值renderView会触发调用setter注释②,同样注释②也会触发调用setter注释③。
####注释③

[源码路径:flutter/lib/src/foundation/node.dart]
  @mustCallSuper
  void attach(covariant Object owner) {
    assert(owner != null);
    assert(_owner == null);
    _owner = owner;
  }

整个流程下来,可以整理为pipelineOwner的rootNode是renderView,而renderView也不客气地attach了pipelineOwner。简单来说就是互相持有对方的引用。

###4.3.4 scheduleInitialFrame

[源码路径:/flutter/lib/src/rendering/view.dart]
  /// Bootstrap the rendering pipeline by scheduling the first frame.
  ///
  /// This should only be called once, and must be called before changing
  /// [configuration]. It is typically called immediately after calling the
  /// constructor.
  void scheduleInitialFrame() {
    assert(owner != null);
    assert(_rootTransform == null);
    scheduleInitialLayout();    // [见4.3.5]
    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());    // [见4.3.6和4.3.7]
    assert(_rootTransform != null);
    owner.requestVisualUpdate();    // [见4.3.8]
  }

###4.3.5 scheduleInitialLayout

[源码路径:flutter/lib/src/rendering/object.dart]
  /// Bootstrap the rendering pipeline by scheduling the very first layout.
  ///
  /// Requires this render object to be attached and that this render object
  /// is the root of the render tree.
  ///
  /// See [RenderView] for an example of how this function is used.
  void scheduleInitialLayout() {
    assert(attached);
    assert(parent is! RenderObject);
    assert(!owner._debugDoingLayout);
    assert(_relayoutBoundary == null);
    _relayoutBoundary = this;    // 注释①
    assert(() {
      _debugCanParentUseSize = false;
      return true;
    }());
    owner._nodesNeedingLayout.add(this);    // 注释②
  }

####注释①
设置relayoutBoundary为自己。relayoutBoundary是在layout布局过程中用的,relayoutBoundary顾名思义就是布局的界限。如果一个 RenderObject 是 relayoutBoundary,就表示它的大小变化不会影响到 parent 的大小了,于是 执行重新布局时,就从该RenderObject开始重新layout,parent 不用重新布局了。【该具体逻辑分析将会在下一篇文章从源码侧说明。】

####注释②
PipelineOwner有个List属性_nodesNeedingLayout,在这里的RenderObject会在PipelineOwner执行flushLayout时,拿出来去执行layout方法。【该具体逻辑分析将会在下一篇文章从源码侧说明。】
###4.3.6 _updateMatricesAndCreateNewRootLayer

[源码路径:flutter/lib/src/rendering/view.dart]
    Layer _updateMatricesAndCreateNewRootLayer() {
    _rootTransform = configuration.toMatrix();
    final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
    rootLayer.attach(this);
    assert(_rootTransform != null);
    return rootLayer;
  }

这个函数主要创建了一个TransformLayer实例。TransformLayer继承与Layer,而Layer的源码注释如下:

[源码路径:flutter/lib/src/rendering/layer.dart]
/// A composited layer.
///
/// During painting, the render tree generates a tree of composited layers that
/// are uploaded into the engine and displayed by the compositor. This class is
/// the base class for all composited layers.

再看看它的关键调用代码:

[源码路径:flutter/lib/src/rendering/view.dart]
  /// Uploads the composited layer tree to the engine.
  ///
  /// Actually causes the output of the rendering pipeline to appear on screen.
  void compositeFrame() {
    Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
    try {
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);    // 这个layer就是上面创建的TransformLayer
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
      assert(() {
        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
        return true;
      }());
    } finally {
      Timeline.finishSync();
    }
  }

从这两处代码和注释可以大概理解到Layer其实就是RenderObject/Render树绘制出来的内容数据。当然Layer还有很多别的重要知识点,【同样将会在下一篇文章从源码侧说明。】

###4.3.7 scheduleInitialPaint

[源码路径:flutter/lib/src/rendering/object.dart]
  /// Bootstrap the rendering pipeline by scheduling the very first paint.
  ///
  /// Requires that this render object is attached, is the root of the render
  /// tree, and has a composited layer.
  ///
  /// See [RenderView] for an example of how this function is used.
  void scheduleInitialPaint(ContainerLayer rootLayer) {
    assert(rootLayer.attached);
    assert(attached);
    assert(parent is! RenderObject);
    assert(!owner._debugDoingPaint);
    assert(isRepaintBoundary);
    assert(_layer == null);
    _layer = rootLayer;
    assert(_needsPaint);
    owner._nodesNeedingPaint.add(this);
  }

这里就干了两件事

  1. 把刚才创建的TransformLayer赋值给_layer,而这个_layer就是最终执行compositeFrame方法用的layer,该方法会把layer的数据通过Window类渲染出来。
  2. PipelineOwner还有个List属性_nodesNeedingPaint,在这个List里的RenderObject会在PipelineOwner执行flushPaint时,拿出来去执行paint方法进行绘制。【该具体逻辑分析将会在下一篇文章从源码侧说明。】

###4.3.8 requestVisualUpdate

[源码路径:flutter/lib/src/rendering/object.dart]
    /// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
  ///
  /// Used to notify the pipeline owner that an associated render object wishes
  /// to update its visual appearance.
  void requestVisualUpdate() {
    if (onNeedVisualUpdate != null)
      onNeedVisualUpdate();    // 回调onNeedVisualUpdate方法
  }
[源码路径:flutter/lib/src/rendering/binding.dart]
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,    // onNeedVisualUpdate其实就是调用ensureVisualUpdate
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    ...
  }
}
[源码路径:flutter/lib/src/scheduler/binding.dart]
  void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();      // 最终会调用scheduleFrame
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

跟着注释说明,最终会调用scheduleFrame方法,而该方法从名字大概就可以知道它会触发调度下一帧渲染逻辑,最终就是触发drawFrame方法。

###4.3.9 ensureInitialized小结
至此,WidgetsFlutterBinding.ensureInitialized()已经分析了七七八八,我们总结一下,这一步还是做了不少事情。

##4.4 attachRootWidget
接下来分析runApp的第二步,WidgetsFlutterBinding.attachRootWidget。它其实是调用了mixin的WidgetsBinding.attachRootWidget()。
我已经整理了这一步的大致时序图。

###3.4.1 时序图
attachRootWidget时序图
我们跟着时序图一步一步分析。

###4.4.2 BuildOwner

[源码路径:flutter/lib/src/widgets/binding.dart]
  /// The [BuildOwner] in charge of executing the build pipeline for the
  /// widget tree rooted at this binding.
  BuildOwner get buildOwner => _buildOwner;
  final BuildOwner _buildOwner = BuildOwner();

又是一个新事物。在WidgetsBinding里有个字段构建了一个BuildOwner实例,从注释得知,它主要管理Widget树的构建build。描述依然非常抽象,从其他核心调用,我们大概可以知道Widget的build、mount和unmount等都跟它有关联。

一个Flutter App全局只有一个BuildOwner实例。

###4.4.3 attachRootWidget

[源码路径:flutter/lib/src/widgets/binding.dart]
  /// Takes a widget and attaches it to the [renderViewElement], creating it if
  /// necessary.
  ///
  /// This is called by [runApp] to configure the widget tree.
  ///
  /// See also [RenderObjectToWidgetAdapter.attachToRenderTree].
  void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(    // 注释①
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement);    // [见4.4.4]
  }

####注释①
这里构造了一个RenderObjectToWidgetAdapter实例,它继承于Widget,所以它本质就是Widget。然后我们写的Widget(也就是rootWidget变量)作为child参数传进去,而Render树的根结点RenderView也作为container参数传进去。

从这些可以看出来这个RenderObjectToWidgetAdapter有多厉害,没错,它就是我们Widget树的根结点,我们写的Widget就是挂在它下面,它对应的RenderObject就是RenderView。

###4.4.4 attachToRenderTree

[源码路径:flutter/lib/src/widgets/binding.dart]
  /// Inflate this widget and actually set the resulting [RenderObject] as the
  /// child of [container].
  ///
  /// If `element` is null, this function will create a new element. Otherwise,
  /// the given element will have an update scheduled to switch to this widget.
  ///
  /// Used by [runApp] to bootstrap applications.
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
    if (element == null) {
      owner.lockState(() {    // lockState,在下面代码执行过程中,禁止调用setState方法
        element = createElement();    // 构建一个Element实例,见[4.4.5]
        assert(element != null);
        element.assignOwner(owner);    // 将上面介绍的BuildOwner赋值给Element实例
      });
      owner.buildScope(element, () {      // 见[4.4.6]
        element.mount(null, null);      // 作为一个回调函数传给buildScope,见[4.4.6]
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element;
  }

这里值得一提的是,入参element,也就是renderViewElement这个时候还是为null,所以会走第一个判断逻辑。当走完这个函数之后,renderViewElement才会被赋值。

###4.4.5 createElement

[源码路径:flutter/lib/src/widgets/binding.dart]
  @override
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

构建了一个RenderObjectToWidgetElement实例,它继承于Element。这里的this是刚才的根Widget RenderObjectToWidgetAdapter。

至此,三棵树的根结点都出来了:

###4.4.6 buildScope

[源码路径:flutter/lib/src/widgets/framework.dart]
void buildScope(Element context, [VoidCallback callback]) {
  if (callback == null && _dirtyElements.isEmpty)
    return;
  Timeline.startSync('Build', arguments: timelineWhitelistArguments);
  try {
    if (callback != null) {
      _dirtyElementsNeedsResorting = false;
      callback();  //此处回调就是刚才说的mount() ,见4.4.7
    }
    ...
    _dirtyElements.sort(Element._sort);
    int dirtyCount = _dirtyElements.length;
    while (index < dirtyCount) {
        _dirtyElements[index].rebuild();     //对mark为dirty的Element执行rebuild操作,这块的核心会在下一篇文章说明
        ...
    }
  } finally {
    ...
    Timeline.finishSync();
  }
}

###4.4.7 mount

[源码路径:flutter/lib/src/widgets/binding.dart] RenderObjectToWidgetElement.mount
  @override
  void mount(Element parent, dynamic newSlot) {
    assert(parent == null);
    super.mount(parent, newSlot);    // 看下面
    _rebuild();    // 见4.4.8
  }
[源码路径:flutter/lib/src/widgets/framework.dart] RenderObjectElement.mount
@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);    // 再看下面
    _renderObject = widget.createRenderObject(this);     //  注释①
    assert(() { _debugUpdateRenderObjectOwner(); return true; }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);    // 本次分析不涉及这里
    _dirty = false;
  }
[源码路径:flutter/lib/src/widgets/framework.dart] Element.mount
// 这里是每个Element必经之路
@mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    assert(_debugLifecycleState == _ElementLifecycle.initial);
    assert(widget != null);
    assert(_parent == null);
    assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
    assert(slot == null);
    assert(depth == null);
    assert(!_active);
    _parent = parent;    // 赋值爸妈
    _slot = newSlot;     // slot参数,可以理解为是一个存数据的地方
    _depth = _parent != null ? _parent.depth + 1 : 1;    // 深度
    _active = true;    // 这个不用说吧
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;    // 这个就是BuildOwner,所以刚才创建的BuildOwner会通过这种方式为整棵树每一个结点都赋值上;
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._register(this);    // 注册并建立globalKey和Element的关联
    }
    _updateInheritance();
    assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
  }

####注释①
widget.createRenderObject()这个方法很重要。
这里就是我们写的Widget创建出RenderObject的地方,然后Element就可以存有RenderObject实例。createRenderObject是每一个RenderObjectWidget必须实现了,看看源码就可以知道每个Widget它对应的RenderObject是什么类别。

那么Element是怎样跟Widget关联起来呢?后面会介绍。(见4.4.10注释②)

那么我们的根Widget RenderObjectToWidgetAdapter是怎么实现createRenderObject方法:

  @override
  RenderObjectWithChildMixin<T> createRenderObject(BuildContext > context) => container;

而这里返回的container就是刚才我们用RenderView去赋值的。所以RenderView就是这里与RenderObjectToWidgetElement关联在一起。

###4.4.8 _rebuild

[源码路径:flutter/lib/src/widgets/binding.dart]   
void _rebuild() {
  try {
    _child = updateChild(_child, widget.child, _rootChildSlot);    // 见4.4.9
  } catch (exception, stack) {
    ...
  }
}

这里的widget就是根结点RenderObjectToWidgetAdapter,而widget.child就是我们写的Widget。因此这里就是把我们的Widget生成对应的Element,然后挂在根Element的child属性里。

###4.4.9 updateChild

[源码路径:flutter/lib/src/widgets/framework.dart]   
@protected
 Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
   ...
   if (newWidget == null) {
     if (child != null)
       deactivateChild(child);
     return null;
   }
   if (child != null) {
     if (child.widget == newWidget) {
       if (child.slot != newSlot)
         updateSlotForChild(child, newSlot);
       return child;
     }
     if (Widget.canUpdate(child.widget, newWidget)) {
       if (child.slot != newSlot)
         updateSlotForChild(child, newSlot);
       child.update(newWidget);
       ...
       return child;
     }
     deactivateChild(child);
   }
   return inflateWidget(newWidget, newSlot);    // 见4.4.10
 }

重要!!!
这个方法是Flutter Rendering层非常重要的一个函数。它在Widget的增删改过程都会触发调用。什么会触发Widget树的增删改?最简单的栗子就是初次应用启动,或者setState的时候;

下面是源码注释:

  /// Update the given child with the given new configuration.
  ///
  /// This method is the core of the widgets system. It is called each time we
  /// are to add, update, or remove a child based on an updated configuration.
  ///
  /// If the `child` is null, and the `newWidget` is not null, then we have a new
  /// child for which we need to create an [Element], configured with `newWidget`.
  ///
  /// If the `newWidget` is null, and the `child` is not null, then we need to
  /// remove it because it no longer has a configuration.
  ///
  /// If neither are null, then we need to update the `child`'s configuration to
  /// be the new configuration given by `newWidget`. If `newWidget` can be given
  /// to the existing child (as determined by [Widget.canUpdate]), then it is so
  /// given. Otherwise, the old child needs to be disposed and a new child
  /// created for the new configuration.
  ///
  /// If both are null, then we don't have a child and won't have a child, so we
  /// do nothing.
  ///
  /// The [updateChild] method returns the new child, if it had to create one,
  /// or the child that was passed in, if it just had to update the child, or
  /// null, if it removed the child and did not replace it.
  ///
  /// The following table summarizes the above:
  ///
  /// |                     | **newWidget == null**  | **newWidget != null**   |
  /// | :-----------------: | :--------------------- | :---------------------- |
  /// |  **child == null**  |  Returns null.         |  Returns new [Element]. |
  /// |  **child != null**  |  Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |

这里主要是处理Element能不能复用的逻辑,我们都知道Element创建很好资源,通过复用可以大大减少耗时。

具体的处理逻辑是:

  1. 如果新的Widget为null,那么就不需要Element,所以就都返回null;
    唯一要注意是如果旧的Element还在,需要移除掉Element与WIdget的关联,同时这个Element会保存在BuildOwner一个缓存_inactiveElements中,如果下次能用的时候会直接拿来用,避免创建耗时。(可以看4.4.10注释①)
  2. 如果新的Widget不为null,而旧的Element也不为null,那么需要通过Widget.canUpdate方法判断这个旧的Element能不能复用。而判断逻辑是Element对应的Widget和新Widget的类型和key相同就可以复用。
  3. 其他情况,就需要通过新的Widget去创建新的Element。具体请看下一小节。

毫无疑问,我们写的Widget肯定会走到情况3,也就是调用inflateWidget创建Element;

###4.4.10 inflateWidget

[源码路径:flutter/lib/src/widgets/framework.dart]   
@protected
 Element inflateWidget(Widget newWidget, dynamic newSlot) {
   final Key key = newWidget.key;
   if (key is GlobalKey) {
     final Element newChild = _retakeInactiveElement(key, newWidget);      // 注释①
     if (newChild != null) {
       newChild._activateWithParent(this, newSlot);
       final Element updatedChild = updateChild(newChild, newWidget, newSlot);
       return updatedChild;
     }
   }
   final Element newChild = newWidget.createElement();      // 注释②
   newChild.mount(this, newSlot);    // 注释③
   return newChild;
 }

这个方法同样重要。
如果你是Android开发的,那么对inflate肯定熟悉不过。Android里如果要把xml转成View,我们调用的接口就是LayoutInflater.inflate。而Flutter的这个接口意思也是差不多,Widget作为Flutter界面的蓝图,它更接近于xml,所以这个接口就是把Widget描述的样子inflate出来。

####注释①
_retakeInactiveElement这个方法内部会去刚才我提到的_inactiveElements缓存里找有没有能复用Element,然后直接拿来用。

####注释②
newWidget.createElement(),顾名思义,就是创建Widget对应的Element实例。其实Element的构造函数是必须要传Widget作为入参,在这里Element就与Widget进行了关联。
至此,我们已经知道Element是如何关联Widget和RenderObject了。

看看源码,可以了解每个Widget对应的Element和RenderObject类型。

####注释③
mount方法可能每个Element类型都会不一样,但是做的事情的核心就是将自己挂在parent Element上,同时为自己的Widget.child生成Element。

###4.4.11 attachRootWidget小结
runApp的第二步已经分析完成。到这一步很多事情就明朗起来。

同时,最初的这两个问题也已经有了答案:

  1. 问题:我写的Widget明明这么复杂,为啥可以被频繁build重新创建,性能还这么好?
    答:Element有高效的复用逻辑,即使是Widget频繁rebuild,但是通过上述的复用操作,可以尽可能减少耗时,提高性能;
  2. 问题:我们都知道有Widget树、Element树、RenderObject树,但是为什么要设计这么多层?Element树究竟有啥用?
    答:分析下来,我们已经很清楚Element在Widget和RenderObject之间的重要性。大家都知道Widget是immutable的,而RenderObject是可以改变而重绘的,这里面就需要Element去从中协调。Element就像一个倒贴的中间商,不仅不赚差价,还帮你做优化。
    – Widget通过Element进行mount,当Widget有变化时也是通过Element做更新改变–updateChild;
    – Element同时拥有Widget和RenderObject的引用,它们之间的沟通需要经过Element;

###4.5 scheduleWarmUpFrame
终于来到最后一步了,WidgetsFlutterBinding.scheduleWarmUpFrame。

[源码路径:flutter/lib/src/scheduler/binding.dart]   
void scheduleWarmUpFrame() {
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
    // We use timers here to ensure that microtasks flush in between.
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);    // 帧准备绘制
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame();    // 帧绘制
      // We call resetEpoch after this frame so that, in the hot reload case,
      // the very next frame pretends to have occurred immediately after this
      // warm-up frame. The warm-up frame's timestamp will typically be far in
      // the past (the time of the last real frame), so if we didn't reset the
      // epoch we would see a sudden jump from the old time in the warm-up frame
      // to the new time in the "real" frame. The biggest problem with this is
      // that implicit animations end up being triggered at the old time and
      // then skipping every frame and finishing in the new time.
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });

    // Lock events so touch events etc don't insert themselves until the
    // scheduled frame has finished.
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

这一步主要就是触发立刻绘制首帧,而不用等待下一个“Vsync”信号。
关于帧绘制handleDrawFrame相关分析,请看下一篇文章。

#5、参考资料

scnuxisan225 发布了20 篇原创文章 · 获赞 42 · 访问量 4万+ 私信 关注

标签:Widget,Element,assert,Rendering,runApp,源码,child,null,Flutter
来源: https://blog.csdn.net/scnuxisan225/article/details/104128264