flutter绘制流程——rebuild
rebuild是Element的方法,有两种场景下会被调用:
- element第一次构建mount的时候
- widget发生变化的时候
void rebuild() {
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
performRebuild();
}
主要逻辑分为2步
- 判断状态是否是active,_dirty是否为true
- 执行performRebuild(),这是个抽象方法,所以具体rebuild的逻辑由element子类去实现
下面重点来看performRebuild()
performRebuild.png
1 performRebuild()
顾名思义真正执行重新build的地方,因此每个实现类会有所不同,下面看下不同类型Element的实现
1.1 RenderObjectElement
更新renderObject,当然还有一些RenderObjectElement的继承类可能还做了其他逻辑
@override
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
1.1.1 SliverMultiBoxAdaptorElement
对于SliverGrid,SliverList,ListView都会用到它,这里逻辑比较多,今天这篇不去细讲,大概了解有关键逻辑 _build(index)
和updateChild`,
@override
void performRebuild() {
super.performRebuild();
final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>();
void processElement(int index) {
//省略...
final Element? newChild = updateChild(newChildren[index], _build(index), index);
//省略...
}
//省略...
newChildren.keys.forEach(processElement);
//省略...
}
1.2 ComponentElement
void performRebuild() {
Widget? built;
try {
//widget重建 如:StatelessElement
built = build();
} catch (e, stack) {
} finally {
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
//..省略
}
}
- 执行build(),作为新的newWidget
- _child = updateChild(_child, built, slot);
1.2.1 StatelessElement
未覆写,逻辑同ComponentElement的performRebuild()
1.2.2 StatefulElement
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
在build之前判断需要didChangeDependencies
总结:performRebuild()实现分两类,ComponentElement和RenderObjectElement
- RenderObjectElement会updateRenderObject,对于有child的继承类会进行_build(index)和updateChild
- ComponentElement的performRebuild主要分为两步。1:build(); 2:updateChild。下面依次展开
2 build()
2.1 ComponentElement
/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
@protected
Widget build();
用来build返回widget,这个我们就很熟悉了,写UI代码主要围绕在这一块
- StatelessElement调用
widget.build(this);
- StatefulElement 调用
state.build(this);
- ProxyElement 直接返回
widget.child
2.2 SliverMultiBoxAdaptorElement
在1.1.1中的performRebuild()执行的是_build
Widget? _build(int index) {
return widget.delegate.build(this, index);
}
看到SliverChildDelegate中的定义,和ComponentElement的build意思差不多,只不是事根据传入index来返回widget
/// Returns the child with the given index.
///
/// Should return null if asked to build a widget with a greater
/// index than exists. If this returns null, [estimatedChildCount]
/// must subsequently return a precise non-null value (which is then
/// used to implement [RenderSliverBoxChildManager.childCount]).
///
/// Subclasses typically override this function and wrap their children in
/// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets.
///
/// The values returned by this method are cached. To indicate that the
/// widgets have changed, a new delegate must be provided, and the new
/// delegate's [shouldRebuild] method must return true.
Widget? build(BuildContext context, int index);
总结:不管是ComponentElement中的build
,还是SliverMultiBoxAdaptorElement中的_build
,最终都是用来构建一个Widget。
下面看到performRebuild的下一步updateChild。
3 updateChild
3.1 Element/ComponentElement
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot)
,用来更新子element
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
//如果newWidget是null,并且old child非null,直接deactivateChild
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
//新旧widget相同的情况
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (Widget.canUpdate(child.widget, newWidget)) {
//可以update的情况,也就是runtimetype和key相同
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
//其他情况,移除旧的,重新inflateWidget新的widget,会创建element
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//old child是null,这里直接inflate新的widget,会创建element
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
总共分以下几种情况
- newWidget是null,则清理掉old child(如果old child不为null),返回null
- 新旧widget相同,说明数据没有变化,直接返回旧的child
- widget可以update,child.update(newWidget); 直接更新old child即可
- 其他情况,重新inflateWidget,这里会createElement,清理掉old child(如果old child不为null)
3.2 SliverMultiBoxAdaptorElement
RenderObjectElement的实现类SliverMultiBoxAdaptorElement额外处理的就是更新child的renderobject的parentData
@override
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
final SliverMultiBoxAdaptorParentData? oldParentData = child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
final Element? newChild = super.updateChild(child, newWidget, newSlot);
final SliverMultiBoxAdaptorParentData? newParentData = newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
// Preserve the old layoutOffset if the renderObject was swapped out.
if (oldParentData != newParentData && oldParentData != null && newParentData != null) {
newParentData.layoutOffset = oldParentData.layoutOffset;
}
return newChild;
}
下面看到update
的逻辑
4 update
Element中定义,更新widget
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
4.1 StatelessElement
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
4.2 StatefulElement
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = state._widget!;
_dirty = true;
state._widget = widget as StatefulWidget;
state.didUpdateWidget(oldWidget) as dynamic;
rebuild();
}
4.3 RenderObjectElement
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
下面看几个典型的实现类
4.3.1 SingleChildRenderObjectElement
@override
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_child = updateChild(_child, widget.child, null);
}
4.3.2 MutliChildRenderObjectElement
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
总结:对于update方法首先一定会做的就是更新_widget。然后对于ComponentElement和RenderObjectElement的逻辑有所不同。
- ComponentElement主要会进行
rebuild();
这样又回到最初的rebuild
,只是到了子节点 - RenderObjectElement则会更新自己的renderObject,然后根据拥有child是否是多个逻辑有所不同,如:
- SingleChildRenderObjectElement只有一个child,执行
updateChild
这样也回到了前面的步骤3 - MutliChildRenderObjectElement可能有多个child,执行
updateChildren
- SingleChildRenderObjectElement只有一个child,执行
下面重点开看updateChildren
5 updateChildren
RenderObjectElement中定义,针对可能有多个child的element的更新逻辑
5.1 定义新旧children的开始和结束位置,用于后面遍历
//定义old和new的首尾位置
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
//根据old和new的长度判断,如果相同则newChildren直接使用oldChildren,如果不同,则创建一个长度为newWidgets.length的list,使用_NullElement.instance来填充
final List<Element> newChildren = oldChildren.length == newWidgets.length ?
oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);
5.2 从开始位置遍历children,主要处理可以直接updateChild的情况,碰到不能update则直接跳出,newChildrenTop和oldChildrenTop会指向不能update的child位置
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
//判断oldChild是否被移除
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
final Widget newWidget = newWidgets[newChildrenTop];
//oldChild是空或者newWidget不能直接更新,就跳出
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
//更新child
final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
//设置到newChild中
newChildren[newChildrenTop] = newChild;
//设置上一个child
previousChild = newChild;
//移动到下一个位置
newChildrenTop += 1;
oldChildrenTop += 1;
}
5.3 从底部开始遍历判断canUpdate,知道返回false,跳出,这样oldChildrenBottom和newChildrenBottom指向末尾不能update的child
// 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;
//注意这里和step2的区别是没有去设置previousChild了,并且没有updateChild
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
5.4 从oldChildrenTop开始遍历oldChildren,取出widget.key不为null的child,存入oldKeyedChildren,后面可能取出进行复用,这里oldChildrenTop应该等于oldChildrenBottom+1
// Scan the old children in the middle of the list.
// 根据top和bottom位置判断是否还存在中间的元素没有处理
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
//用于存放有key的old child
Map<Key, Element>? oldKeyedChildren;
if (haveOldChildren) {
oldKeyedChildren = <Key, Element>{};
//从顶部开始遍历oldChildren
while (oldChildrenTop <= oldChildrenBottom) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if (oldChild != null) {
//如果有key,则存入oldKeyedChildren
if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key!] = oldChild;
else
//没有直接废弃oldChild
deactivateChild(oldChild);
}
//注意:这里是old child的位置移动
oldChildrenTop += 1;
}
}
5.5 从newChildrenTop开始遍历newWidgets,根据key从oldKeyedChildren取出old child,然后判断是否可以直接update,如果可以则在updateChild的作为oldChild参数传入,否则传null。到这里newChildrenTop应该等于newChildrenBottom+1
// Update the middle of the list.
//从顶部更新newChildren
while (newChildrenTop <= newChildrenBottom) {
Element? oldChild;
final Widget newWidget = newWidgets[newChildrenTop];
if (haveOldChildren) {
final Key? key = newWidget.key;
//判断new child是否有key
if (key != null) {
//获取old child有相同key的child
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, slotFor(newChildrenTop, previousChild))!;
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
}
5.6 在4.3中只是做了newChildrenBottom和oldChildrenBottom的标记,并没有真正的updateChild,所以。下面重置newChildrenBottom和oldChildrenBottom。继续从oldChildrenTop开始遍历,然后updateChild
// We've scanned the whole list.
//重置bottom位置
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];
//更新剩余的child
final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
5.7 清理没有复用成功的child
// Clean up any of the remaining middle nodes from the old list.
if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
for (final Element oldChild in oldKeyedChildren.values) {
//将剩下带有key的old child,同时又没能复用的child进行clean
if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
deactivateChild(oldChild);
}
}
return newChildren;
}
总结:这么长的流程和逻辑主要是为了对多个children的情况要进行判断是否可以复用,对于不能复用的child进行清理,最终针对child还是会执行到updateChild
这样又回到了3
6 inflateWidget
Element中定义,在第3节中 updateChild
如果old child是空或者无法update就需要inflateWidget
写android的朋友应该很熟悉了,android里有LayoutInflater.from().inflate()
,从xml来解析获取到View;同样在这里通过widget来解析返回Element。关于GlobalKey的逻辑,我们先忽略,后面再介绍。下面的逻辑就简单了,创建一个element,然后mount到当前element
Element inflateWidget(Widget newWidget, Object? 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!;
}
}
//创建子Element
final Element newChild = newWidget.createElement();
//将child挂载到当前element
newChild.mount(this, newSlot);
//返回child element
return newChild;
}
7 总结
走完上面整个rebuild流程,第一感受就是在于ComponentElement和RenderObjectElement在流程上有明显的区别,这也回到这两类Element的设计,RenderObjectElement不一定包含子child,但它包括renderObject用于渲染,而ComponentElement是一种组成的Element,它并不包含RenderObject,但它会有子 Element。因此在rebuild时ComponentElement只需要关心child的update,而RenderObjectElement还需要关注RenderObject的更新。另外在多child的情况如:第5节,diff的逻辑会稍微复杂一点。
对于整个流程中的关于方法我们也要熟悉,如:update,inflateWidget,updateChild。