import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
home: TestStatelessPage(),
));
}
class TestStatelessPage extends StatelessWidget {
const TestStatelessPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.amber,
child: const Center(
child: Text("abc"),
),
),
);
}
}
MaterialApp
→ TestStatelessPage
→ Scaffold
→ Container
→ Center
→ Text
一个简简单单的Flutter页面,黄黄的背景中心有一个text
文本;
直接进入正题,MaterialApp
作为参数被传入runApp
之后做了什么
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
WidgetsBinding
@protected
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget, // 这里被作为child参数传入
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance!.ensureVisualUpdate();
}
}
最终作为child
参数被传入RenderObjectToWidgetAdapter
(实际就是一个RenderObjectWidget
)中;
以上这段代码中,实例化了一个RenderObjectToWidgetAdapter
对象,传入了3个参数而后调用了attachToRenderTree
方法,传入了buildOwner
和renderViewElement
参数;
这里简单提及一下BuildOwner
,它是Flutter架构中非常重要的类,在WidgetsBinding
被初始化时实例,负责管理Flutter
中所有的Element
,包括构建、更新、管理脏元素列表等功能;
第二个参数renderViewElement
就是调用attachToRenderTree
之后才进行赋值的,所以首次进入该方法时,必然是以null
值传入;
RenderObjectToWidgetAdapter
RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetAdapter({
this.child,
required this.container,
this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));
final Widget? child;
}
上文提到的,RenderObjectToWidgetAdapter
是RenderObjectWidget
的子类;
至于名字为什么不是一个***Widget
,因为它与其他直接继承RenderObjectWidget
的子Widget
不同,还声明了一个extend RenderObject
的泛型,定义上来说它的作用是连接Flutter的Widget
系统和底层的RenderObject
系统,暂时先不深入;
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
进入attachToRenderTree
方法,根据前面分析,element
此时为null
,所以进入了第一个if分支;
这里调用了自己内部的方法createElement
,创建了Flutter
架构中的第一个Element
。这里提一下createElement
方法是声明在Widget
类中的一个抽象方法,每一个具体实现的Widget
子类都要实现该方法,一般是在构建Flutter Element
树时自上而下的调用,根据对应的Widget
生成Element
并返回。而此处的RenderObjectToWidgetAdapter
是顶级的Widget
,所以它就需要该attachToRenderTree
方法来调用自己的createElement
方法;
继续向下看,调用element!.assignOwner(owner);
,这里是为顶级Element
制定了owner
参数,即前面提到的BuildOwner
,这个类贯穿整个Element
树,自上而下的每一个Element
被创建时,其内部的owner
变量都会被赋值为parent
持有的owner
对象,所以最终所有的Element
内的owner
变量均指向一个,即顶级Element
中assignOwner
方法中传入的BuildOwner
;
owner.buildScope(element!, () {
element!.mount(null, null);
});
最后终于调用了mount
方法,这里的owner.buildScope
方法我们先简单去理解,简化示例代码:
BuildOwner
@pragma('vm:notify-debugger-on-exception')
void buildScope(Element context, [ VoidCallback? callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
/// 省略...
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
try {
callback();
} finally {
/// 省略...
}
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {
/// 省略...
}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
/// 省略...
}
}
以上代码可以看到,传入的第二个参数实际上就是经过一系列判断之后要调用执行的方法,简单的去解释这个方法就是需要在callback
函数调用前后分别执行部分逻辑;
总结下这个方法的主要功能:
- 确保一些前置条件可以通过,比如检查当前是否有其他
buildScope
在运行,若有则抛出异常,因为每次只能有一个buildScope
在运行 - 执行你传入的
callback
方法 - 遍历所有被标记为“脏”的元素,并调用它们的
rebuild
方法重新构建对应的Widget
最终进入Element
的mount
方法:
RenderObjectToWidgetElement
@override
void mount(Element? parent, Object? newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
assert(_child != null);
}
这里的两个参数简单介绍下:
parent
:较好理解,Element
树构建是自上而下,所以向下每个Element
要挂载时就需要知道自己的父元素,即parent
参数newSlot
:slot
参数是一个更复杂的概念。在 Flutter 的元素树中,每个元素都有一个父元素(除了根元素),并且可以有多个子元素。slot
是用来标识子元素在其父元素中的位置的。它可以是任何类型的对象,具体的类型和含义取决于父元素。一般来说,只有那些需要区分多个子元素的父元素才会使用slot
。
由于自己是第一个Element
,所以这两个参数都传递了null
;
至此就开始了Flutter Element
树体系中的第一个mount
方法的执行,之后所有Widget
的Element
的挂载逻辑都是从以上的方法中向下遍历进行执行;
在mount
方法中不断super
,追溯到顶层父类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;
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
可以看到该方法中处理了许多变量的赋值,有我们上文中提到的owner
变量;
这里看下对_depth
变量的赋值,若当前Element
有parent
,则赋值为_parent
的depth+1
值,当前Element
无parent
,所以depth==1
,为元素树中的1号元素;
在之前的RenderObjectToWidgetAdapter#attachToRenderTree
方法中断点,在执行了element.mount
之后,我们也可以看到element
的_depth
值确实为1;
以上梳理了自我们常见的runApp
入口函数开始到第一个Element
被挂载到Element
树体系上的逻辑步骤;
其中可以看到许多的关键类和方法,在整个Flutter体系中构建、重绘流程都有使用,后续文章再继续分析!若有疑问欢迎提出,若文中有错误,更欢迎指出!