Flutter | 三棵树构建流程分析(下)

简介: Flutter | 三棵树构建流程分析(下)

我们大致分析一下


首先是调用到 MultiChildRenderObjectElement 的 mount 方法中,先将传入的 parent 插入到树中,

接着在 RenderObjectElement 中 创建了一个 renderObject 并添加到渲染树中插槽的指定位置

最后回到 MultiChildRenderObjectElement 中遍历所有的 child,并且调用 inflateWidget 创建 child并插入到指定的插槽中。


StatefulElement


相比于 StatelessWidget ,StatefulWidget 中多了一个 State,在 StatefulWidget 中会通过 createState 来创建一个 State,如下所示


abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key? key }) : super(key: key);
  @override
  StatefulElement createElement() => StatefulElement(this);
  @protected
  @factory
  State createState(); // ignore: no_logic_in_create_state, this is the original sin
}


通过上面代码可以看出 StatefulWidget 对应的 Element 正是 StatefulElement。


class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    assert(state._element == null);
    state._element = this;
    state._widget = widget;
  }
  State<StatefulWidget> get state => _state!;
  State<StatefulWidget>? _state;
  @override
  Widget build() => state.build(this);
}


在 StatefulElement 中通过调用 widget.createState 获取到了 state 对象


StatefulElement 在创建完成后,framework 就会调用 StatelessElement 中的 mount 方法了。


和 StatelessElement 不同的是:


Statelesselement 中是通过调用 widget.build(this) 方法

StatefulElement 中是通过调用 state.build(this) 方法


总结


通过上面的分析,我们知道了 Element 的生命周期以及它的调用过程。并且如果你仔细观察上面举例的三个 Element 就会发现,上面的这三种 Element 可以分为两类,分别是组合类和绘制类。


组合类一般都继承自 StatelessElement 或者 StatefulElement,它们都属于组合类,并不参与绘制,内部嵌套了多层 Widget,查看它们的 mount 方法,就会发现其中并没有创建 renderObject 并添加到渲染树。例如 StatelessWidget ,StatefulWidget,Text,Container ,Image 等。

绘制类的特征顾名思义就是参加绘制了,在mount 方法中,会有创建 renderObject 并 attachRenderObject 渲染树中。如 Column ,SizedBox,Transform 等。

其实还有一种类型,这里借用大佬的一张图来看一下:


image.png


代理类对应的 element 是 ProxyElement。


RenderObject


在上面我们提到过每一个 Element 都对应一个 RenderObject,我们可以通过 Element.renderObject 来获取。并且 RenderObject 的主要职责是 Layout 绘制,所有的 RenderObject 会组成一颗渲染树 Render Tree。


通过上面的分析,我们知道树的核心就在 mount 方法中,我们直接查看 RenderObjectElement.mount() 方法


@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}


在执行完 super.mount 之后 (将 parent 插入到 element 树中),执行了 attachRenderObject 方法。


@override
void attachRenderObject(Object? newSlot) {
  assert(_ancestorRenderObjectElement == null);
  _slot = newSlot;
  //查询当前最近的 RenderObject 对象  
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  // 将当前节点的 renderObject 对象插入到上面找的的 RenderObject下面  
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
  //......... 
}
RenderObjectElement? _findAncestorRenderObjectElement() {
   Element? ancestor = _parent;
   while (ancestor != null && ancestor is! RenderObjectElement)
     ancestor = ancestor._parent;
   return ancestor as RenderObjectElement?;
 }


上面代码也不难理解,就是一个死循环,退出的添加就是 ancestor 父节点为空和父节点是 RenderObjectElement 。说明这个方法会找到离当前节点最近的一个 RenderObjectElement 对象,然后调用 insertRenderObjectChild 方法,这个方法是一个抽象方法,我们查看两个类里面的重写逻辑


SingleChildRenderObjectElement


void insertRenderObjectChild(RenderObject child, Object? slot) {
  final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
  renderObject.child = child;
}


在上面代码中,找到了当前 RenderObjectElement 的 renderObject ,并且将我们传入的 child 给了 renderObject 的 child 。所以这样就将传入的 child 挂在了 RenderObject 树上。


MultiChildRenderObjectElement


@override
void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
  final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
  assert(renderObject.debugValidateChild(child));
  renderObject.insert(child, after: slot.value?.renderObject);
  assert(renderObject == this.renderObject);
}


上面代码中,找到 renderObject 之后赋值给了 ContainerRenderObjectMixin> 这个类,我们来看一下这个类


/// Generic mixin for render objects with a list of children.
/// 具有子项列表的渲染对象的通用混合
/// Provides a child model for a render object subclass that has a doubly-linked
/// list of children.
/// 为具有双向链接的子项列表的渲染对象子类提供子模型
mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {
}


泛型 mixin 用于渲染一组子对象的对象。看到这里我们可以知道 MultiChildRenderObjectElement 是可以有子列表的。


