Flutter Provider原理深入淺出(上) 從Provider的用法入手,深入分析了Provider原理相關(guān)的源碼,Flutter Provider原理深入淺出(中)結(jié)合啟動原理分析了Widget tree和Element tree是如何建立關(guān)系的,并解釋了上遺留的一些疑問。本文將重點(diǎn)分析Provider是如何結(jié)合Flutter的渲染原理來實(shí)現(xiàn)狀態(tài)改變影響UI的效果。
原理上第四節(jié)Consumer源碼分析中,我們分析到buildWithChild是系統(tǒng)調(diào)用的,那深入源碼,我們看看具體是怎么調(diào)用的,直接上堆棧
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虛擬機(jī)調(diào)用的,里面涉及到UI的渲染原理,具體可以移步這里,1.22.6關(guān)于Consumer的源碼已經(jīng)做了改動,但原理基本相似,先看下改動后的源碼。
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版本,已經(jīng)不存在buildWithChild抽象方法了,取而代之的是cloneWithChild和build,這里build是沒有回傳child參數(shù)的,這里似乎回應(yīng)了之前我們有疑問的回傳了child但并沒使用的問題,但具體原因有待考察,這里build確實(shí)沒有拿到之前的child,但是回調(diào)業(yè)務(wù)層,是把外面?zhèn)鹘oConsumer的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還是分界線,這里標(biāo)識dart層渲染的起點(diǎn),_handleDrawFrame是C++層onDrawFrame在dart的回調(diào)方法。而WidgetsBinding.drawFrame是RendererBinding初始化的時候注冊進(jìn)去的,初始化方法如下:
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調(diào)用了buildOwner.buildScope(renderViewElement);,這里的renderViewElement只有一個地方進(jìn)行賦值,就是attachRootWidget
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
根據(jù)原理中介紹,attachRootWidget是啟動的時候調(diào)用的,這里的_renderViewElement就是runApp時候傳入的widget構(gòu)造的對應(yīng)的element。

buildScope調(diào)用了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中傳了回調(diào)element.mount(null, null); 所以,當(dāng)Widget Tree被渲染為Element Tree后會調(diào)用mount,后續(xù)當(dāng)視圖樹發(fā)生變化后,_dirtyElements將存放這些子視圖,并遍歷進(jìn)行rebuild操作,如果有子類就繼續(xù)進(jìn)行子類的rebuild,StatelessElement調(diào)用rebuild后執(zhí)行了widget.build(this),也就觸發(fā)了Consumer.build,也就和新版本(1.22.6)的堆棧對應(yīng)上了,這里其實(shí)也是一個視圖樹的構(gòu)建過程,通過Consumer的builder參數(shù)來完成這個頁面對應(yīng)Widget的構(gòu)建。那講到這里我們明白了,初始化的時候系統(tǒng)觸發(fā)了UI渲染機(jī)制,一步步的完成了UI的初始化工作(Widget Tree 轉(zhuǎn)化成 Element Tree),接下來就是當(dāng)Model的數(shù)據(jù)發(fā)生變化時,是如果改變UI的。
我們嘗試修改修改Model中的數(shù)據(jù),得到下面的堆棧
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)
對應(yīng)的我們發(fā)現(xiàn),_dirtyElements是這樣的

這里,我們發(fā)現(xiàn),當(dāng)前頁面,用到Model1的是_dirtyElements[1]和_dirtyElements[2],所以這也符合我們的預(yù)期,我們只剩下最后一個環(huán)節(jié),Model1中的數(shù)據(jù)改變是怎樣影響_dirtyElements的,又是如何觸發(fā)重新渲染的。
我們看到,觸發(fā)數(shù)據(jù)改變的時候,產(chǎn)生如下的堆棧
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)
當(dāng)Model數(shù)據(jù)發(fā)生變化時,會調(diào)用到setState,最終觸發(fā)BuildOwner.scheduleBuildFor執(zhí)行,里面調(diào)用了_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也會觸發(fā)UI刷新,上面notifyListeners觸發(fā)markNeedsBuild是因?yàn)?,在使用前調(diào)用了addListener,demo中,Model1的addListener是在attachRootWidget中調(diào)用的,也可以任何時機(jī),只要在觸發(fā)Model1數(shù)據(jù)變化前進(jìn)行就可以,所以當(dāng)后面Model1中的數(shù)據(jù)發(fā)生變化的時候才能觸發(fā)setState。
最后,誰來觸發(fā)_dirtyElements,簡單說,還是系統(tǒng),引用中的一段描述,當(dāng)調(diào)用到引擎Engine的ScheduleFrame()方法過程則會注冊VSYNC信號回調(diào),一旦Vsync信號達(dá)到,則會調(diào)用到doFrame()方法。所以VSYNC會周期性的回調(diào),來調(diào)用drawFrame,當(dāng)發(fā)現(xiàn)有_dirtyElements,就rebuild。
總結(jié):
- 數(shù)據(jù)的改變一定要調(diào)用
markNeedsBuild才能讓UI刷新到最新的數(shù)據(jù),而Provider內(nèi)部實(shí)現(xiàn)了這樣的機(jī)制,當(dāng)我們的Modelwith 或者 extendsChangeNotifier的時候就具備了這樣的潛力,只要在Model實(shí)例數(shù)據(jù)發(fā)生變化的時候調(diào)用notifyListeners即可。 - 使用
Provider.of監(jiān)聽數(shù)據(jù)變化的原理類似,都是在想辦法讓數(shù)據(jù)發(fā)生變化時,Element出現(xiàn)在_dirtyElements中,作為Widget在構(gòu)建Element Tree的時候,就已經(jīng)將widget和element建立了關(guān)系,當(dāng)數(shù)據(jù)改變觸發(fā)rebuild的時候,element可以找到對應(yīng)的widget進(jìn)行rebuild,所以我們可以看到最新的數(shù)據(jù)呈現(xiàn)在UI上。 -
Consumer原理也是一樣的,因?yàn)?strong>Consumer底層也是調(diào)用
Provider.of,可以說Consumer是Provider.of的包裝,對用戶更加友好。