跳至主要內容

Flutter APP 绘制过程简析

JI,XIAOYONG...大约 18 分钟

本文基于 Flutter 3.0

Flutter App 基于 Dart 语言编写,提供了一套简单易用的 API,可以让开发者在 Flutter 中快速开发出一个精美的 APP。那么在 Flutter 中是如何绘制一个 APP 呢,runApp 是怎么将我们编写的 Widget 等添加到手机上的呢?本文简单从 Widget,Element,RenderObjet 三者的关系来梳理一下 Flutter 的绘制过程。

让我们运行一个“最”简单的 Flutter App,分析一下在这个过程中涉及到的 Widget、Element、RenderObject 这三个 tree 的关系。

main() {
  runApp(const Center(
    // Center 非必须,为了让文本居中显得更清晰
    child: Text(
      "Hello center text!",
      textDirection: TextDirection.ltr, // 文本方向
    ),
  ));
}

上述代码的效果如下:

flutter_run_app_hello_center_text
flutter_run_app_hello_center_text

让我们使用 Flutter DevTools 看一下实际生成的 Widget Details Tree

[root]
 >renderObject:RenderView#a00a5

 Center
  alignment:Alignment.center
  widthFactor:null
  heightFactor:null
  >renderObject:RenderPositionedBox#94e0d

  Text
   "Hello center text!"
   textAlign:null
   textDirection:ltr
   locale:null
   softWrap:null
   overflow:null
   textScaleFactor:null
   maxLines:null
   textWidthBasis:null
   textHeightBehavior:null

   RichText
    textDirection:ltr
    softWrap:wrapping at box width
    maxLines:unlimited
    text:"Hello center text!"
    renderObject:RenderParagraph#71aa1

可以看到,除了我们在代码里面添加的 Center 和 Text 这两个 Widget 之外,还多出来好几个 Widget/RenderObject,当我们仔细查看具体的 Widget,可以看到其内部还有 XXXElement,BuildOwner 之类的字段:

Center
 alignment:Alignment.center
 widthFactor:null
 heightFactor:null
 renderObject:RenderPositionedBox#94e0d
 >_parent:RenderObjectToWidgetElement
 _debugReassembleConfig:null
 _notificationTree:null
 >_slot:Object
 _depth:2
 >_widget:Center
 >_owner:BuildOwner
 >_lifecycleState:_ElementLifecycle
 >_debugForgottenChildrenWithGlobalKey:_HashSet
 _inheritedWidgets:null
 _dependencies:null
 _hadUnsatisfiedDependencies:true
 _dirty:false
 _inDirtyList:false
 _debugBuiltOnce:false
 _debugA1lowIgnoredCallsToMarkNeedsBuild:false
 _debugDoingBuild:false
 >_ancestorRenderObjectElement:RenderObjectToWidgetElement
 >_child:StatelessElement

上述涉及到的几个类彼此之间到底是什么关系,我们的“Hello center text!”又是怎样才显示到屏幕上的,让我们接下来一个一个分析一下:

runApp

在执行 runApp 的时候主要执行了三步

// -> \lib\src\widgets\binding.dart

void runApp(Widget app) {
	// 创建 render tree 的根节点 RenderView
  WidgetsFlutterBinding.ensureInitialized()
		// 将我们的 app widget 绑定到 RenderView
    ..scheduleAttachRootWidget(app)
		// 安排屏幕帧绘制
    ..scheduleWarmUpFrame();
}

WidgetsFlutterBinding.ensureInitialized()

创建 RenderView 具体的逻辑在WidgetsFlutterBinding.ensureInitialized方法中:

// -> \lib\src\widgets\binding.dart

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

	static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding._instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

ensureInitialized方法中,如果WidgetsBinding._instance为 null 则会先用构造方法创建,因为WidgetsFlutterBinding继承自 BindingBase,所以实际上执行下方的方法:

// -> lib\src\foundation\binding.dart
abstract class BindingBase {
	BindingBase() {
    initInstances();
    initServiceExtensions();
  }

	
  
  void initInstances() {
  }
}

这里主要做了 2 件事,我们关注initInstances()方法,这个方法的主要逻辑都在他的子类中,也就是之前WidgetsFlutterBinding混合的几个 BindingBase 子类中,我们关注和屏幕渲染有关的 RendererBinding:

