如何刷新Flutter应用的一个界面?被问到这个问题,相信很多人第一个想到的都是setState。没错,setState方法确实可以让State的build方法重走,从而达到刷新界面的效果。但是你有没有想过,为什么setState可以触发rebuild呢?rebuild后视图又是如何更新的呢?
带着这些疑问,有目标的开始探索源码吧!
1、setState做了什么
State -> setState
[->flutter/src/widgets/framework.dart]
void setState(VoidCallback fn) {
assert(fn != null);
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
///...
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
///...
}
return true;
}());
final dynamic result = fn() as dynamic;
assert(() {
if (result is Future) {
///...
return true;
}());
_element.markNeedsBuild();
}
点进setState方法我们会发现有一堆assert,最终会调用_element.markNeedsBuild()方法。
StatefulElement -> markNeedsBuild
[->flutter/src/widgets/framework.dart]
void markNeedsBuild() {
assert(_debugLifecycleState != _ElementLifecycle.defunct);
if (!_active)
return;
assert(owner != null);
assert(_debugLifecycleState == _ElementLifecycle.active);
///...
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
StatefulElement本身没有重写markNeedsBuild方法,所以我们最终调用的还是Element中的markNeedsBuild方法。可以看到上面也是一堆断言,最终判断当前element是否已被标记为dirty,如果没有则标记为dirty,然后调用owner.scheduleBuildFor(this)方法。
BuildOwner -> scheduleBuildFor
[->flutter/src/widgets/framework.dart]
void scheduleBuildFor(Element element) {
assert(element != null);
assert(element.owner == this);
///...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
///...
}
scheduleBuildFor方法首先会去调用onBuildScheduled方法,然后把element放入_dirtyElements中,并且标记为dirty。只是加入脏列表中的话并不会造成什么实际效果,因此主动触发刷新的逻辑应该是在onBuildScheduled方法中。
/// Called on each build pass when the first buildable element is marked
/// dirty.
VoidCallback onBuildScheduled;
onBuildScheduled是一个回调,我们需要追踪传入的地方。
WidgetsBinding -> initInstances
[->flutter/src/widgets/binding.dart]
void initInstances() {
super.initInstances();
_instance = this;
assert(() {
_debugAddStackFilters();
return true;
}());
// Initialization of [_buildOwner] has to be done after
// [super.initInstances] is called, as it requires [ServicesBinding] to
// properly setup the [defaultBinaryMessenger] instance.
_buildOwner = BuildOwner();
buildOwner.onBuildScheduled = _handleBuildScheduled;
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}
通过追踪我们找到了BuildOwner被创建的地方,也就是WidgetsBinding的初始化方法。onBuildScheduled的调用最终会调用WidgetsBinding的_handleBuildScheduled方法。
WidgetsBinding -> _handleBuildScheduled
[->flutter/src/widgets/binding.dart]
void _handleBuildScheduled() {
// If we're in the process of building dirty elements, then changes
// should not trigger a new frame.
///...
ensureVisualUpdate();
}
_handleBuildScheduled方法上面也是个很长的断言,最终会调用ensureVisualUpdate方法。
SchedulerBinding -> ensureVisualUpdate
[->flutter/src/scheduler/binding.dart]
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
此方法会判断当前的阶段,如果处于空闲或者下一帧回调状态,则会执行scheduleFrame方法,否则什么也不做。
SchedulerBinding -> scheduleFrame
[->flutter/src/scheduler/binding.dart]
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
///...
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
此方法首先会调用ensureFrameCallbacksRegistered方法。然后调用window的scheduleFrame方法。
SchedulerBinding -> ensureFrameCallbacksRegistered
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}
此方法会注册window下的onBeginFrame和onDrawFrame回调。
Window -> scheduleFrame
[->sky_engine/ui/window.dart]
/// Requests that, at the next appropriate opportunity, the [onBeginFrame]
/// and [onDrawFrame] callbacks be invoked.
///
/// See also:
///
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
走到这里,就陷入native层了。通过注释我们可以看到,onBeginFrame和onDrawFrame回调将会被调用。
SchedulerBinding -> _handleBeginFrame
[->flutter/src/scheduler/binding.dart]
void _handleBeginFrame(Duration rawTimeStamp) {
if (_warmUpFrame) {//如果当前帧已被处理 直接return
assert(!_ignoreNextEngineDrawFrame);
_ignoreNextEngineDrawFrame = true;//忽略后续的drawFrame
return;
}
handleBeginFrame(rawTimeStamp);
}
如果当前帧还未被处理,则会调用handleBeginFrame方法
SchedulerBinding -> handleBeginFrame
void handleBeginFrame(Duration? rawTimeStamp) {
Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
_firstRawTimeStampInEpoch ??= rawTimeStamp;
//调整当前帧时间戳
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null)
_lastRawTimeStamp = rawTimeStamp;
///...
assert(schedulerPhase == SchedulerPhase.idle);//只有当前进度为空闲才可往下走
_hasScheduledFrame = false;
try {
// TRANSIENT FRAME CALLBACKS
Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
_schedulerPhase = SchedulerPhase.transientCallbacks;//更新进度
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
//回调
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;//更新进度
}
}
可以看到这个方法主要是用来进行一些调整,然后回调当前的进度。
SchedulerBinding -> _handleDrawFrame
[->flutter/src/scheduler/binding.dart]
void _handleDrawFrame() {
if (_ignoreNextEngineDrawFrame) {
_ignoreNextEngineDrawFrame = false;
return;
}
handleDrawFrame();
}
如果该帧没被忽略,则会调用到handleDrawFrame中。
SchedulerBinding -> handleDrawFrame
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
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(); //postFrameCallback被调用后会清除
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
} finally {
_schedulerPhase = SchedulerPhase.idle;//更新状态
Timeline.finishSync(); // end the Frame
///...
_currentFrameTimeStamp = null;
}
}
可以看到此方法也是用来进行回调的。
至此,onBuildScheduled回调执行完毕。element被加入了_dirtyElements中,setState方法也执行完成了。
小结
看完上面的一大堆的调用,我们可以知道setState本质上是调用了element的markNeedsBuild方法。该方法会尝试触发帧的调度,然后把当前element加入到BuildOwner的脏列表中。在脏列表中的数据在一系列调度后,会被更新到屏幕上。
2、如何刷新一个StatelessWidget
这个问题我之前在面试中被问过。当时我并没有太深入阅读Flutter的源码,因此当时我的回答是用外部刷新。现在想想,回答的还是有点肤浅了。
虽然StatelessWidget设计上是无状态的,也没有暴露任何刷新的方法,但是我们想要去刷新它也不是不可以的。上面分析了那么多我们已经知道了,setState刷新一个StatefulWidget的本质是调用element的markNeedsBuild方法来触发更新,而StatelessWidget自身也是有Element的。因此我们只要调用了StatelessWidget的Element的markNeedsBuild方法,就可以刷新一个StatelessWidget。
代码验证一下:
class RefreshStateless extends StatelessWidget {
StatelessElement element;
String text = '测试';
@override
Widget build(BuildContext context) {
element = context;
return GestureDetector(
onTap: () {
text = '我被刷新啦';
element.markNeedsBuild();
},
child: Container(
child: Text(text),
),
);
}
}
在build方法中我们保存了StatelessWidget的BuildContext也就是StatelessElement。最开始我们展示的是测试字样。点击后我们把文字更新为我被刷新啦,然后手动调用markNeedsBuild方法。
一开始运行后显示的使我们设置的初始值:
点击后会刷新成如下:
可以看到刷新成功了。只要我们改变了build方法下的描述内容,StatelessWidget也是可以被刷新的,当然并不推荐这么做。
3、脏列表是如何被更新的
上文我们已经知道了setState的本质是将当前的element加入脏列表,脏列表中的数据在后续会被调度处理。现在我们来追踪一下脏列表,看看它是如何被处理的。
BuildOwner -> buildScope
首先在BuildOwner下我们发现脏列表会在buildScope方法下被处理,接着追踪该方法的调用:
WidgetsBinding -> drawFrame
void drawFrame() {
///...
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();//call super
buildOwner.finalizeTree();
}
///...
}
由调用链我们追踪到WidgetsBinding的drawFrame方法下调用了此方法,调用完成后会再调用父类的drawFrame方法。现在需要再看看WidgetsBinding的drawFrame方法是如何被调用的:
可以看到只在一处被调用。
RendererBinding -> _handlePersistentFrameCallback
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();//调用drawFrame
_scheduleMouseTrackerUpdate();
}
继续追踪调用:
RendererBinding -> initInstances
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
addPersistentFrameCallback(_handlePersistentFrameCallback);//持久回调
initMouseTracker();
}
我们可以看到该方法被注册为一个持久的监听,每当有帧更新的时候都会被调用。该回调具体的回调时机我们已经分析过了,在触发了window的shceduleFrame回调后,会被调用,具体回调的实现SchedulerBInding 的 handleDrawFrame方法中。
现在我们可以知道了脏列表更新的方法调用顺序了:
BuildOwner -> buildScope
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
///... assert部分
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);//开始Build
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
///...
_dirtyElementsNeedsResorting = false;
try {
callback();
} finally {
///... a
}
}
_dirtyElements.sort(Element._sort);//按深度排序 自上而下开始构建
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
///... assert
try {
_dirtyElements[index].rebuild();//调用 dirty element的rebuild方法
} catch (e, stack) {
/// ...error handle
}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
//build之后可能会有新的脏列表数据 在此进行处理
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;//调整dirtyCount
while (index > 0 && _dirtyElements[index - 1].dirty) {
// It is possible for previously dirty but inactive widgets to move right in the list.
// We therefore have to move the index left in the list to account for this.
// We don't know how many could have moved. However, we do know that the only possible
// change to the list is that nodes that were previously to the left of the index have
// now moved to be to the right of the right-most cleaned node, and we do know that
// all the clean nodes were to the left of the index. So we move the index left
// until just after the right-most clean node.
index -= 1;
}
}
}
///...
} finally {
for (final Element element in _dirtyElements) {
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
///...
}
assert(_debugStateLockLevel >= 0);
}
buildScope方法会将_dirtyElements列表数据重新排序,然后自上而下调用rebuild方法。
Element -> rebuild
void rebuild() {
///... assert
performRebuild();//实际执行build的地方
///... assert
}
rebuild方法里只是一堆判断和断言,最后把处理逻辑交给了performRebuild方法。不同的Element有不同的处理逻辑。这里看一下StatefulElement的实现。
StatefulElement -> performRebuild
void performRebuild() {
if (_didChangeDependencies) {
_state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
如果依赖发生了改变,会回调state的didChangeDependencies,接着调用了父类的performRebuild方法。
ComponentElement -> performRebuild
void performRebuild() {
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built;
try {
assert(() {
_debugDoingBuild = true;
return true;
}());
built = build();//调用build方法
assert(() {
_debugDoingBuild = false;
return true;
}());
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
///...
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);//更新element
assert(_child != null);
} catch (e, stack) {
///...
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
这里我们可以看到build方法被调用,生成了新的Widget配置,接着通过updateChild方法来更新element。updateChild方法的实现继承自Element。
Element -> updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
///hot reload情况下可能会发生element类型改变
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
//element类型一致 配置一致
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//element类型一致 配置类型和key一致
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);//更新widget
assert(child.widget == newWidget);
assert(() {
child.owner._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
//创建新的element
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//创建新的element
newChild = inflateWidget(newWidget, newSlot);
}
///...
return newChild;
}
这段代码的逻辑并不复杂。如果newWidget为null,则element的配置没有了,这时候需要把这个element从树中移除。如果之前的child为空,则加载newWidget的配置,生成一个element返回。否则会判断child能否更新。只有不能更新的情况下才会创建一个新的Element。
StatefulElement -> update
void update(StatefulWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;//回调didUpdateWidget
///...
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();//rebuild
}
StatefulElement在update方法中回调了didUpdateWidget生命周期并马上rebuild。
RenderBinding -> drawFrame
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame(); //实际渲染的地方
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
终于走到这里,这也是实际构建视图的地方。pipelineOwner会进行布局、混合、绘制等一系列的操作。最后通过RenderView的compositeFrame实际的去进行渲染一帧。
BuildOwner -> finalizeTree
void finalizeTree() {
Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
try {
lockState(() {
//取消挂载不活动的element
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
});
///...
} catch (e, stack) {
///...
} finally {
Timeline.finishSync();
}
}
这个方法看起来很长,实际在非debug模式下只做了一件事。那就是释放不活动的element。
4、总结
不想再写了,就把图补全一下吧。这里先把setState的流程图再补完一下:
再来看脏列表刷新相关调用: