写在前面
当我们对 StatefulWidget 调用 setState() 方法后,就会触发该 Widget 进行 rebuild,以构建我们新的数据。
内容
加入到脏列表
首先看下 setState()
方法,里面主要做了两件事,同步地调用回调方法,然后调用 Element 的 markNeedsBuild()
方法
/// State
@protected
void setState(VoidCallback fn) {
...
final Object? result = fn() as dynamic;
...
_element!.markNeedsBuild();
}
Element 的 markNeedsBuild()
方法里会把这个 Element 标记为 dirty ,然后调用 BuildOwner 类型的 owner 的 scheduleBuildFor()
方法,把自己加入到一个 List<Element>
类型的 _dirtyElements 的列表里
/// Element
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
/// BuildOwner
void scheduleBuildFor(Element element) {
...
if (element._inDirtyList) {
...
_dirtyElementsNeedsResorting = true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
关于 BuildOwner
Element 里的这个 BuildOwner 类型的 owner ,是用来管理当前 Element 的生命周期的。
Element 的 owner 是在其 mount()
方法被调用的时候,从其 parent 那里赋值的。但它上面总会有个头,就是来自 RootRenderObjectElment 的 assignOwner(BuildOwner owner)
方法
/// RootRenderObjectElement
abstract class RootRenderObjectElement extends RenderObjectElement {
...
void assignOwner(BuildOwner owner) {
_owner = owner;
}
...
}
这个方法被调用是在 binding.dart 的 RenderObjectToWidgetAdapter 里的 attachToRenderTree(BuildOwner owner)
方法里被调用:
/// RenderObjectToWidgetAdapter
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
...
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()
,在这里的时候就会传入第一个 buildOwner。
/// WidgetsBinding
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance!.ensureVisualUpdate();
}
}
@protected
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}
attachToRenderTree()
这个方法再继续跟踪其上面的调用的话,就是runApp()
里WidgetsFlutterBinding.ensureInitialized()
的scheduleAttachRootWidget()
方法
而这个 BuildOwner 是一个来自 WidgetsBinding 的属性,它在 WidgetsBinding 的初始化实例函数里被创建。
/// WidgetsBinding
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
...
_buildOwner = BuildOwner();
...
}
BuildOwner? _buildOwner;
...
}
下一帧的回调
那么从上面我们知道 setState()
的方法里是跟 BuildOwner 有关,那么是怎么起作用的呢?
ScheduleBinding 里有一个 handleDrawFrame()
的方法,这个方法在 engine 产生新的一帧的时候会被调用:
void handleDrawFrame() {
...
Timeline.finishSync(); // end the "Animate" phase
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
} finally {
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
...
}
}
它主要是触发 persistent 回调和 postFrame 回调,persistent 回调是典型地作为 build, layout,paint 的渲染管道,postFrame 回调则是用来做清理和下一帧的准备工作。
那既然 ScheduleBinding 这里有 handleDrawFrame()
方法用来处理监听帧变化的回调,那么这些回调是一开始在哪里注册呢?
答案是在 RenderBinding 的 initInstances()
方法里,我们还可以看到回调被触发后,会调用drawFrame()
方法 :
/// 注意 RendererBinding 混入了 SchedulerBinding
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
...
addPersistentFrameCallback(_handlePersistentFrameCallback);
...
}
void addPersistentFrameCallback(FrameCallback callback) {
_persistentCallbacks.add(callback);
}
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
...
}
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
}
因为 RenderBinding 混入了 SchedulerBinding,所以 RenderBinding 可以拿到 _persistentCallbacks 这个列表,就能把回调加入到里面去。
但这里 RenderBinding 自身的 drawFrame()
却没有跟 Widget 构建有关的信息,是一些布局、渲染之类的。
那这里就要跳到 WidgetsBinding 里了,它混入了 RendererBinding ,重写它的 drawFrame()
,做了额外的操作。
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
...
@override
void drawFrame() {
...
try {
if (renderViewElement != null)
buildOwner!.buildScope(renderViewElement!);
super.drawFrame();
buildOwner!.finalizeTree();
} finally {
...
}
...
}
...
}
这里我们就可以知道,上面的回调,会触发到 WidgetsBinding 里重写的 drawFrame()
方法,就看到了我们的 buildOwner ,调用了它的 buildScope()
方法。
/// BuildOwner
void buildScope(Element context, [ VoidCallback? callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
...
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
...
Element? debugPreviousBuildTarget;
...
_dirtyElementsNeedsResorting = false;
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 {
for (final Element element in _dirtyElements) {
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
...
}
...
}
buildScope()
方法里大致就是遍历脏列表,然后调用里面每一个 Element 的 rebuild()
方法,并最后变更状态,清空脏列表。
Rebuild
在 BuildOwner 的 buildScope()
方法里触发了 Element 的 rebuild()
方法,接着是 performRebuild()
。
/// Element
void rebuild() {
...
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
...
Element? debugPreviousBuildTarget;
...
performRebuild();
...
}
performRebuild()
是个抽象方法,依赖于具体的实现。因为我们一开始说的是 StatefulWidget,那就看下 StatefulElement:
/// StatefulElement
@override
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
可以看出调用了 State 里的 didChangeDependencies()
,这就是我们 setState() 的时候,会走 didChangeDependencies()
的原因。另外我们还看到它调用了 super.performRebuild()
,因为 StatefulElement 继承自 ComponentElement。
在 ComponentElement 里的 performRebuild()
方法里:
/// ComponentElement
@override
void performRebuild() {
...
Widget? built;
try {
...
built = build();
...
} catch (e, stack) {
...
} finally {
_dirty = false;
...
}
try {
_child = updateChild(_child, built, slot);
...
} catch (e, stack) {
...
_child = updateChild(null, built, slot);
}
...
}
调用 build()
方法,拿到新的 Widget,然后调用 updateChild()
方法来更新。
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
// 如果 newWidget 为 null,
// child 也为 null,就直接返回 null,
// child 不为 null ,就 deactivate child 后再返回 null
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
// 如果 newWidget 不为 null
// child 不为 null
if (child != null) {
bool hasSameSuperclass = true;
...
if (hasSameSuperclass && child.widget == newWidget) {
// child.widget == newWidget,只要替换就行
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
}
// 如果 canUpdate() 为真,说明只要更新配置信息即可
else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
...
newChild = child;
} else {
// 说明前后的 Widget 是不一样的,那么就 deactivate child ,挂载新的
deactivateChild(child);
...
newChild = inflateWidget(newWidget, newSlot);
}
} else {
// child 为 null,说明这里是新增了 Widget ,就直接创建新的 Element,并添加进给定的 slot 里。
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
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!;
}
}
final Element newChild = newWidget.createElement();
...
newChild.mount(this, newSlot);
...
return newChild;
}
这里面有一套官方规则的总结:
newWidget == null | newWidget != null | |
---|---|---|
child == null | Returns null. | Returns new [Element]. |
child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
像官方一开始的那个更新数字的 Demo,就会走到 Element 的 update()
方法,它的继承者对其有不同的实现:
void update(covariant Widget newWidget) {
...
_widget = newWidget;
}
同样的还是看 StatefulElement 的 update()
方法:
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
...
final StatefulWidget oldWidget = state._widget!;
_dirty = true;
state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
...
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();
}
这里会调用到生命周期里的didUpdateWidget()
,最后调用了 Element 的 rebuild()
方法,又回到了我们前面的流程。但由于这里调用的其实是 child 的update()
方法,所以可想而知就是不断地往下调用。
而这也就是说为什么 setState()
往往会产生比较大的开销的原因,如果 Widget 的粒度相对来说不够细,拆分的不够好,不能针对性的更新,那么就会导致其下方的子 Widget 都要跟着重建。