通过上面的注释可以看出一个重点,双向链表。所以 MultiChildRenderObjectElement 的子节点通过双向链表连接。上面 insert 最终会调用到 _insertIntoChildList 方法中,如下:


ChildType? _firstChild;
ChildType? _lastChild;
void _insertIntoChildList(ChildType child, { ChildType? after }) {
  final ParentDataType childParentData = child.parentData! as ParentDataType;
  _childCount += 1;
  assert(_childCount > 0);
  if (after == null) {
    // after 为 null,则插入到 _firstChild 中
    childParentData.nextSibling = _firstChild;
    if (_firstChild != null) {
      final ParentDataType _firstChildParentData = _firstChild!.parentData! as ParentDataType;
      _firstChildParentData.previousSibling = child;
    }
    _firstChild = child;
    _lastChild ??= child;
  } else {
    final ParentDataType afterParentData = after.parentData! as ParentDataType;
    if (afterParentData.nextSibling == null) {
      // insert at the end (_lastChild); we'll end up with two or more children 
      // 将 child 插入到末尾  
      assert(after == _lastChild);
      childParentData.previousSibling = after;
      afterParentData.nextSibling = child;
      _lastChild = child;
    } else {
      // insert in the middle; we'll end up with three or more children
      // 插入到中间
      childParentData.nextSibling = afterParentData.nextSibling;
      childParentData.previousSibling = after;
      // set up links from siblings to child
      final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType;
      final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType;
      childPreviousSiblingParentData.nextSibling = child;
      childNextSiblingParentData.previousSibling = child;
      assert(afterParentData.nextSibling == child);
    }
  }
}


根据上面的注释我们可以将其分为三部分,1,在 after 为 null 的时候将 child 插入到第一个节点,2,将 child 插入到 末端,3,将 child 插入到中间。


我们以一个例子为例:


Column(
  children: [
     SizedBox(.....),
     Text(data),
      Text(data),
      Text(data),
   ],
)


第一个 Stack 向上找到 Column( RenderObjectElement ) 之后, 调用这个方法,目前 after 为 null,则 _firstchild 就是 SizedBox( SizedBox 对应的 renderObject 是 RenderConstrainedBox)


第二个是 Text,我们知道 Text 是组合类型的,所以他不会被挂载到 树中,通过查询源码可以看出最终的 text 用的是 RichText。RichText 向上查找到 Column( RenderObjectElement ) 之后 ,调用这个方法,传入了两个参数,第一个 child 是 RichText 对应的 RenderParagraph ,第二个 after 是 SizedBox 对应的 RenderConstrainedBox 。依照上面逻辑,执行下面的代码


final ParentDataType afterParentData = after.parentData! as ParentDataType;
if (afterParentData.nextSibling == null) {
  // insert at the end (_lastChild); we'll end up with two or more children
  assert(after == _lastChild);
  childParentData.previousSibling = after;
  afterParentData.nextSibling = child;
  _lastChild = child;
}


将 child 的 childParentData.previousSibling 指向第一个节点,将第一个接的的 afterParentData.nextSibling 指向 child,最后让 _lastchild 执行 child。


后面也是如此,当流程结束后就可以得到一个 RenderTree。


总结


本文主要介绍了三棵树的构建过程以及 elemnt 的生命周期,这些虽然我们在开发过程中用的比较少,但是却是通向 flutter 内部世界的大门。


其实在写这篇文章的时候也是一知半解,通过不断的查看源码,翻看博客等才慢慢的有些了解。并且在最后产出了这篇文章,可能文章中会有一些错误,如果你看到的话麻烦在下面提出来,感谢!!


