Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程
一 前言
说明
本文主要讲述的是flutter的构建流程。一步步地去分析Flutter是如何从上往下构建Widget,顺便带出Flutter日常开发中的基本概念。
源码警告⚠️ 本文在分析过程中会夹带部分源码,整体边幅较长。友情提示,通过导航栏去到相应的地方
问题
希望你能在阅读的过程能带着以下的一些问题。
- Widget,Element,RenderObject是什么,三者的关系是怎样的?
- build方法中的BuildContext是什么?
- setState发生了什么?
- 为什么GlobalKey可以使你在任意地方获取Widget?
正文分割线
二 Widget,Element,RenderObjct
Widget,Element,RenderObject说明
在日常开发中,我们经常接触到都是Widget,包括各种的StalessWidget,StafulWidget,仿佛各种Widget就是界面上的一个节点。但是,Widget并不是界面上的一个节点.它只是一个描述节点的工具。请参照以下案例
其实按照我们的开发经验可知。一个节点是无法同时出现在两个地方。就像原生开发中。一个按钮是无法同时添加到两个地方的。Widget就是一个描述文件,负责产生一个节点,以及说明这个节点的功能是什么。
那现在问题来了,到底什么是节点呢?Flutter中的节点是一个比较神奇的存在。它不仅可用于表达UI界面上的一个元素,也可以用来表达一种布局信息,或是存储依赖关系等。
有一种说法是flutter中万物皆是Widget,其内在的意义就是flutter中的节点可以表示开发中的大部分的元素。flutter中的节点是用Element来表示。
既然说到节点Element可以表达大部分元素。那么具体到要产生在我们能看到UI信息,是怎么做到的呢?
首先我们先看看Widget类的定义
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key? key;
//创建节点Element
@protected
@factory
Element createElement();
...
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
...
}
Widget类是一个抽象类,其中有一个createElement()方法。这个方法是用来创建节点Element。至于创建什么类型的Element。具体由Widget子类去实现
然后我们先看下Widget和Element这两个类的继承树
从图片可以很容易地观察出,每一种类型的Widget都有对应的Element。RenderObjectWidget是用来构建具体显示在界面上的节点的描述信息类。在这里我们重点看下RenderObjectWidget和RenderObjectElement。
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key? key }) : super(key: key);
//创建RenderObjectElement类型的节点
@override
@factory
RenderObjectElement createElement();
//创建RenderObject
@protected
@factory
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
在这里,我们可以看到RenderObjectWidget的createElement()方法会创建RenderObjectElement一个RenderObjectElement。然后看下RenderObjectElement的代码
abstract class RenderObjectElement extends Element {
RenderObjectElement(RenderObjectWidget widget) : super(widget);
@override
RenderObjectWidget get widget => super.widget as RenderObjectWidget;
@override
RenderObject get renderObject => _renderObject!;
RenderObject? _renderObject;
//mount方法极其重要,是所有Elemnt子类都必须需要实现方法
//就是通过这个方法一层层地往下构建Element节点树
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
#
_renderObject = widget.createRenderObject(this);
...
}
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
...
_dirty = false;
}
...
}
结合RenderObjectWidget和RenderObjectElement的源码,我们可以看到这么一个基础的流程
可以看到Widget,Element和RenerObject三者的关系如下
值得一提的是,RenderObjct其实还不是真正显示在界面上的元素,其背后还有一个用于绘制的图层layer。关系有点像iOS开发中的UIView和CALayer。
这里小结一下
- Element是Flutter中代表一个节点的类。这里的节点不单单指UI上的界面元素
- Widget是Element的描述类,用于说明创建一个怎样的Element节点,
- RenderObjct是用于表示显示在UI上的布局和元素
三 Widget树构建
Widget树是什么?
我们平常的学习过程中,经常会遇到Widget,Element和RenderObjct三棵树的概念,那到底这是什么意思呢,三棵树到底又是如果构建呢?结合前面三者的关系,我们接着分析一下。首先我们先看下具体的demo。如下
图中左方是demo的效果,中间是demo的代码,右方是demo中的各个Widget。可以看到,右方的所有Widget以一个树状的方式层级展开。形象一点的话,如下
可以看到,Widget以一个树状的方式去一级级往下走。我们在写Flutter项目的时候,所有的Widget都会以这样的一个树状层级构建,这个就是Widget树的概念。
Widget树怎么构建?
首先我们从main函数开发分析,我提炼一下重点,main函数执行后的流程如下
main函数最终会执行RenderObjectToWidgetAdapter类的attachToRenderTree方法。attachToRenderTree方法如下
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
//创建RenderObjectToWidgetElement节点
element = createElement();
assert(element != null);
//为节点设置一个BuildOwner
element!.assignOwner(owner);
});
//调用BuildOwner的buildScope方法
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
在构建过程中,这个方法做了两个事情,构建RenderObjectToWidgetElement节点,并把节点和回调传入BuildOwner的buildScope方法。这个其实就是构建我们Widget树的开端。
构建管理者BuilderOwner
你可以理解为BuilderOwner为一个Widget树的管理者,负责Widget树的管理和更新。他与Widget,Element和RenderObjct的关系大致如下
BuilderOwner不与Widget直接接触,而是通过构建,更新Element树,从而构建和更新Widget树。
除了构建和更新Widget和Element树,BuilderOwner还负责管理GlobalKey等操作
构建流程
从前面我们看到runApp()方法会调用到RenderObjectToWidgetAdapter的attachToRenderTree方法中。这个方法RenderObjectToWidgetElement节点,并把节点和回调传入BuildOwner的buildScope方法。BuildOwner类和buildScope方法因为和更新过程联系比较大,稍后再详细描述,这里需要知道的是,buildScope会执行回调,也就是下面的代码
//调用BuildOwner的buildScope方法
//根据上方代码可知,element是RenderObjectToWidgetElement
owner.buildScope(element!, () {
element!.mount(null, null);
});
这里的RenderObjectToWidgetElement其实是一个根节点,也就是Element树的根节点,从这个节点开始,我们将一步步地往下构建子Element树.看下RenderObjectToWidgetElement中mount方法的实现
@override
void mount(Element? parent, Object? newSlot) {
...
super.mount(parent, newSlot);
_rebuild();
...
}
可以看到,这里是调用了父类的mount方法,以及调用_rebuild方法。因为这一小节讲得是Widget树的构建。这里重点关注一下super.mount方法,因为RenderObjectToWidgetElement继承RootRenderObjectElement,而RootRenderObjectElement继承RenderObjectElement。所以调用super.mount会调用到RenderObjectElement中的mount方法。如下
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
_dirty = false;
}
这里做了一件事,就是为RenderObjectToWidgetElement绑定一个RenderObject,用于表示这个节点是需要显示在界面。然后执行完mount方法以后,会调用_rebuild()方法。如下
void _rebuild() {
try {
// 在这个案例中,widget.child 就是我们在runApp方法中传入的MyApp()
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
...
}
_rebuild方法中会调用Element这个基类的updateChild方法。如下
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null)
//先使子节点child设置为一个不可用状态
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
//如果子节点非空,
//则再判断是更新,删除子节点或是不对子节点进行操作
bool hasSameSuperclass = true;
...
if (hasSameSuperclass && child.widget == newWidget) {
//widget和久的widget相等的情况下
//如果只是slot改变,则更新slot。否则widget无需更改
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//Widget更新
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
//其他情况,使子节点child设置为一个不可用状态,再重新构建一个子节点
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//如果子节点为空,则代表是子节点不存在,则为插入一个子节点
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
针对我们runApp方法,其实是一次初次构建。那么这时候RenderObjectElement这时候是没有子节点。也就是说child为空。所以会直接走到newChild = inflateWidget(newWidget, newSlot) 这个方法里面,newWidget就是案例中的MyApp()这个实例 。inflateWidget如下
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
if (key is GlobalKey) {
//Widget的key是否是GlobalKey,
final Element? newChild = _retakeInactiveElement(key, newWidget);
//找到原来的Widget对应的Element,如果在Element树的位置有改变,则更新位置
if (newChild != null) {
...
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
//调用newWidget,创建一个Element
final Element newChild = newWidget.createElement();
...
//调用mount方法,把newChild这个element插入到Element树对应的位置
newChild.mount(this, newSlot);
...
return newChild;
}
可以看到,上面的代码中调用了newWidget.createElement()。也就是MyApp().createElement()的方法。这时候,就终于调用到我们自己写的代码了。从我们的代码可以看出,MyApp继承StatelessWidget。
StatelessWidget的createElement方法会创建一个StatelessElement。所以上方代码中的newChild就是一个StatelessElement类型的Element。而StatelessElement继承ComponentElement。当调用mount方法时,会执行ComponentElement的mount方法。
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
_firstBuild();
...
}
这里可以看到,执行了_firstBuild方法。__firstBuild方法最终会调用performRebuild方法。performRebuild如下
void performRebuild() {
...
built = build();
...
_child = updateChild(_child, built, slot);
...
}
这个方法做的事情,就是调用build()方法产生一个Widget。然后再调用updateChild方法,传入Widget,然后去创建,或是更新一个Element。
ComponentElement中的build()方法不做任何事情,交由子类去实现。那么对于刚才的MyApp()这个Widget。对应的就是StatelessElement。StatelessElement的build方法如下。
Widget build() => widget.build(this);
这个方法就只是简单的返回widget.build(this)。但是,widget.build(this)这个方法是否似曾相识。没错,他其实就是我们平常写StatelessWidget或是State时候的build方法。对于我们的demo,就是MyApp的build方法,如下
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
留意一下,这里的build方法的参数是BuildContext。结合我们上面的流程可知,BuildContext其实就是Element。理解这一点非常的重要。
结合上面的分析可知,我们平常开发中的BuildContext其实就是父Element节点。通过这个父Element节点,我们可以对节点树进行一些操作,具体看BuildContext的定义。
梳理一下我们刚才的分析,可以大概得出以下这么一个流程。
- runApp()方法创建RenderObjectToWidgetAdapter,并传入MyApp这个Widget,作为RenderObjectToWidgetAdapter的child。
- RenderObjectToWidgetAdapter创建RenderObjectToWidgetElement。
- 调用RenderObjectToWidgetElement的mount方法
- mount方法中调用child或是children的build方法去创建子Widget
- 调用子Widget的createElement,产生一个子Element.
- 调用子Element的mount方法
然后不断的重复4~6这三个步骤,不断的产生子Widget和子Element,直到Widget树和Element树完全构建为止。
构建流程图
结合上面的分析以及不同类型的Widget和Element(具体可以看framework.dart实现,这里不做详细分析)。可以得出以下的一个构建流程
四 Widget树是怎么更新的?
State
我们知道,一个界面不是一成不变的。当Widget和Element树构建完以后,我们需要更新界面。在Flutter开发中,我们一般会通过setState()方法去刷新界面。那调用setState方法发生了什么事情呢?
setState()方法是State里面的一个方法。首先我们先了解一下State是什么。要了解State,要先了解一下StatelessWidget和StatefulWidget。StatelessWidget和StatefulWidget都是继承于Widget。
StatelessWidget代表没有状态的Widget,也就是说这个Widget是不会改变的。StatefulWidget代表的是有状态的Widget,也就是这个Widget是会随着状态的改变。前面提到,Widget终归是只是一个描述类。作用是用来产生节点的。所以我们看下StatelesslElement和StatefulElement这两个类
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
}
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatefulElement(StatefulWidget widget)
: state = widget.createState(),
super(widget) {
state._element = this;
state._widget = widget;
}
@override
Widget build() => state.build(this);
final State<StatefulWidget> state;
...
}
可以看到StatefulElement比StatelessWidget多了一个state。state的代码如下
abstract class State<T extends StatefulWidget> with Diagnosticable {
...
T get widget => _widget!;
T? _widget;
bool get mounted => _element != null;
...
BuildContext get context {
...
return _element!;
}
StatefulElement? _element;
...
void setState(VoidCallback fn) {
...
final dynamic result = fn() as dynamic;
...
_element.markNeedsBuild();
}
...
}
可以看到StatefuleElement,StatefuleWidget与State的关系如下
widget去创建一个Element,Element的创建时调用Widget的createState方法创建一个State。这个State就代表着Element的状态,它内部持有Widget和Element,可以同时访问到Widget和Element里的内容。
从最熟悉的方法,也就是State的setState方法开始讲起。setState方法定义如下
void setState(VoidCallback fn) {
...
final dynamic result = fn() as dynamic;
...
_element.markNeedsBuild();
}
可以看出,这个方法先是我们调用了传入的方法,执行方法后,就调用了State中的_element的markNeedsBuild()方法,markNeedsBuild()方法是将一个Element标记为需要更新。方法的定义如下
void markNeedsBuild() {
...
if (!_active) return;
...
_dirty = true;
owner.scheduleBuildFor(this); //加入build owner的rebuild计划
}
markNeedsBuild方法首先判断一个Elemnt是否是_active,如果不是,则不做任何处理,因为一个非活跃的状态不会显示在界面上,所以不需做处理。然后将其_dirty值设置为true,标记这个Element 需要更新。然后调用BuildOwner的scheduleBuildFor方法,并传入需要更新的Element。BuildOwner的scheduleBuildFor定义如下
void scheduleBuildFor(Element element) {
...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled(); // 通知Engine 在下一帧需要做更新操作;
}
_dirtyElements.add(element);
...
}
方法里的_scheduledFlushDirtyElements表明是否是在更新过程中,可以看到,这里先判断_scheduledFlushDirtyElements是否为true,就是不在更新过程中,则调用onBuildScheduled通知框架需要进行更新树。 然后将刚才传入的element添加到BuildOwner的_dirtyElements中。这个_dirtyElements是一个列表,存储着需要所有更新的Elemnt。
当框架收到需要更新树的信息后,就会调用BuildOwner的buildScope()方法,前面在构建过程中提到过这个buildScope方法,但是没有细说,在这里我们看一下buildScope方法的实现
void buildScope(Element context, [VoidCallback callback]) {
if (callback == null && _dirtyElements.isEmpty) return;
...
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
...
callback(); //callback先执行
}
while (index < dirtyCount) {
...
_dirtyElements[index].rebuild(); //进行rebuild
...
}
_dirtyElements.clear();
}
复制代码
buildScope()方法中先是执行了callback方法,对于首次构建的流程,这个callback方法就是调用RootRenderObjectElement,也就是根Elemnt的mount方法进行构建。
然后这个方法主要就做了一件事,就是从_dirtyElements列表中取出每一个需要更新的Element,然后对所有Elemnt调用rebuild()方法,再清空_dirtyElements列表,标志着这一轮更新完成。
Element中的rebuild方法会调用performRebuild方法。performRebuild在Element中没有具体的实现。对于不同的Element子类,有着不一样的额外的实现。
首先对于RenderObjctElemnt及其子类,performRebuild只会调用updateRenderObject去更新RenderObjct对象,不会涉及任何的子节点的更新.那这里就有疑问了,该怎么对有子节点的RenderObjctElemnt进行更新子树呢?这里先留一下小疑问,等讲完ComponetElemnt然后再解答。
对于ComponetElemnt及其子类,调用performRebuild方法会调用updateChild方法。前面在构建过程中,就贴出了updateChild这两个方法,这里就不再重复去贴代码。在这里重点说一下updateChild这个方法
updateChild
updateChild中传入了(Element child, Widget newWidget, dynamic newSlot) 三个参数。child代表的是该ComponetElemnt的子节点,newWidget是子节点对应的Widget,newSlot是子节点的位置信息
在执行updateChild过程中根据传入的参数做了以下的一些处理。
- 如果new widget为空,但是element不为空(也就是原有的widget被删除了)。首先deactivateChild(child),如果child不为空,则解绑child的renderobjce,并添加到build owner中的_inactiveElements列表中,并返回函数。deactivateChild(child会把child添加到BuildOwner_inactiveElements中)
- 如果new widget非空,child不为空
- 如果传入的widget和原来的widget是同一个,但是slot不一样,则调用updateSlotForChild更新位置
- 如果不是同一个widget,则判断widget的canUpdate是否是true,如果是true的话(代表element的可以重用),先判断slot是否和原来的slot相等,不相等,则调用updateSlotForChild更新位置。然后调用elemnt的update方法进行更新
- 如果不符合上面2.1,2.2的情况(则代表element不可重用,newWidget需要用例外一个新的element),则调用deactivateChild(child)方法,并调用inflateWidget(newWidget, newSlot)产生新的child
- newWidget为空,child为空。则表明该Wiget还不存在Element,则调用inflateWidget去创建一个新的Element。
上面的1,2,3分别代表着删除,修改,增加 Elemnt子节点的三种情况。当对一个element(ComponetElemnt及其子类的实例)调用markNeedsBuild方法的时候,会调用到updateChild方法去更新该element。
对于步骤1,当一个Widget不再使用的时候,会调用deactivateChild方法,这个方法会把对应的Element放入到BuildlOwner的_inactiveElements列表中,如果Element被再次使用到(如使用了GlobalKey),就会从_inactiveElements列表中移除。如果没有被再次用到,再一次更新树的时候就会被销毁。
对于步骤2.2,elemnt的update方法只会简单的设置widget。具体的实现由各个子类实现。这一步可以buid方法可以沿下更新树
如StatelessElement会调用rebuild方法,RenderObjectElemnt会更新renderobjct。像SingleChildRenderObject和MutilChildRederObjectElemnt等有子elemnt的还会调用updateChild方法(MutilChildRederObjectElemnt 是调用updateChildren,但是updateChildren是对updateChild的一个包装,对传入的列表逐个调用updateChild)更新子elemnt,
在说RenderObjctElemnt的performRebuild时候,留了一个小疑问:如何更新有子节点的RenderObjcetElement的子树。
其实在开发过程中,我们是不会直接调用RenderObjcetElement的markNeedsBuild方法的。就拿Stack这个Wigdet来说。Stack继承自MultiChildRenderObjectWidget。MultiChildRenderObjectWidget对应的是MutilChildRederObjectElemnt。
但会发现,Stack是没有setState方法的,也就说不会直接调用对应Element的markNeedsBuild方法。Stack一般是被ComponetWidget及其子类对应的Widget(如StatefuleWidget)所包裹着,也就是Stack的一般都有一个父Widget包裹着。更新的时候,就会调用父Widget对应Element的updateChild更新子树。
假设你要在更新流程中,保留Stack,只删除Stack里的一个子节点,会走到父Widget对应的Element的updateChild方法中,也就是上方的步骤2.2。
步骤2.2中调用了child.update(newWidget)。也就是Stack对应的Element的update方法
Stack对应的Element是MutilChildRederObjectElement,update方法如下
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
...
_children = updateChildren(_children, widget.children,
forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
}
可以看到,update方法调用了updateChildren方法,updateChildren这个方法会遍历子节点,对每一个子节点调用updateChild方法,从而完成RenderObjcetElement子树的更新。
验证这一个流程也很简单,在MutilChildRederObjectElement那里的update方法打个断点即可,如下。观察左方的调用栈即可得到具体的流程
更新流程图
综合上面的流程可以看出,调用State的setState方法,其实就是标记State对应的Element需要更新,通过调用markNeedsRebuild方法标记Element需要更新并通知到Flutter框架需要更细。当框架更新该Element的时候,会调用updateChild方法向下递归更新子树,直到叶子节点为止
五 Widget,Eleemtn,RenderObject树的补充
上方的三,四小节标题虽然是Widget的构建和更新。但是其实Widget的构建和更新其实都是依赖Element的。在实际内容中其实是Widget和Element树的更新。但是对于RenderObject树的构建没有太多的描述。
如果对上方Element的构建流程理解以后,再结合以下的代码,你就知道RenderObjct树是怎么构建的。
RenderObject树的构建
首先看下RenderObjectElement的mount方法,如下
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
//创建RenderObjct
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
...
}
...
void attachRenderObject(Object? newSlot) {
assert(_ancestorRenderObjectElement == null);
//找到父RenderObjct节点,并在父RenderObjct节点的字节点中加入该RenderObject节点
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(
renderObject, newSlot);
final ParentDataElement<ParentData>? parentDataElement =
_findAncestorParentDataElement();
if (parentDataElement != null) _updateParentData(parentDataElement.widget);
}
首先在Element的构建过程中,去创建RenderObject。然后在通过Element树找到父节点,并通过_ancestorRenderObjectElement指向父节点。这样在Element树构建的过程中,RenderObject树也会不断的构建。
RenderObject树更新
看下RenderObjectElement的update方法,如下
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
...
widget.updateRenderObject(this, renderObject);
...
}
RenderObject树的更新也是依赖于Element树的更新。当Element更新的时候,会调用RenderObjectElement的update方法,然后调用对应Widget的updateRenderObject方法。
对于开发者而言,对RenderObject的操作基本都是通过Widget去实现,基本屏蔽了Element的实现。
六 拓展
Flutter中的Key
Flutter中有各种各样的Key,作用大致都是用来标志一个对象的。集成体系大致如下
大家可以直接看下注释就可以知道不同的Key是用来做什么的。这里想重点说一下GlobalKey。很多人用来做跨Widget访问状态使用。
先看下它的定义
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
...
factory GlobalKey({String? debugLabel}) => LabeledGlobalKey<T>(debugLabel);
...
const GlobalKey.constructor() : super.empty();
Element? get _currentElement =>
WidgetsBinding.instance!.buildOwner!._globalKeyRegistry[this];
...
BuildContext? get currentContext => _currentElement;
...
Widget? get currentWidget => _currentElement?.widget;
...
T? get currentState {
final Element? element = _currentElement;
if (element is StatefulElement) {
final StatefulElement statefulElement = element;
final State state = statefulElement.state;
if (state is T) return state;
}
return null;
}
}
可以看到,GlobalKey是用于标记一个State的。同时它有currentContext,currentWidget,和currentState。允许你在Widget树上的任何一个节点获取到其持有的Widget,State,Context。那是如何做到的呢?
当我在Widget中声明一个GlobalKey时,随着Widget树的构建,会调用Element树的mount方法
看下Element的mount方法,如下
void mount(Element? parent, Object? newSlot) {
...
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
...
_owner = parent.owner;
}
...
final Key? key = widget.key;
//是否是GlobalKey,如果是,向BuildOwner中注册一个GlobalKey
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
可以看到,如果一个Widget声明了一个GlobalKey,那么对应的Element就会在BuildOwner中调用_registerGlobalKey注册。BuildOwner的_registerGlobalKey如下
void _registerGlobalKey(GlobalKey key, Element element) {
...
_globalKeyRegistry[key] = element;
}
_globalKeyRegistry是BuildOwner的一个Map,用于存储所有的GlobalKey和其对应的Element.
final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};
当调用_registerGlobalKey的时候,实际上就是把GlobalKey和Element绑定在一起,放入到BuildOwner的一个Map当中。
前面提到过BuildOwner,BuildOwner负责Widegt树的构建和更新的,一般在Flutter的声明周期中,一般只会有一个BuildOwner实例(这一块在讨论Flutter中的runApp流程的时候再讨论)
当我们去使用GlobalKey去拿Widget,Context,State的时候,其实就是以这个GlobalKey为键,去BuildOwner中取出对应的Element。再通过Element 获取到对应的Widget,State,Context。
就是那么的简单!!!
总结
其实理解Widget,Element,RenderObject树的概念不是必要的,但是很重要。对于后续理解app的流程,手势传递等,以及优化等分析都是必不可少的部分。
转载自:https://juejin.cn/post/7038087364060659725