setState方法算是flutter使用最频繁的方法了,每次页面数据有改变,都需要调用这个方法,去触发页面的刷新,展示最新的UI效果,接下来从源码角度解读下setState后具体发生了什么
系统源码部分,会做截取,仅保留跟主题有关的部分,开始吧
void setState(VoidCallback fn) {
// 省略了一大堆的判断代码
final Object? result = fn() as dynamic;
_element!.markNeedsBuild();
}
上面可以看到,回调方法VoidCallback fn是马上会被同步执行,然后调用这个widget对应的element的markNeedsBuild方法
void markNeedsBuild() {
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
就是把这个element标记为dirty,如果已经标记过,则忽略,说明连续调用两次setState方法,第二次其实是多余的,然后是调用owner的scheduleBuildFor方法
这里的owner,是BuildOwner,先记住全局只有一个BuildOwner实例,它是在启动的时候创建的,这里先不展开说明,我们先记住全局就一个owner就好
void scheduleBuildFor(Element element) {
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
上面的源码,其实写法上有点不合理,我改下,效果一样,但是更合理些
_dirtyElements.add(element);
element._inDirtyList = true;
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
}
首先,先把当前的element加到一个_dirtyElements的数组里面,_scheduledFlushDirtyElements用于判断有没有调用过刷新dirtyElement,一般会是false,然后调用onBuildScheduled!()方法,这个方法,其实是一个回调方法
VoidCallback? onBuildScheduled;
真正的方法是这个
void _handleBuildScheduled() {
ensureVisualUpdate();
}
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
判断当前的状态,然后触发scheduleFrame,计划刷新下一帧
void scheduleFrame() {
ensureFrameCallbacksRegistered();
window.scheduleFrame();
}
这个方法,其实就是让系统触发下一帧的刷新,系统刷新有固定的频率,一般是一秒60帧,内部已经是底层的方法了
// window内方法
void scheduleFrame() => platformDispatcher.scheduleFrame();
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
调用到engine层的方法了,告知系统触发下一帧的回调,然后会收到系统下一帧刷新的回调,接收方法在这里
void _handleDrawFrame() {
handleDrawFrame();
}
这个方法被调用,说明已经到下一帧的刷新时间了
void handleDrawFrame() {
_frameTimelineTask?.finish(); // 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 localPostFrameCallbacks =
List.of(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
} finally {
_schedulerPhase = SchedulerPhase.idle;
_frameTimelineTask?.finish(); // end the Frame
}());
_currentFrameTimeStamp = null;
}
}
这个方法有点长,widget的回调其实是在_persistentCallbacks里面,继续走到_invokeFrameCallback方法里.
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp) {
try {
callback(timeStamp);
} catch (exception, exceptionStack) {
}
}
这里的callback方法,其实下面这个方法:
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
继续调用drawFrame方法
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner!.buildScope(renderViewElement!);
super.drawFrame();
buildOwner!.finalizeTree();
} finally {
}
}
又回到了调用buildOwner的方法了
void buildScope(Element context) {
try {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
final Element element = _dirtyElements[index];
try {
element.rebuild();
} catch (e, stack) {
}
} finally {
for (final Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
}
}
先把_dirtyElements做下排序,越靠近root的靠前,先执行刷新,然后调用每个element的rebuild方法,最后再把_dirtyElements数组清空
void rebuild() {
performRebuild();
}
继续往下调用
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
然后调用super的performRebuild方法,然后又回调到StatefulElement的build方法
Widget build() => state.build(this);
最终触发的地方,就是在这里了
总结setState其实就是告诉系统,在下一帧刷新的时候,需要更新当前widget,整个过程,是一个异步的行为,所以下面的三个写法,效果上是一样的
// 写法一
_counter++;
setState(() {});
// 写法二
setState(() {
_counter++;
});
// 写法三
setState(() {});
_counter++;