Flutter Provider原理深入浅出(上) 从Provider的用法入手,深入分析了Provider原理相关的源码,Flutter Provider原理深入浅出(中)结合启动原理分析了Widget tree和Element tree是如何建立关系的,并解释了上遗留的一些疑问。本文将重点分析Provider是如何结合Flutter的渲染原理来实现状态改变影响UI的效果。
原理上第四节Consumer源码分析中,我们分析到buildWithChild
是系统调用的,那深入源码,我们看看具体是怎么调用的,直接上堆栈
Consumer.buildWithChild (consumer.dart:177)
SingleChildStatelessWidget.build (nested.dart:260)
StatelessElement.build (framework.dart:4701)
SingleChildStatelessElement.build (nested.dart:280)
ComponentElement.performRebuild (framework.dart:4627)
Element.rebuild (framework.dart:4343)
BuildOwner.buildScope (framework.dart:2730)
----------------------------------------------------------------------------------------------------------
WidgetsBinding.drawFrame (binding.dart:913)
RendererBinding._handlePersistentFrameCallback (binding.dart:302)
SchedulerBinding._invokeFrameCallback (binding.dart:1117)
SchedulerBinding.handleDrawFrame (binding.dart:1055)
SchedulerBinding._handleDrawFrame (binding.dart:971)
----------------------------------------------------------------------------------------------------------
_rootRun (zone.dart:1190)
_CustomZone.run (zone.dart:1093)
_CustomZone.runGuarded (zone.dart:997)
_invoke (hooks.dart:251)
_drawFrame (hooks.dart:209)
从下往上_drawFrame
是dart虚拟机调用的,里面涉及到UI的渲染原理,具体可以移步这里,1.22.6关于Consumer的源码已经做了改动,但原理基本相似,先看下改动后的源码。
class Consumer<T> extends StatelessWidget
implements SingleChildCloneableWidget {
/// {@template provider.consumer.constructor}
/// Consumes a [Provider<T>]
/// {@endtemplate}
Consumer({
Key key,
@required this.builder,
this.child,
}) : assert(builder != null),
super(key: key);
/// The child widget to pass to [builder].
final Widget child;
/// {@template provider.consumer.builder}
/// Build a widget tree based on the value from a [Provider<T>].
///
/// Must not be `null`.
/// {@endtemplate}
final Widget Function(BuildContext context, T value, Widget child) builder;
@override
Widget build(BuildContext context) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
@override
Consumer<T> cloneWithChild(Widget child) {
return Consumer(
key: key,
builder: builder,
child: child,
);
}
}
对比1.21.x版本,已经不存在buildWithChild
抽象方法了,取而代之的是cloneWithChild
和build
,这里build
是没有回传child
参数的,这里似乎回应了之前我们有疑问的回传了child
但并没使用的问题,但具体原因有待考察,这里build
确实没有拿到之前的child
,但是回调业务层,是把外面传给Consumer
的child
又回传出去了。
新版本(1.22.6)的堆栈
ProviderDebugPage1.build.<anonymous closure> (test_provider_page1.dart:37)
Consumer.build (consumer.dart:180)
StatelessElement.build (framework.dart:4701)
ComponentElement.performRebuild (framework.dart:4627)
Element.rebuild (framework.dart:4343)
ComponentElement._firstBuild (framework.dart:4606)
ComponentElement.mount (framework.dart:4601)
这里省略若干层
Element.rebuild (framework.dart:4343)
StatelessElement.update (framework.dart:4708)
Element.updateChild (framework.dart:3314)
ComponentElement.performRebuild (framework.dart:4652)
Element.rebuild (framework.dart:4343)
ProxyElement.update (framework.dart:4987)
Element.updateChild (framework.dart:3314)
ComponentElement.performRebuild (framework.dart:4652)
StatefulElement.performRebuild (framework.dart:4800)
Element.rebuild (framework.dart:4343)
BuildOwner.buildScope (framework.dart:2730)
-------------------------------------------------------------------------------------------------------
WidgetsBinding.drawFrame (binding.dart:913)
RendererBinding._handlePersistentFrameCallback (binding.dart:302)
SchedulerBinding._invokeFrameCallback (binding.dart:1117)
SchedulerBinding.handleDrawFrame (binding.dart:1055)
SchedulerBinding._handleDrawFrame (binding.dart:971)
-------------------------------------------------------------------------------------------------------
_rootRun (zone.dart:1190)
_CustomZone.run (zone.dart:1093)
_CustomZone.runGuarded (zone.dart:997)
_invoke (hooks.dart:251)
_drawFrame (hooks.dart:209)
SchedulerBinding._handleDrawFrame
还是分界线,这里标识dart层渲染的起点,_handleDrawFrame
是C++层onDrawFrame
在dart的回调方法。而WidgetsBinding.drawFrame
是RendererBinding
初始化的时候注册进去的,初始化方法如下:
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
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();
}
}
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
其中WidgetsBinding.drawFrame
调用了buildOwner.buildScope(renderViewElement);
,这里的renderViewElement
只有一个地方进行赋值,就是attachRootWidget
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
根据原理中介绍,attachRootWidget
是启动的时候调用的,这里的_renderViewElement
就是runApp
时候传入的widget
构造的对应的element
。
buildScope
调用了callback()
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
assert(context != null);
assert(_debugStateLockLevel >= 0);
assert(!_debugBuilding);
assert(() {
if (debugPrintBuildScope)
debugPrint('buildScope called with context $context; dirty list is: $_dirtyElements');
_debugStateLockLevel += 1;
_debugBuilding = true;
return true;
}());
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
assert(_debugStateLocked);
Element debugPreviousBuildTarget;
assert(() {
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
debugPreviousBuildTarget = _debugCurrentBuildTarget;
_debugCurrentBuildTarget = context;
return true;
}());
_dirtyElementsNeedsResorting = false;
try {
callback();
} finally {
assert(() {
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
assert(_debugCurrentBuildTarget == context);
_debugCurrentBuildTarget = debugPreviousBuildTarget;
_debugElementWasRebuilt(context);
return true;
}());
}
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
assert(_dirtyElements[index] != null);
assert(_dirtyElements[index]._inDirtyList);
assert(() {
if (_dirtyElements[index]._active && !_dirtyElements[index]._debugIsInScope(context)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Tried to build dirty widget in the wrong build scope.'),
ErrorDescription(
'A widget which was marked as dirty and is still active was scheduled to be built, '
'but the current build scope unexpectedly does not contain that widget.',
),
ErrorHint(
'Sometimes this is detected when an element is removed from the widget tree, but the '
'element somehow did not get marked as inactive. In that case, it might be caused by '
'an ancestor element failing to implement visitChildren correctly, thus preventing '
'some or all of its descendants from being correctly deactivated.',
),
DiagnosticsProperty<Element>(
'The root of the build scope was',
context,
style: DiagnosticsTreeStyle.errorProperty,
),
DiagnosticsProperty<Element>(
'The offending element (which does not appear to be a descendant of the root of the build scope) was',
_dirtyElements[index],
style: DiagnosticsTreeStyle.errorProperty,
),
]);
}
return true;
}());
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {
_debugReportException(
ErrorDescription('while rebuilding dirty elements'),
e,
stack,
informationCollector: () sync* {
if (index < _dirtyElements.length) {
yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index]));
yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
} else {
yield ErrorHint('The element being rebuilt at the time was index $index of $dirtyCount, but _dirtyElements only had ${_dirtyElements.length} entries. This suggests some confusion in the framework internals.');
}
},
);
}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
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;
}
}
}
assert(() {
if (_dirtyElements.any((Element element) => element._active && element.dirty)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('buildScope missed some dirty elements.'),
ErrorHint('This probably indicates that the dirty list should have been resorted but was not.'),
Element.describeElements('The list of dirty elements at the end of the buildScope call was', _dirtyElements),
]);
}
return true;
}());
} finally {
for (final Element element in _dirtyElements) {
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
assert(_debugBuilding);
assert(() {
_debugBuilding = false;
_debugStateLockLevel -= 1;
if (debugPrintBuildScope)
debugPrint('buildScope finished');
return true;
}());
}
assert(_debugStateLockLevel >= 0);
}
这里的callback
,attachRootWidget
初始化的时候attachToRenderTree
中传了回调element.mount(null, null);
所以,当Widget Tree被渲染为Element Tree后会调用mount
,后续当视图树发生变化后,_dirtyElements
将存放这些子视图,并遍历进行rebuild
操作,如果有子类就继续进行子类的rebuild
,StatelessElement
调用rebuild
后执行了widget.build(this)
,也就触发了Consumer.build
,也就和新版本(1.22.6)的堆栈对应上了,这里其实也是一个视图树的构建过程,通过Consumer
的builder
参数来完成这个页面对应Widget的构建。那讲到这里我们明白了,初始化的时候系统触发了UI渲染机制,一步步的完成了UI的初始化工作(Widget Tree 转化成 Element Tree),接下来就是当Model的数据发生变化时,是如果改变UI的。
我们尝试修改修改Model中的数据,得到下面的堆栈
ProviderDebugPage1.build.<anonymous closure> (test_provider_page1.dart:37)
Consumer.build (consumer.dart:180)
StatelessElement.build (framework.dart:4701)
ComponentElement.performRebuild (framework.dart:4627)
Element.rebuild (framework.dart:4343)
BuildOwner.buildScope (framework.dart:2730)
WidgetsBinding.drawFrame (binding.dart:913)
RendererBinding._handlePersistentFrameCallback (binding.dart:302)
SchedulerBinding._invokeFrameCallback (binding.dart:1117)
SchedulerBinding.handleDrawFrame (binding.dart:1055)
SchedulerBinding._handleDrawFrame (binding.dart:971)
_rootRun (zone.dart:1190)
_CustomZone.run (zone.dart:1093)
_CustomZone.runGuarded (zone.dart:997)
_invoke (hooks.dart:251)
_drawFrame (hooks.dart:209)
对应的我们发现,_dirtyElements
是这样的
这里,我们发现,当前页面,用到Model1
的是_dirtyElements[1]和_dirtyElements[2]
,所以这也符合我们的预期,我们只剩下最后一个环节,Model1中的数据改变是怎样影响_dirtyElements
的,又是如何触发重新渲染的。
我们看到,触发数据改变的时候,产生如下的堆栈
BuildOwner.scheduleBuildFor (framework.dart:2546)
Element.markNeedsBuild (framework.dart:4311)
State.setState (framework.dart:1264)
_ListenableDelegateMixin.startListening.<anonymous closure> (listenable_provider.dart:190)
ChangeNotifier.notifyListeners (change_notifier.dart:226)
MyModel1.incrementCounter (test_provider_model1.dart:13)
MyModel1.incrementCounter (test_provider_model1.dart:1)
_InkResponseState._handleTap (ink_well.dart:993)
_InkResponseState.build.<anonymous closure> (ink_well.dart:1111)
GestureRecognizer.invokeCallback (recognizer.dart:183)
TapGestureRecognizer.handleTapUp (tap.dart:598)
BaseTapGestureRecognizer._checkUp (tap.dart:287)
BaseTapGestureRecognizer.handlePrimaryPointer (tap.dart:222)
PrimaryPointerGestureRecognizer.handleEvent (recognizer.dart:476)
PrimaryPointerGestureRecognizer.handleEvent (recognizer.dart:1)
PointerRouter._dispatch (pointer_router.dart:77)
PointerRouter._dispatchEventToRoutes.<anonymous closure> (pointer_router.dart:122)
_LinkedHashMapMixin.forEach (compact_hash.dart:377)
PointerRouter._dispatchEventToRoutes (pointer_router.dart:120)
PointerRouter.route (pointer_router.dart:106)
GestureBinding.handleEvent (binding.dart:358)
GestureBinding.dispatchEvent (binding.dart:338)
RendererBinding.dispatchEvent (binding.dart:267)
GestureBinding._handlePointerEvent (binding.dart:295)
GestureBinding._flushPointerEventQueue (binding.dart:240)
GestureBinding._handlePointerDataPacket (binding.dart:213)
GestureBinding._handlePointerDataPacket (binding.dart:1)
_rootRunUnary (zone.dart:1206)
_rootRunUnary (zone.dart:1)
_CustomZone.runUnary (zone.dart:1100)
_CustomZone.runUnaryGuarded (zone.dart:1005)
_invoke1 (hooks.dart:265)
_dispatchPointerDataPacket (hooks.dart:174)
当Model数据发生变化时,会调用到setState
,最终触发BuildOwner.scheduleBuildFor
执行,里面调用了_dirtyElements.add(element)
BuildOwner.scheduleBuildFor (framework.dart:2590)
Element.markNeedsBuild (framework.dart:4311)
Element.didChangeDependencies (framework.dart:4130)
InheritedElement.notifyDependent (framework.dart:5205)
InheritedElement.notifyClients (framework.dart:5244)
ProxyElement.updated (framework.dart:4997)
InheritedElement.updated (framework.dart:5217)
ProxyElement.update (framework.dart:4985)
Element.updateChild (framework.dart:3314)
ComponentElement.performRebuild (framework.dart:4652)
StatefulElement.performRebuild (framework.dart:4800)
Element.rebuild (framework.dart:4343)
BuildOwner.buildScope (framework.dart:2730)
WidgetsBinding.drawFrame (binding.dart:913)
RendererBinding._handlePersistentFrameCallback (binding.dart:302)
SchedulerBinding._invokeFrameCallback (binding.dart:1117)
SchedulerBinding.handleDrawFrame (binding.dart:1055)
SchedulerBinding._handleDrawFrame (binding.dart:971)
_rootRun (zone.dart:1190)
_CustomZone.run (zone.dart:1093)
_CustomZone.runGuarded (zone.dart:997)
_invoke (hooks.dart:251)
_drawFrame (hooks.dart:209)
同样didChangeDependencies
也会触发UI刷新,上面notifyListeners
触发markNeedsBuild
是因为,在使用前调用了addListener
,demo中,Model1
的addListener
是在attachRootWidget
中调用的,也可以任何时机,只要在触发Model1
数据变化前进行就可以,所以当后面Model1
中的数据发生变化的时候才能触发setState
。
最后,谁来触发_dirtyElements
,简单说,还是系统,引用中的一段描述,当调用到引擎Engine的ScheduleFrame()方法过程则会注册VSYNC信号回调,一旦Vsync信号达到,则会调用到doFrame()方法。所以VSYNC会周期性的回调,来调用drawFrame
,当发现有_dirtyElements
,就rebuild
。
总结:
- 数据的改变一定要调用
markNeedsBuild
才能让UI刷新到最新的数据,而Provider
内部实现了这样的机制,当我们的Model
with 或者 extendsChangeNotifier
的时候就具备了这样的潜力,只要在Model
实例数据发生变化的时候调用notifyListeners
即可。 - 使用
Provider.of
监听数据变化的原理类似,都是在想办法让数据发生变化时,Element
出现在_dirtyElements
中,作为Widget
在构建Element Tree的时候,就已经将widget和element建立了关系,当数据改变触发rebuild的时候,element可以找到对应的widget进行rebuild,所以我们可以看到最新的数据呈现在UI上。 -
Consumer原理也是一样的,因为Consumer底层也是调用
Provider.of
,可以说Consumer是Provider.of
的包装,对用户更加友好。