相关文章
|
11天前
|
存储 调度 数据安全/隐私保护
鸿蒙Flutter实战:13-鸿蒙应用打包上架流程
鸿蒙应用打包上架流程包括创建应用、打包签名和上传应用。首先,在AppGallery Connect中创建项目、APP ID和元服务。接着,使用Deveco进行手动签名,生成.p12和.csr文件,并在AppGallery Connect中上传CSR文件获取证书。最后,配置签名并打包生成.app文件,上传至应用市场。常见问题包括检查签名配置文件是否正确。参考资料:[应用/服务签名](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-signing-V5)。
38 3
鸿蒙Flutter实战:13-鸿蒙应用打包上架流程
|
1月前
|
前端开发 JavaScript Android开发
Flutter 与 React Native - 详细深入对比分析(2024 年)
Flutter和React Native是两大跨平台框架,各有优缺点。Flutter性能优越,UI灵活,使用Dart;React Native生态广泛,适合JavaScript开发。
267 5
Flutter 与 React Native - 详细深入对比分析(2024 年)
|
15天前
|
缓存 JavaScript API
Flutter&鸿蒙next 状态管理框架对比分析
在 Flutter 开发中,状态管理至关重要,直接影响应用的性能和可维护性。本文对比分析了常见的状态管理框架,包括 setState()、InheritedWidget、Provider、Riverpod、Bloc 和 GetX,详细介绍了它们的优缺点及适用场景,并提供了 Provider 的示例代码。选择合适的状态管理框架需考虑应用复杂度、团队熟悉程度和性能要求。
82 0
|
17天前
|
Dart Android开发 开发者
Flutter跨平台开发实战:构建高性能移动应用
【10月更文挑战第25天】随着移动设备种类的增加,开发者面临跨平台应用开发的挑战。Flutter作为Google推出的开源UI工具包,凭借其强大的跨平台能力和高效的开发效率,成为解决这一问题的新方案。本文将介绍Flutter的核心优势、实战技巧及性能优化方法,通过一个简单的待办事项列表应用示例,帮助读者快速上手Flutter,构建高性能的移动应用。
31 0
|
3月前
|
移动开发 Dart Android开发
构建未来:基于Flutter的跨平台移动应用开发
【7月更文挑战第52天】随着移动设备市场的多样化,跨平台移动应用的需求日益增长。传统的Android和iOS原生开发方式虽强大但成本较高,而新兴的跨平台框架如React Native、Xamarin等虽然提供了解决方案,但仍存在性能与体验上的妥协。本文将探讨使用Google推出的UI工具包Flutter进行高效、高性能的跨平台移动应用开发。我们将分析Flutter的核心架构,展示如何利用其丰富的组件库和高效的渲染引擎在Android和iOS之间实现无缝衔接,并讨论其在现代移动开发中的应用前景。
|
4月前
|
存储 安全 数据安全/隐私保护
构建安全Flutter应用 - 6个实用技巧
随着越来越多的敏感用户数据在Flutter应用中流通,应用安全已成为首要关注点。本文为您总结6大关键Flutter应用安全最佳实践,帮助开发者筑牢应用安全防线,保护用户隐私。
130 1
构建安全Flutter应用 - 6个实用技巧
|
3月前
|
开发者 监控 开发工具
如何将JSF应用送上云端?揭秘在Google Cloud Platform上部署JSF应用的神秘步骤
【8月更文挑战第31天】本文详细介绍如何在Google Cloud Platform (GCP) 上部署JavaServer Faces (JSF) 应用。首先,确保已准备好JSF应用并通过Maven构建WAR包。接着,使用Google Cloud SDK登录并配置GCP环境。然后,创建`app.yaml`文件以配置Google App Engine,并使用`gcloud app deploy`命令完成部署。最后,通过`gcloud app browse`访问应用,并利用GCP的监控和日志服务进行管理和故障排查。整个过程简单高效,帮助开发者轻松部署和管理JSF应用。
60 0
|
3月前
|
开发者 容器 Java
Azure云之旅:JSF应用的神秘部署指南,揭开云原生的新篇章!
【8月更文挑战第31天】本文探讨了如何在Azure上部署JavaServer Faces (JSF) 应用,充分发挥其界面构建能力和云平台优势,实现高效安全的Web应用。Azure提供的多种服务如App Service、Kubernetes Service (AKS) 和DevOps简化了部署流程,并支持应用全生命周期管理。文章详细介绍了使用Azure Spring Cloud和App Service部署JSF应用的具体步骤,帮助开发者更好地利用Azure的强大功能。无论是在微服务架构下还是传统环境中,Azure都能为JSF应用提供全面支持,助力开发者拓展技术视野与实践机会。
18 0
|
3月前
|
开发框架 Dart 前端开发
移动应用开发中的跨平台策略:React Native与Flutter的比较分析
【8月更文挑战第31天】 在快速变化的移动应用市场,开发者面临着如何在众多平台间高效部署应用的挑战。本文将深入探讨两种主流的跨平台移动应用开发框架——React Native和Flutter,通过对比它们的核心特性、性能表现以及社区生态,为开发者提供选择框架时的参考依据。我们将借助代码示例,展现两者在实际开发中的应用差异,并分析各自的优势和潜在局限,以期帮助开发者根据项目需求做出明智的技术选型。
|
3月前
|
开发框架 API 开发者
Flutter表单控件深度解析:从基本构建到高级自定义,全方位打造既美观又实用的移动端数据输入体验,让应用交互更上一层楼
【8月更文挑战第31天】在构建美观且功能强大的移动应用时,表单是不可或缺的部分。Flutter 作为热门的跨平台开发框架,提供了丰富的表单控件和 API,使开发者能轻松创建高质量表单。本文通过问题解答形式,深入解读 Flutter 表单控件,并通过具体示例代码展示如何构建优秀的移动应用表单。涵盖创建基本表单、处理表单提交、自定义控件样式、焦点管理和异步验证等内容,适合各水平开发者学习和参考。
81 0