// -> lib\src\rendering\binding.dart

/// The glue between the render tree and the Flutter engine.
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  
  void initInstances() {
    super.initInstances();
    _instance = this;
    // 这里创建了 PipelineOwner,用来管理 rendering pipeline 也就是我们 app 中所有的 RenderObject
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    platformDispatcher
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    // 注意这里创建了 RenderView
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
    if (kIsWeb) {
      addPostFrameCallback(_handleWebFirstFrame);
    }
  }

  /// Creates a [RenderView] object to be the root of the
  /// [RenderObject] rendering tree, and initializes it so that it
  /// will be rendered when the next frame is requested.
  ///
  /// Called automatically when the binding is created.
  void initRenderView() {
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }

	set renderView(RenderView value) {
    assert(value != null);
    // 注意这里,将 renderView 设置为_pipeline 的根节点
    _pipelineOwner.rootNode = value;
  }
}

我们主要关注两件事:

  • 创建了用于管理渲染管道的 PipelineOwner _pipelineOwner

    Pipeline 是用来管理 rendering tree,其内部持有我们的 renderView 作为 rootNode,同时维护了_nodesNeedingLayout,_nodesNeedingCompositingBitsUpdate,_nodesNeedingPaint,_nodesNeedingSemantics 四个列表,当 flutter framework 每次需要往屏幕上绘制内容时会依次遍历这四个列表,将 RenderObject 绘制到屏幕上面。

  • 创建了 rendering tree 的根节点renderView ,并将其设置为_pipelineOwner的根节点

..scheduleAttachRootWidget(app)

此方法是WidgetsFlutterBinding的另外一个混合类WidgetsBinding负责具体实现:

WidgetsBindingscheduleAttachRootWidget 方法最后调用了attachRootWidget(Widget rootWidget)

// -> lib\src\widgets\binding.dart

/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {

 // 将 rootWidget 绑定到 renderViewElement
 void attachRootWidget(Widget rootWidget) {
    final bool isBootstrapFrame = renderViewElement == null;
    _readyToProduceFrames = true;
    // 用于将 rootWidget 绑定到 renderView 上面
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    )
   // 在此创建或者使用已有的 RenderObjectToWidgetElement,并作为根 Element
   // 并将 RenderObjectToWidgetAdapter 和 RenderView 与之绑定
   // 这里的_buildOwner 在 WidgetsBinding.initInstances 方法创建,用于管理 widget 框架的类
   .attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
    if (isBootstrapFrame) {
      // 如果是启动框架,就安排更新帧
      SchedulerBinding.instance.ensureVisualUpdate();
    }
  }
}

这里主要有 3 步:

  • 创建 RenderObjectToWidgetAdapter 包装 RenderView
  • attachToRenderTree方法中创建 RenderObjectToWidgetElement 并mount到 element tree 中(widget tree 实际上并不存在,而是通过 element tree 管理)
  • 需要的话安排一次 frame(刷新页面)

还需要注意一个新的角色buildOwner,这个对象全局唯一(一般由 parent 传给 child),在WidgetsBinding.initInstances方法创建,用来管理与 Widget tree 相关的类,实际上就是通过管理 Element 的插入,移除,更新来间接管理 Widget tree(对应我们在之前遇到的用来管理 rendering tree 的pipelineOwner ,这两个 Owner 管理着我们所说的 Flutter 的 Widget/Element/RenderObject“三”个 tree)。

RenderObjectToWidgetAdapter

前面我们知道renderView其实是一个 RenderObject,所以这里为他创建了一个对应的 Widget——RenderObjectToWidgetAdapter,其主要作用是将rootWidget(也就是我们最开始写的 Center Widget 及其 child)绑定到之前生成的renderView上面,并将renderView作为自己对应的 RenderObject。

// -> lib\src\widgets\binding.dart

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  /// Creates a bridge from a [RenderObject] to an [Element] tree.
  ///
  /// Used by [WidgetsBinding] to attach the root widget to the [RenderView].
  RenderObjectToWidgetAdapter({
    this.child,
    required this.container,
    this.debugShortDescription,
  }) :
   // 注意这里用 container 也就是 RenderView 创建了一个 GlobalObjectKey,
   // 在 RenderObjectToWidgetElementmount 的时候会用到
   super(key: GlobalObjectKey(container));

   
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

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

