ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2020-01-31 22:08:08  阅读:313  来源: 互联网

标签:Widget Element assert Rendering runApp 源码 child null Flutter


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

#0、本系列文章

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

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

  • 我写的Widget是怎么渲染出来呢?
  • 为什么有时候Container是撑满父亲,有时候又不是?
  • 我写的Widget明明这么复杂,为啥可以被频繁build重新创建,性能还这么好?
  • 我们都知道有Widget树、Element树、RenderObject树,但是为什么要设计这么多层?Element树究竟有啥用?
  • 那个渲染溢出(overflow)的错误提示好烦啊,它是怎么出来的?

如果你也有这些问题,本系列文章可以解答以上所有疑惑。本系列会沿着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都是继承于RenderObjectWidget,而每个RenderObjectWidget都必须实现createRenderObject方法用来构建对应的RenderObject。这个就是展示型Widget对应的RenderObject
  • 而我们平时写的Widget还有很多现成的Widget(如Container、Text)都是组合型Widget(Stateless和Stateful)。如果追踪源码你会发现组合型Widget的build方法中会构建真正的RenderObjectWidget。因此对于组合型Widget,它对应的RenderObject就是这个RenderObjectWidget构建的RenderObject。

同样,下文所说的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()已经分析了七七八八,我们总结一下,这一步还是做了不少事情。

  • 这一步主要是初始化了整个RenderObject树到渲染通道的框架。
  • 首先创建了PipelineOwner实例,通过PipelineOwner去管理渲染通道的执行流程;然后创建了RenderView实例,并且绑定了PipelineOwner实例,而Window实例也在RenderView关联起来。最后,为RenderView构建了一个绘制的载体TransformLayer,然后触发调度下一帧scheduleFrame。
  • 这些串在一起就是,RenderView的内容(也就是我们写的Widget对应RenderObject内容)绘制在TransformLayer上,然后通过PipelineOwner管理整个渲染调度流程,把TransformLayer上绘制好的内容数据,通过Window类发送到更底层/GPU渲染出来。

##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。

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

  • Widget树的根结点是RenderObjectToWidgetAdapter;
  • Element树的根结点是RenderObjectToWidgetElement;
  • RenderObject树的根结点是RenderView;
    他们仨一一对应。

###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的第二步已经分析完成。到这一步很多事情就明朗起来。

  • 首先构造Widget树和Element树的根结点,分别是RenderObjectToWidgetAdapter和RenderObjectToWidgetElement,而他们对应的RenderObject就是上一步构造的Render树根结点RenderView;
  • 然后把我们写的Widget作为rootWidget通过mount方法挂载到了树的根结点上。到这里我们已经解答“我写的Widget是怎么渲染出来呢?”的一半了。(还有一半下一篇文章进行分析)

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

  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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有