视图树的创建与管理机制、布局、渲染核心框架
调用runApp(rootWidget),将rootWidget传给rootElement,做为rootElement的子节点,生成Element树,由Element树生成Render树
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
runApp(rootWidget) => attachRootWidget(rootWidget) => attachToRenderTree() => element.mount() => _rebuild() => updateChild()
WidgetsFlutterBinding混入了不少的其他的Binding
持有BuildOwner、PipelineOwner
BuildOwner
BuildOwner是Widget framework的管理类, 该类跟踪哪些小部件需要重新构建,并处理应用于整个小部件树的其他任务,比如管理树的非活动元素列表
PipelineOwner
管理真正需要绘制的View, 对RenderObjectTree中发生变化节点的进行flush操作, 最后交给底层引擎渲染
1.attachRootWidget(app) 方法创建了Root[Widget](也就是 RenderObjectToWidgetAdapter)
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
2.紧接着调用attachToRenderTree方法创建了 Root[Element]
RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement element]) {
if (element == null) {
owner.lockState(() {
element = createElement(); //创建rootElement
element.assignOwner(owner); //绑定BuildOwner
});
owner.buildScope(element, () { //子widget的初始化从这里开始
element.mount(null, null); // 初始化子Widget前,先执行rootElement的mount方法
});
} else {
...
}
return element;
}
3.Root[Element]尝试调用mount方法将自己挂载到父Element上,因为自己就是root了,所以没有父Element,挂空了
owner.buildScope(element, () { //子widget的初始化从这里开始
element.mount(null, null); // 初始化子Widget前,先执行rootElement的mount方法
});
4.mount的过程中会调用Widget的createRenderObject,创建了 Root[RenderObject]
void mount(Element parent, dynamic newSlot) {
_parent = parent; //持有父Element的引用
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;//当前节点的深度
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner; //每个Element的buildOwner,都来自父类的BuildOwner, 这样可以保证一个ElementTree,只由一个BuildOwner来维护
...
}
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
5.我们将app作为参数传给了Root[Widget](也就是 RenderObjectToWidgetAdapter),app[Widget]也就成了为root[Widget]的child[Widget]
6.调用owner.buildScope,开始执行子Tree的创建以及挂载(与更新流程一致, 见更新)
7.调用createElement方法创建出Child[Element]
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;
}
8.调用Element的mount方法,将自己挂载到Root[Element]上,形成一棵树
9.挂载的同时,调用widget.createRenderObject,创建Child[RenderObject]
10.创建完成后,调用attachRenderObject,完成和Root[RenderObject]的链接
@override
void attachRenderObject(dynamic newSlot) {
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
final ParentDataElement parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
RenderObject与父RenderObject的挂载稍微复杂了点。每一个Widget都有一个对应的Element,但Element不一定会有对应的RenderObject。(因为有一些Element是不用来做页面显示的, 像StatelessWidget=>StatelessElement没有对应的RenderObject)所以你的父Element并不一有RenderObject,这个时候就需要向上查找。
RenderObjectElement _findAncestorRenderObjectElement() {
Element ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor;
}
find方法在向上遍历Element,直到找到RenderObjectElement,RenderObjectElement肯定是有对应的RenderObject了,这个时候在进行RenderObject子父间的挂载。
安排一个帧尽快运行, 这在应用程序启动期间使用,以便第一个帧(可能非常昂贵)可以多运行几毫秒。(个人理解是为了实现第一次页面渲染可以调用到 => drawFrame)
@protected
void setState(VoidCallback fn) {
...
_element.markNeedsBuild();
}
void markNeedsBuild() {
...
_dirty = true; // 标记自身为dirty
owner.scheduleBuildFor(this); // 通知buildOwner处理
}
buildOwner会将所有dirty的Element添加到_dirtyElements当中,等待下一帧绘制时集中处理。
还会调用ui.window.scheduleFrame();通知底层渲染引擎安排新的一帧处理。
void scheduleBuildFor(Element element) {
...
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
}
...
}
void buildScope(Element context, [VoidCallback callback]) {
...
try {
...
//1.排序
_dirtyElements.sort(Element._sort);
...
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
//2.遍历rebuild
_dirtyElements[index].rebuild();
} catch (e, stack) {
}
index += 1;
}
} finally {
for (Element element in _dirtyElements) {
element._inDirtyList = false;
}
//3.清空
_dirtyElements.clear();
...
}
}
遍历执行的过程中,也有可能会有新的element被加入到_dirtyElements集合中,此时会根据dirtyElements集合的长度判断是否有新的元素进来了,如果有,就重新排序。
void rebuild() {
if (!_active || !_dirty)
return;
Element debugPreviousBuildTarget;
performRebuild();
}
element的rebuild方法最终会调用performRebuild(),而performRebuild()不同的Element有不同的实现
performRebuild()不同的Element有不同的实现,我们暂时只看最常用的两个Element:
ComponentElement,是StatefulWidget和StatelessElement的父类
void performRebuild() {
Widget built;
try {
built = build();
}
...
try {
_child = updateChild(_child, built, slot);
}
...
}
build()执行我们复写的StatefulWidget的state的build方法, 拿到子Widget, 交给updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
//1. 如果newWidget是null, 说明删除控件, Element被删除
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
if (child != null) {
//2. 如果新旧控件相同, 说明Widget复用了, 判断位置是否相同, 不相同更新
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
return child;
}
//3. 判断key值和运行时类型(runtimeType)是否相等, 都相同才可以更新, 更新并返回Element(这个时候应该是Widget变了, 但是还是同类型的Widget)
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);//
return child;
}
deactivateChild(child);
}
//4. 如果上面的条件都不满足, 创建新的Element
return inflateWidget(newWidget, newSlot);
}
1.如果newWidget是null, 说明删除控件, Element被删除
2.如果新旧控件相同, 说明Widget复用了, 判断位置是否相同, 不相同更新
3.判断key值和运行时类型(runtimeType)是否相等, 都相同才可以更新, 更新并返回Element(这个时候应该是Widget变了, 但是还是同类型的Widget)
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
child.update(newWidget);方法, 会根据newWidget的类型执行不同的update方法, 例如:
Column是MultiChildRenderObjectWidget类型的, 就会执行下面的方法:
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
由于Column里面的孩子是children类型(MultiChildRenderObjectWidget), 有多个, 所以对比算法采用updateChildren, 返回新的Element
Container是StatelessWidget类型的, 所以他执行StatelessWidget的update方法:
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
Scaffold是StatefulWidget类型的, 所以执行:
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();
}
如果是StatelessWidget/StatefulWidget类型, 则继续执行下一级的对比, 以此类推.(child.update => rebuild => performRebuild(), rebuild就是上面那个方法)
如果是MultiChildRenderObjectWidget类型, 则updateChildren里面会进行List对比算法, 每一个item也会调用updateChild()方法, 进行计算, 详细过程见最后
4.如果上面的条件都不满足, 创建新的Element
首先会尝试通过GlobalKey去查找可复用的Element,复用失败就调用Widget的方法创建新的Element,然后调用mount方法,将自己挂载到父Element上去,会在这个方法里创建新的RenderObject。
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;
}
RenderObjectElement,是有渲染功能的Element的父类
与ComponentElement的不同之处在于,没有去build,而是调用了updateRenderObject方法更新RenderObject。
@override
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
他代表的具有自己渲染功能的一类Widget(Text,没有child的)
@protected
void drawFrame() {
pipelineOwner.flushLayout(); //布局需要被布局的RenderObject
pipelineOwner.flushCompositingBits(); // 判断layer是否变化
pipelineOwner.flushPaint(); //绘制需要被绘制的RenderObject
renderView.compositeFrame(); // this sends the bits to the GPU 将画好的layer传给engine绘制
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. 一些语义场景需要
}
void finalizeTree() {
Timeline.startSync('Finalize tree', arguments: timelineWhitelistArguments);
try {
lockState(() {
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
});
...
} catch (e, stack) {
_debugReportException('while finalizing the widget tree', e, stack);
} finally {
Timeline.finishSync();
}
}
所有没用的element都调用了deactivateChild方法进行回收
void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
也就在这里将被废弃的element添加到了_inactiveElements当中。
另外在废弃element之后,调用inflateWidget创建新的element时,还调用了_retakeInactiveElement尝试通过GlobalKey复用element,此时的复用池也是在_inactiveElements当中。
如果你没有在一帧里通过GlobeKey完成Element的复用,_inactiveElements在最后将被清空,就没办法在复用了。
@protected
List updateChildren(List oldChildren, List newWidgets, { Set forgottenChildren }) {
Element replaceWithNullIfForgotten(Element child) {
return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
}
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
final List newChildren = oldChildren.length == newWidgets.length ?
oldChildren : List(newWidgets.length);
Element previousChild;
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
final Widget newWidget = newWidgets[newChildrenTop];
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
final Widget newWidget = newWidgets[newChildrenBottom];
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
// Scan the old children in the middle of the list.
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
Map oldKeyedChildren;
if (haveOldChildren) {
oldKeyedChildren = {};
while (oldChildrenTop <= oldChildrenBottom) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if (oldChild != null) {
if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key] = oldChild;
else
deactivateChild(oldChild);
}
oldChildrenTop += 1;
}
}
// Update the middle of the list.
while (newChildrenTop <= newChildrenBottom) {
Element oldChild;
final Widget newWidget = newWidgets[newChildrenTop];
if (haveOldChildren) {
final Key key = newWidget.key;
if (key != null) {
oldChild = oldKeyedChildren[key];
if (oldChild != null) {
if (Widget.canUpdate(oldChild.widget, newWidget)) {
// we found a match!
// remove it from oldKeyedChildren so we don't unsync it later
oldKeyedChildren.remove(key);
} else {
// Not a match, let's pretend we didn't see it for now.
oldChild = null;
}
}
}
}
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
}
// We've scanned the whole list.
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = oldChildren[oldChildrenTop];
final Widget newWidget = newWidgets[newChildrenTop];
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// Clean up any of the remaining middle nodes from the old list.
if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (Element oldChild in oldKeyedChildren.values) {
if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
deactivateChild(oldChild);
}
}
return newChildren;
}