RenderObjectToWidgetAdapter.createRenderObject 返回的就是container 也就是我们的 RenderView。

attachToRenderTree

// -> lib\src\widgets\binding.dart
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      owner.lockState(() {
        // 创建 RenderObjectToWidgetElement,并将 RenderObjectToWidgetAdapter 与之绑定
        element = createElement();
        assert(element != null);
        // 创建好 Element 之后,将 BuildOwner 与之绑定
        element!.assignOwner(owner);
      });
      owner.buildScope(element!, () {
        // 这里最终会通过 updateChild 方法将 rootWidget 对应的 Element 插入到
        // RenderObjectToWidgetElement 下面,在 rootWidget 中第一个 RenderObjectElement
        // 的 mount 方法中,通过 attachRenderObject(newSlot) 将自己的 renderObject 绑定到 renderView
        element!.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }
}

RenderObjectToWidgetAdapterattachToRenderTree 方法中,创建对应的RenderObjectToWidgetElement 与自己绑定,并且同时也将rootWidget和之前创建的 rendering tree 的根节点renderView绑定。

|RenderObjectToWidgetAdapter|RenderObjectToWidgetElement|RenderView|

我们再来看一下RenderObjectToWidgetElement调用的父类RenderObjectElement.mount方法:

// -> lib\src\widgets\framework.dart

abstract class RenderObjectElement extends Element {

  
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    // 本例中这里实际上获取到的是 RenderView
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
    // 将 RenderView 绑定到指定 newSlot(这里是 null) 中
    attachRenderObject(newSlot);
    _dirty = false;
  }

  
  void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    // 这里因为 RenderView 是根节点,所以_ancestorRenderObjectElement 和 parentDataElement 都为 null
    // 但是对于 RenderView 下级的节点,比如本例中的 Center Widget,他对应的祖先节点就是持有 RenderView
    // 的 RenderObjectToWidgetElement,所以这里会将 CenterWidget 的 RenderPositionedBox
    // 作为 RenderView 的 child
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null)
      _updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);
  }
}

此外,还会调用的Element.mount方法:

// -> lib\src\widgets\framework.dart
// Element 的方法:

   void mount(Element? parent, Object? newSlot) {
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) {
      // Only assign ownership if the parent is non-null. If parent is null
      // (the root node), the owner should have already been assigned.
      // See RootRenderObjectElement.assignOwner().
      _owner = parent.owner;
    }
    assert(owner != null);
    // 这里将 RenderObjectToWidgetElement 注册到 owner 中,key 是创建 RenderObjectToWidgetAdapter 时候创建的 GlobalObjectKey
    final Key? key = widget.key;
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
    _updateInheritance();
    attachNotificationTree();
  }

可以看到,这里将RenderObjectToWidgetElement 注册到了 BuildOwner 中

RenderObjectToWidgetElementmount方法执行时,除了调用父类的mount方法外,还会触发_rebuild() 方法:

class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObjectElement {{
  Element? _child;
  void _rebuild() {
    try {
  // 在这里分析可知,这里的 widget 即`RenderObjectToWidgetElement` 的 widget,也就是
  // `RenderObjectToWidgetAdapter`,他的 child 也就是 rootWidget
  // 所以 updateChild 传入的值分别是 null,RenderObjectToWidgetAdapter.child
  // 会创建 rootWidget 对应的 element 并将其作为当前 element 的 child
      _child = updateChild(_child, (widget as RenderObjectToWidgetAdapter<T>).child, _rootChildSlot);
    } catch (exception, stack) {
      ...
  }
}

_rebuild方法中,我们可以看到,在WidgetsBinding.attachRootWidget方法中给 RenderObjectToWidgetAdapter 作为child参数传入的rootWidget(也即我们示例中的CenterWidget),在这里被传入了 RenderView 对应的 RenderObjectToWidgetElement 的child中(这里的过程我们下面 Center 一节再分析),从而将其插入到 Flutter 的渲染树中。

这样 RenderView(RenderObject)就有了对应的WidgetElement,并且有了自己的child

..scheduleWarmUpFrame()

这个方法则是尽快安排一个 frame 以便在屏幕下次刷新的时候显示 app 的内容(在 app 启动之后的第一次!!!),这样我们的 app 启动了,我们写的内容也能正常显示到屏幕上。

通过上述分析,我们可以得知,runApp 方法执行之后,创建了RenderView对象,并将其作为整个 Flutter APP 的 RenderObject rendering tree 的根节点(后续所有的 Widget 创建的 RenderObject 都是在 RenderView 的下层),并且初始化它以便在下一帧的时候对其进行渲染。


分析完了runApp,我们再来看一下刚刚提到的几个类,以及他们是如何添加到我们的 flutter app 中的。

RenderView

先看一下在最顶层的RenderViewopen in new window

↓[root]
 >renderObject:RenderView#a00a5
 parent:null
 _debugReassembleConfig:null
 _notificationTree:nul1
 slot:null
 depth:1
 _widget:RenderObjectToWidgetAdapter
 >_owner:BuildOwner
 _lifecycleState:_ElementLifecycle
 _debugForgottenChildrenWithGlobalKey:_HashSet
 _inheritedWidgets:nul1
 _dependencies:null
 _hadUnsatisfiedDependencies:false
 _dirty:false
 _inDirtyList:false
 _debugBuiltOnce:false
 _debugA1lowIgnoredCallsToMarkNeedsBuild:false
 _debugDoingBuild:false
 _ancestorRenderObjectElement:null
 _child:SingleChildRenderObjectElement
 _newWidget:null

查阅源码可知,RenderView 是 RenderObject,一般情况下是 Flutter 的根 View,表示整个 rendering tree 的 output surface,处理引导着 render pipeline。

RenderView 有且仅有一个 RenderBox 类型的child,他会强制将childsize改为 RenderView 初始化时候的入参configuration的值(一般是当前window也就是手机屏幕的逻辑像素size)。

Center

上节我们说道,Center Widget 通过RenderObjectToWidgetElement.updateChild(最终调用 Element 同名方法)方法插入到渲染树中,下面我们详细分析一下这个过程:

updateChild中,因为child==null,而newWidget也就是 Center 不为null,所以直接使用inflateWidget(newWidget, newSlot)创建新的 Element 并作为 RenderObjectToWidgetElement 的_child,而作为第一次创建的 Center,在Element.inflateWidget方法中大概会执行下面几步:

// -> lib\src\widgets\framework.dart
// Element 的 inflateWidget 方法:

final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;

也就是这里先执行了Center.createElement方法创建 Element,然后调用此Element.mount方法将 Element 添加到 Element tree。

让我们再看一下 Center 的 Widget Details Tree:

Center
 alignment:Alignment.center
 widthFactor:null
 heightFactor:null
 renderObject:RenderPositionedBox#94e0d
 >_parent:RenderObjectToWidgetElement
 _debugReassembleConfig:null
 _notificationTree:null
 >_slot:Object
 _depth:2
 >_widget:Center
 >_owner:BuildOwner
 >_lifecycleState:_ElementLifecycle
 >_debugForgottenChildrenWithGlobalKey:_HashSet
 _inheritedWidgets:null
 _dependencies:null
 _hadUnsatisfiedDependencies:true
 _dirty:false
 _inDirtyList:false
 _debugBuiltOnce:false
 _debugA1lowIgnoredCallsToMarkNeedsBuild:false
 _debugDoingBuild:false
 >_ancestorRenderObjectElement:RenderObjectToWidgetElement
 >_child:StatelessElement

可以看到 Center 的_parent_ancestorRenderObjectElement是 RenderObjectToWidgetElement,_depth是 2,这个和我们最初的分析一致,因为 Center(其实严格来说,是 Center Widget 的(或子级的)RenderObject)是 RenderView 的child

我们接下来主要关注一下几个属性:

  • alignment: Alignment.center
  • renderObject: RenderPositionedBox
  • _widget: Center
  • _child: StatelessElement

先看一下 Center 的源码:

// -> lib\src\widgets\basic.dart

class Center extends Align {
  /// Creates a widget that centers its child.
  const Center({ Key? key, double? widthFactor, double? heightFactor, Widget? child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}

Center 代码十分简单,主要的逻辑在他的父类 Align 中:

// -> lib\src\widgets\basic.dart

class Align extends SingleChildRenderObjectWidget {
	const Align({
    Key? key,
    // 这里 alignment 默认是居中
    this.alignment = Alignment.center,
    this.widthFactor,
    this.heightFactor,
    Widget? child,
    }) : super(key: key, child: child);

  
  RenderPositionedBox createRenderObject(BuildContext context) {
    // Center 的父类可以创建自己的 RenderObject
    return RenderPositionedBox(
      alignment: alignment,
      widthFactor: widthFactor,
      heightFactor: heightFactor,
      textDirection: Directionality.maybeOf(context),
    );
  }

  
  void updateRenderObject(BuildContext context, RenderPositionedBox renderObject) {
    renderObject
      ..alignment = alignment
      ..widthFactor = widthFactor
      ..heightFactor = heightFactor
      ..textDirection = Directionality.maybeOf(context);
  }
}

Align 其实是一个SingleChildRenderObjectWidget ,对应的 Element 是SingleChildRenderObjectElement,他创建的 RenderObject 是RenderPositionedBox

SingleChildRenderObjectElement 是一个RenderObjectElement 也就意味着他在 rendering tree 有一个关联的 RenderObject 负责 layout,painting 以及 hit-test。

回到我们的 Center Widget 中:

alignment: Alignment.center

Alignment.center是在创建 Center 的时候默认设置的对齐方式

renderObject: RenderPositionedBox

RenderPositionedBox是 Center Widget 对应的 RenderObject,在SingleChildRenderObjectWidget.mount 的时候创建。其本身并不在屏幕上绘制肉眼可见的内容,而是将 child 按照指定的对齐方式进行定位。

RenderPositionedBox 的继承关系:RenderPositionedBoxRenderAligningShiftedBoxRenderShiftedBoxRenderBoxRenderObject

RenderPositionedBox可以按照给定的AlignmentGeometry定位 child。在本例中,他的几个属性如下:

  • alignment: Alignment.center
  • _owner: PipelineOwner
  • _parent: RenderView
  • _child: RenderParagraph

前三个属性含义很明显,这里我们注意到他的_child并不是我们预期的Text,这个原因我们后面再分析。

_widget: Center

其实通过上述的分析,我们应该已经知道,我们在 Widget Details Tree 中看到的 Center 其实是 Center Widget 对应的 Element,也就是SingleChildRenderObjectElement

其继承关系:SingleChildRenderObjectElementRenderObjectElementElement

根据 Element 的定义,这里的 Widget 是在 Widget 创建SingleChildRenderObjectElement的时候传入的:

// -> lib\src\widgets\framework.dart

Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

// -> lib\src\widgets\framework.dart
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const SingleChildRenderObjectWidget({ Key? key, this.child }) : super(key: key);

  /// The widget below this widget in the tree.
  ///
  /// {@macro flutter.widgets.ProxyWidget.child}
  final Widget? child;

  
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

然后这个 Element 在上述WidgetsBinding.attachRootWidget步骤中通过一系列操作,最终在 RenderObjectToWidgetElement 的updateChild方法被创建并被BuildOwner 插入到 tree 中。

这里的_widget才真正对应着我们在runApp里面传入的 Center Widget,他的child也正是我们的 Text。

Center
 alignment:Alignment.center
 widthFactor:null
 heightFactor:null
 >renderObject:RenderPositionedBox#6e802
 >_parent:RenderObjectToWidgetElement
 _debugReassembleConfig:null
 _notificationTree:null
 _slot:Object
 denth:2 _
 [[[widget:Center]]] //注意这里
  key:null
  location:_Location
  [[[child:Text]]] //注意这里
   key:null
   >_location:_Location
   data:'Hello center text!'
   textSpan:null
   style:null
   strutStyle:null
   textAlign:null
   >textDirection:TextDirection
   locale:null
   softWrap:null
   overflow:null
   textScaleFactor:null
   maxLines:null
   semanticsLabel:null
   textWidthBasis:null
   textHeightBehavior:null
  >alignment:Alignment
  widthFactor:null
  heightFactor:nul1
 >_owner:BuildOwner

_child: StatelessElement

Center 对应的 Element 的_child是一个 StatelessElement,按照我们上一步的分析,StatelessElement 应该是 Text Widget 创建,事实也确实如此:

↓_child: StatelessElement
 >_parent: SingleChildRenderObjectElement
 debugReassembleConfig: null
 _notificationTree: null
 slot: null
 depth: 3
 >_widget: Text
 >_owner: BuildOwner
 >_lifecycleState: _ElementLifecycle
 >_debugForgottenChildrenWithGlobalKey: _HashSet
 _inheritedWidgets: null
 dependencies: null
 _hadUnsatisfiedDependencies: true
 _dirty: false
 _inDirtyList: false
 debugBuiltOnce: false
 _debugAllowIgnoredCallsToMarkNeedsBuild: false
 >_child: MultichildRenderObjectElement
 debugDoingBuild: false

让我们分析一下这个_child的赋值过程:

// -> lib\src\widgets\framework.dart
class SingleChildRenderObjectElement extends RenderObjectElement {
	
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, (widget as SingleChildRenderObjectWidget).child, null);
  }
}

在 Center 对应的 Element——SingleChildRenderObjectElementmount的时候,同时也会更新child(本例中 Center 的child是 Text),这里调用的是 Element 的updateChild方法,他的逻辑如下:

|                     | **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]. |

updateChild的逻辑分为 4 种情况:其余情况都比较简单,只有newWidget != null或者child != null的时候需要判断一下,如果可以更新就更新否则就创建新的 Element,可以分为下面这几种情况:

  • child.widget == newWidget:两个是同一个对象,就只更新childslot
  • Widget.canUpdate(child.widget, newWidget):二者的runtimeTypekey一样,就调用child.update(newWidget)更新child._widget,必要时更新childslot
  • 否则创建新的element并替代

到这里跟 Center 插入到 render tree 的步骤一样,将 Text 插入到了 tree 中。

Text

接下来我们分析一下 Text 是如何被加入 Widget Details Tree 的。

其继承关系:TextStatelessWidgetWidget

Text 是 StatelessWidget,他的内容比较简单,主要的逻辑都在build方法中:

// -> \lib\src\widgets\text.dart

class Text extends StatelessWidget {

  const Text(
    String this.data, {
    Key? key,
    ...
  }) :
       textSpan = null,
       super(key: key);

  
  Widget build(BuildContext context) {
    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
    TextStyle? effectiveTextStyle = style;
    if (style == null || style!.inherit)
      effectiveTextStyle = defaultTextStyle.style.merge(style);
    if (MediaQuery.boldTextOverride(context))
      effectiveTextStyle = effectiveTextStyle!.merge(const TextStyle(fontWeight: FontWeight.bold));
    // 注意这里返回了 RichText
    Widget result = RichText(
      textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
      textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
      ...
      text: TextSpan(
        style: effectiveTextStyle,
        text: data,
        children: textSpan != null ? <InlineSpan>[textSpan!] : null,
      ),
    );
    ...
    return result;
  }
}

同样,作为 StatelessWidget,他也创建了一个StatelessElement

其继承关系:StatelessElementComponentElementElement

按照之前的分析,Text 插入到 tree 中执行的方法分别是Text.createElement和 Text 对应的 Element——StatelessElement.mount方法:

Text 是 StatelessWidget 的子类,他的主要逻辑都在 StatelessWidget:

// -> lib\src\widgets\framework.dart

abstract class StatelessWidget extends Widget {
 const StatelessWidget({ Key? key }) : super(key: key);
  
  StatelessElement createElement() => StatelessElement(this);
  
  Widget build(BuildContext context);
}

可以看到其createElement创建的是 StatelessElement,也就是说 Text 插入到 Center 过程主要在 StatelessElement 中。

StatelessElement.mount方法主要逻辑在 ComponentElement 中,这个方法除了调用 Element 同名方法外,还调用了ComponentElement._firstBuild()Element.rebuild()ComponentElement.performRebuild()

// -> lib\src\widgets\framework.dart
// ComponentElement 类中的方法

void performRebuild() {
    Widget? built;
    try {
      // 这里调用 Element 对应的 Widget 的 build 方法创建 Widget,也就是 RichText
      built = build();
    } catch (e, stack) {
     ...
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    // 将上述创建的 Widget:built 经过处理后赋值给 Element 的_child
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      built = ErrorWidget.builder(...);
      _child = updateChild(null, built, slot);
    }
  }

这里的主要有两个步骤:

  • 调用ComponentElement.build方法,生产 Widget(本例中,间接调用了 Text 的build方法)
  • 调用ComponentElement.updateChild方法,更新child(最终执行的是 Element 同名方法逻辑)

到目前为止,我们的 Widget/Element/RenderObject tree 如下(第四级 RichText 下面再分析):

**Widget**:       RenderObjectToWidgetAdapterCenterText*RichText*

**Element**:      RenderObjectToWidgetElementSingleChildRenderObjectElementStatelessElement*MultiChildRenderObjectElement*

**RenderObject**: RenderViewRenderPositionedBox[X]*RenderParagraph*

注:
1. 这里的[X]实际上不存在,只是为了和Text对应表示这里本应该有一个对应的RenderObject
2. 最后一列*RichText*对应的节点目前还没有分析到,此处仅做提前展示

不难看出,在从定往下数第三层也就是 Text 对应的这一级中,RenderObject tree 这里并没有对应的对象,在上面的分析中,我们也看到了 StatelessWidget 本身并没有创建 RenderObject 的方法。实际上,Widget 分为多个种类,只有 RenderObject 类及其子类才会创建 RenderObject:

Untitled
Untitled

RichText

终于到了我们这个 app 真正在屏幕上显示的内容这里了,上面我们分析到,Text 作为 StatelessWidget,本身并不会产生可以在屏幕上绘制的 RenderObject,而是通过他的build方法返回一个可以产生 RenderObject 的 Widget,在本例中,这个 Widget 就是 RichText:

// -> lib\src\widgets\basic.dart

class RichText extends MultiChildRenderObjectWidget {
	
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection != null || debugCheckHasDirectionality(context));
    return RenderParagraph(text,
      textAlign: textAlign,
      textDirection: textDirection ?? Directionality.of(context),
      softWrap: softWrap,
      overflow: overflow,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      strutStyle: strutStyle,
      textWidthBasis: textWidthBasis,
      textHeightBehavior: textHeightBehavior,
      locale: locale ?? Localizations.maybeLocaleOf(context),
    );
  }
}

RichText 继承自MultiChildRenderObjectWidget ,如上节分析的,是一种RenderObjectWidget,它创建了真正在屏幕上渲染的 RenderObject——RenderParagraph

// -> lib\src\rendering\paragraph.dart

class RenderParagraph extends RenderBox
    with ContainerRenderObjectMixin<RenderBox, TextParentData>,
             RenderBoxContainerDefaultsMixin<RenderBox, TextParentData>,
                  RelayoutWhenSystemFontsChangeMixin {}

上面说道,Text 本身作为 StatelessWidget 并不产生 RenderObject,那么这里的 RenderParagraph 是如何找到并插入到 rendering tree 中的呢?

带着这个疑问,我们看一下MultiChildRenderObjectWidget 创建的MultiChildRenderObjectElement

// -> lib\src\widgets\framework.dart

class MultiChildRenderObjectElement extends RenderObjectElement {
	
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    final MultiChildRenderObjectWidget multiChildRenderObjectWidget = widget as MultiChildRenderObjectWidget;
    // 本例中不涉及 children
    final List<Element> children = List<Element>.filled(multiChildRenderObjectWidget.children.length, _NullElement.instance);
    Element? previousChild;
    for (int i = 0; i < children.length; i += 1) {
      final Element newChild = inflateWidget(multiChildRenderObjectWidget.children[i], IndexedSlot<Element?>(i, previousChild));
      children[i] = newChild;
      previousChild = newChild;
    }
    _children = children;
  }
}

这里可以看到,在 MultiChildRenderObjectElement 的mount方法中:

  • 调用父类mount方法
  • 遍历了所有的children将其插入到 MultiChildRenderObjectElement 中。

