Flutter源码学习--State
写在前面:本人小白一枚,自己阅读源码,有什么错误的地方希望大家多多包容
Flutter源码学习--State
一、setState
首先看看Flutter的State源码中对setState方法的注释是怎么说的:
/// Notify the framework that the internal state of this object has changed.
///
/// Whenever you change the internal state of a [State] object, make the
/// change in a function that you pass to [setState]:
///
/// ```dart
/// setState(() { _myState = newValue });
/// ```
///
/// The provided callback is immediately called synchronously. It must not
/// return a future (the callback cannot be `async`), since then it would be
/// unclear when the state was actually being set.
///
/// Calling [setState] notifies the framework that the internal state of this
/// object has changed in a way that might impact the user interface in this
/// subtree, which causes the framework to schedule a [build] for this [State]
/// object.
///
/// If you just change the state directly without calling [setState], the
/// framework might not schedule a [build] and the user interface for this
/// subtree might not be updated to reflect the new state.
///
/// Generally it is recommended that the `setState` method only be used to
/// wrap the actual changes to the state, not any computation that might be
/// associated with the change. For example, here a value used by the [build]
/// function is incremented, and then the change is written to disk, but only
/// the increment is wrapped in the `setState`:
///
/// ```dart
/// Future<void> _incrementCounter() async {
/// setState(() {
/// _counter++;
/// });
/// Directory directory = await getApplicationDocumentsDirectory();
/// final String dirName = directory.path;
/// await File('$dir/counter.txt').writeAsString('$_counter');
/// }
/// ```
///
/// It is an error to call this method after the framework calls [dispose].
/// You can determine whether it is legal to call this method by checking
/// whether the [mounted] property is true.
@protected
void setState(VoidCallback fn)
提取出几条重要的信息:
- setState方法的参数(Voidback回调)的作用是执行你的意志,即将State的变化在回调中告诉系统;而且,在这个回调中不能干计算密集型的事情,也就是说setState的参数回调中只需要声明State最终的变化,表现起来几乎就是一些赋值语句。
- setState方法不能再dispose方法中调用,setState的参数不能的返回值不能使Future
- setState方法是"线程"安全的(当然Dart中没有线程这一说)
- 调用setState方法是否是正确的可以根据State的mounted属性是否为true来判断,为true则表示安全
1. State的状态
在framework.dart中定义了一个枚举类_StateLifecycle用来定义State的生命周期:
framework.dart:
enum _StateLifecycle {
created, //State刚被创建,但是还没有"初始化";对应于方法调用流程即StatefulWidget的createState方法执行完毕,而State的initState方法还没有执行。
initialized, //State执行完initState了,但是还没有准备好去build;即State的didChangeDependencies还没有被执行
ready, //State已经准备好去build了,但是dispose方法还没有被执行
defunct, //State的dispose方法已经被执行了,意味着State生命的结束
}
State的生命周期还是很容易记住的,对应的几个方法也是常用到的
2. setState方法
setState方法很简单,刚开始是几个assert进行校验,这里只列出重要的代码:
@protected
void setState(VoidCallback fn) {
....
final dynamic result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError(
...
);
}
return true;
}());
_element.markNeedsBuild();
}
抽丝剥茧后的setState方法就是这么简单:
- 执行了setState的VoidBack回调,并检验其返回的不是Future对象
- 调用_element.markNeedsBulid()
2.1 _element的实现是什么类型?如何初始化的?
State中对_element的定义如下:
BuildContext get context => _element;
StatefulElement _element;
用它作为实际的context,而_element的实际类型是StatefulElement;在State源码的其他地方并未找到初始化_element的代码;推测其初始化的地方应该是StatefulWidget的createState中(State在定义context属性时的注释确实说明了这一点)
2.2 StatefulWidget的createState
framework.dart中StatefulWidget的createState方法是一个空方法,让人有点失落,本来猜测可能是StatefulWidget的子类中去实现的,看了看MaterialApp的createState方法,也没有找到StatefulElement是如何被创建,并且传递给State的。
在StatefulWidget中发现了另外一个方法:createElement() => StatefulElement(this);虽然不知道StatefulElement是如何传递给State的,但是这个方法应该就是StatefulElement的初始化的地方了。
二、markNeedsBuild
上面提到了State中的_element实际类型是StatefulElement,经过查看StatefulElement的该方法来源是其间接父类Element,并且该方法由Element实现,子类并未重写
Element.class
老样子,方法实现中刚开始也是一对assert校验,简化后的代码依然很简单:
void markNeedsBuild() {
......
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
其中的dirty属性看来是起一种标记的作用,看看其在Element中是如何定义的:
/// Returns true if the element has been marked as needing rebuilding.
bool get dirty => _dirty;
bool _dirty = true;
这一下,代码就传递到了owner的scheduleBuildFor(this)
三、scheduleBuildFor
同样,去掉assert校验后的方法体为:
void scheduleBuildFor(Element element) {
........
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
操作就是执行onBuildScheduled()方法,而后将element添加到BuildOwner的_dirtyElements中,并将element标记为已经在_dirtyElements中
很想知道BuilderOwner的onBuildScheduled()方法是如何实现的,虽然发生的原因是由于_element调用了内部的owner.scheduleBuildFor
(this)方法,但是我并没有在StatefulElement的构造器中发现初始化owner的代码。
不负众望在widget/binding.dart的WidgetsBinding中发现了BuildOwner的初始化语句,以及其onBuildScheduled的来源:
/// The [BuildOwner] in charge of executing the build pipeline for the
/// widget tree rooted at this binding.
BuildOwner get buildOwner => _buildOwner;
final BuildOwner _buildOwner = BuildOwner();
@override
void initInstances() {
super.initInstances();
_instance = this;
buildOwner.onBuildScheduled = _handleBuildScheduled; //onBuildScheduled的来源
ui.window.onLocaleChanged = handleLocaleChanged;
ui.window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
SystemChannels.system.setMessageHandler(_handleSystemMessage);
}
_handleBuildScheduled()
void _handleBuildScheduled() {
// If we're in the process of building dirty elements, then changes
// should not trigger a new frame.
assert(() {
if (debugBuildingDirtyElements) {
throw FlutterError(
'Build scheduled during frame.\n'
'While the widget tree was being built, laid out, and painted, '
'a new frame was scheduled to rebuild the widget tree. '
'This might be because setState() was called from a layout or '
'paint callback. '
'If a change is needed to the widget tree, it should be applied '
'as the tree is being built. Scheduling a change for the subsequent '
'frame instead results in an interface that lags behind by one frame. '
'If this was done to make your build dependent on a size measured at '
'layout time, consider using a LayoutBuilder, CustomSingleChildLayout, '
'or CustomMultiChildLayout. If, on the other hand, the one frame delay '
'is the desired effect, for example because this is an '
'animation, consider scheduling the frame in a post-frame callback '
'using SchedulerBinding.addPostFrameCallback or '
'using an AnimationController to trigger the animation.'
);
}
return true;
}());
ensureVisualUpdate();
}
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() {
if (_hasScheduledFrame || !_framesEnabled)
return;
assert(() {
if (debugPrintScheduleFrameStacks)
debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
return true;
}());
ui.window.scheduleFrame();
_hasScheduledFrame = true;
}
可见最终调用的是window.scheduleFrame()进行重新绘制
四、总结
通过源码的学习可以有一些收获:
再思setState
- 可以看到在整个过程中,setState方法的作用真的只是作为一个接口,来通知_element启动markNeedsBuild,而setState的参数回调的作用就只是更改State的一些属性,从而等待下一次绘制frame的到来;
- 可以看到StatefulWidget的setState启动绘制很复杂,我们平时在使用StatefulWidget的时候一定要抓住重点,在WidgetTree的末尾使用StatefulWidget,这样可以减少重新绘制的范围,从而节约成本。比如:有一个界面,内容只有一个ListView,但是含有加载功能,那么该界面的MatrialApp以及Scaffold都应该是写死的,只留下Scaffold的body属性使用StatefulWidget来实现。
- BuildContext:在查看官方文档的时候,经常会提到BuildContext代表了Widget插入的位置,为啥BuildContext可以代表位置呢?而BuildContext的具体类型是一个Element的子类,而Element中不仅定义了Element parent,还定义了一系列访问child或者children的接口;可以说Widget树真的是一个树,而且每个节点都可以访问上级和下级