Flutter Framework
视图树的创建与管理机制、布局、渲染核心框架
视图树
- Widget => 为Element提供配置信息
- Element => Flutter创建Element的可见树, 同时持有Widget和RenderObject
- RenderObject => 渲染树中的一个对象
渲染机制
调用runApp(rootWidget),将rootWidget传给rootElement,做为rootElement的子节点,生成Element树,由Element树生成Render树
runApp(首次执行)
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
复制代码
runApp(rootWidget) => attachRootWidget(rootWidget) => attachToRenderTree() => element.mount() => _rebuild() => updateChild()
1. WidgetsFlutterBinding
WidgetsFlutterBinding混入了不少的其他的Binding
- BindingBase 那些单一服务的混入类的基类
- GestureBinding framework手势子系统的绑定,处理用户输入事件
- ServicesBinding 接受平台的消息将他们转换成二进制消息,用于平台与flutter的通信
- SchedulerBinding 调度系统,用于调用Transient callbacks(Window.onBeginFrame的回调)、Persistent callbacks(Window.onDrawFrame的回调)、Post-frame callbacks(在Frame结束时只会被调用一次,调用后会被系统移除,在Persistent callbacks后Window.onDrawFrame回调返回之前执行)
- PaintingBinding 绘制库的绑定,主要处理图片缓存
- SemanticsBinding 语义化层与Flutter engine的桥梁,主要是辅助功能的底层支持
- RendererBinding 渲染树与Flutter engine的桥梁
- WidgetsBinding Widget层与Flutter engine的桥梁
持有BuildOwner、PipelineOwner
-
BuildOwner
BuildOwner是Widget framework的管理类, 该类跟踪哪些小部件需要重新构建,并处理应用于整个小部件树的其他任务,比如管理树的非活动元素列表
-
PipelineOwner
管理真正需要绘制的View, 对RenderObjectTree中发生变化节点的进行flush操作, 最后交给底层引擎渲染
2. attachRootWidget
-
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子父间的挂载。
3. scheduleWarmUpFrame
安排一个帧尽快运行, 这在应用程序启动期间使用,以便第一个帧(可能非常昂贵)可以多运行几毫秒。(个人理解是为了实现第一次页面渲染可以调用到 => drawFrame)
setState(更新)
@protected
void setState(VoidCallback fn) {
...
_element.markNeedsBuild();
}
复制代码
1.Element标记自身为dirty,并通知buildOwner处理
void markNeedsBuild() {
...
_dirty = true; // 标记自身为dirty
owner.scheduleBuildFor(this); // 通知buildOwner处理
}
复制代码
2.buildOwner将element添加到集合_dirtyElements中,并通知ui.window安排新的一帧
buildOwner会将所有dirty的Element添加到_dirtyElements当中,等待下一帧绘制时集中处理。
还会调用ui.window.scheduleFrame();通知底层渲染引擎安排新的一帧处理。
void scheduleBuildFor(Element element) {
...
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
复制代码
3.底层引擎最终回调到Dart层, 完成计算渲染回收
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
}
...
}
复制代码
3.1 执行buildOwner的buildScope方法
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();
...
}
}
复制代码
3.1.1 按照Element的深度从小到大,对_dirtyElements进行排序
3.1.2 遍历执行_dirtyElements当中element的rebuild方法
遍历执行的过程中,也有可能会有新的element被加入到_dirtyElements集合中,此时会根据dirtyElements集合的长度判断是否有新的元素进来了,如果有,就重新排序。
void rebuild() {
if (!_active || !_dirty)
return;
Element debugPreviousBuildTarget;
performRebuild();
}
复制代码
element的rebuild方法最终会调用performRebuild(),而performRebuild()不同的Element有不同的实现
执行performRebuild()
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的)
3.1.3 遍历结束之后,清空dirtyElements集合
3.2 执行WidgetsBinding 的drawFrame (), PipelineOwner对RenderObject管理, 更新页面
@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. 一些语义场景需要
}
复制代码
3.3 执行了buildOwner.finalizeTree()清理
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在最后将被清空,就没办法在复用了。
updateChildren详细过程
@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;
}
复制代码
- 从前往后, 依次判断oldChild是否存在(如果是GlobalKey, 则当做不存在处理), 并且新旧节点是否相同, 皆是则执行updateChild对比里面的节点, 返回Element, 赋值给newChildren, 记录下一个没有对比的新旧节点序号; 如果不同, 则跳出循环.
- 从后往前, 依次判断oldChild是否存在, 并且新旧节点是否相同, 皆是则记录下一个没有对比的新旧节点序号; 如果不同, 则跳出循环.
- 判断是否还有未比较的oldChildren, 如果有, 则获取到所有含有key的节点, 存入oldKeyedChildren, 不包括GlobalKey.
- 循环未比较的newChildren, 是否存在key, 存在则对比key是否在oldKeyedChildren中存在, 存在则移除oldKeyedChildren中对于的key, 最后调用updateChild返回Element, 赋值给newChildren.
- 再次循环, 把后面具有相同key的数据赋值给newChildren.
- 删除多余的oldChildren.