在前面的 Text 源码中,我们注意到给只给RichText.text赋值了,RichText 的textSpanchildren都是null,所以后面对children的处理在本例中不涉及,让我们看一下super.mount(parent, newSlot)方法:

// -> lib\src\widgets\framework.dart

abstract class RenderObjectElement extends Element {
  
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    // 这里调用对应的 RenderObjectWidget 创建_renderObject
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
    // 将其绑定到 rendering tree 中
    attachRenderObject(newSlot);
    _dirty = false;
  }
}

这里主要有 2 步:

  • 通过widget.createRenderObject 创建_renderObject,本例中就是用 RichText 创建了 RenderParagraph
  • 调用RenderObjectElement.attachRenderObject方法将_renderObject插入到 rendering tree

让我们看一下 attachRenderObject 的实现:

// -> lib\src\widgets\framework.dart
// RenderObjectElement 类的方法
  
  void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    // 向上遍历,找到父级节点中最近的 RenderObjectElement
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    // 将 renderObject 插入
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null)
      _updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);
  }

  RenderObjectElement? _findAncestorRenderObjectElement() {
    Element? ancestor = _parent;
    while (ancestor != null && ancestor is! RenderObjectElement)
      ancestor = ancestor._parent;
    return ancestor as RenderObjectElement?;
  }

可以看到,在attachRenderObject方法中插入的方式很简单:先在当前 tree 中向上找到父级中离得最近的 RenderObjectElement,在本例中是 Center 这个 Widget 对应的 SingleChildRenderObjectElement(注意不是创建 RichText 的 Text),然后调用其insertRenderObjectChild方法将当前的 RenderParagraph 插入到 rendering tree 中:

// -> lib\src\widgets\framework.dart
class SingleChildRenderObjectElement extends RenderObjectElement {
	
  void insertRenderObjectChild(RenderObject child, Object? slot) {
    final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
    renderObject.child = child;
  }
}

在 SingleChildRenderObjectElement 的insertRenderObjectChild方法中先是查找当前 Element 持有的renderObject,然后将我们传入的 RichText 的 RenderObject——RenderParagraph 赋值给renderObject.child

到这里,我们的所有 Widget 在 Element 的组织下,将对于的 RenderObject 添加到 Rendering Tree 中,他们的关系如下:

**Widget**:       RenderObjectToWidgetAdapterCenterTextRichText

**Element**:      RenderObjectToWidgetElementSingleChildRenderObjectElementStatelessElementMultiChildRenderObjectElement

**RenderObject**: RenderViewRenderPositionedBoxRenderParagraph

这样,当屏幕刷新的时候,这些内容便绘制在屏幕上面。

总结

runApp方法中,WidgetsFlutterBinding 作为将 flutter framework 绑定到 flutter engine 的粘合剂:

  • ensureInitialized方法中创建了_pipelineOwner(管理 rendering tree)、renderViewbuildOwner(通过管理 Element tree 间接管理 widget tree),并将renderView设置为_pipelineOwner的根节点。

  • scheduleAttachRootWidget方法中,为renderView创建并绑定了对应的 Widget(RenderObjectToWidgetAdapter)和 Element(RenderObjectToWidgetElement)。然后通过RenderObjectToWidgetElement.mount方法,将之前创建的buildOwner与自己绑定。

    并且将我们在runApp传入的 WidgetrootWidget(也就是本例中的 Center Widget)对应的 Element 添加为 RenderObjectToWidgetElement 的子节点。并依此将 Text、Text 内部的 RichText 等对应的 Element 都加入到 Element tree 中,直到遍历完整个 Widget tree。

  • scheduleWarmUpFrame方法中安排在下一次屏幕刷新的时候将我们的内容展示在屏幕上面。

下面是我们这个“最”简单的 Flutter App 的结构示意:

参考资料

Flutter - Dart API docsopen in new window

Flutter, what are Widgets, RenderObjects and Elements? - Norbert Kozsir | Flutter Europeopen in new window

Flutter Widgets Explained | Understand How Flutter Works!open in new window

深入浅出 Flutter Framework 之 PipelineOwneropen in new window

文章标题:《Flutter APP 绘制过程简析》
本文作者: JI,XIAOYONG
发布时间: 2022/06/25 09:03:30 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/4cbcfe72.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8