回顧
Flutter State Management狀態管理全面分析
上期我們對Flutter的狀態管理有了全局的認知,也知道了如何分辨是非好壞,不知道也沒關系哦,我們接下來還會更加詳細的分析,通過閱讀Provider源碼,來看看框架到底如何組織的,是如何給我們提供便利的。
本期內容
通過官方我們已經知道其實Provider就是對InheritedWidget的包裝,只是讓InheritedWidget用起來更加簡單且高可復用。我們也知道它有一些缺點,如
- 容易造成不必要的刷新
- 不支持跨頁面(route)的狀態,意思是跨樹,如果不在一個樹中,我們無法獲取
- 數據是不可變的,必須結合StatefulWidget、ChangeNotifier或者Steam使用
我特別想弄明白,這些缺點在Provider的設計中是如何規避的,還有一個是Stream不會主動的close掉流的通道,不得不結合StatefulWidget使用,而Provider提供了dispose回調,你可以在該函數中主動關閉,好厲害,如何做到的呢?帶著這些疑問,我們去尋找答案
如何使用
我們先來使用它,然后在根據用例分析源碼,找到我們想要的答案,先看一個簡單的例子
step 1
第一步定義一個ChangeNotifier,來負責數據的變化通知
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
step 2
第二步,用ChangeNotifierProvider來訂閱Counter,不難猜出,ChangeNotifierProvider肯定是InheritedWidget的包裝類,負責將Counter的狀態共享給子Widget,我這里將ChangeNotifierProvider放到了Main函數中,并在整個Widget樹的頂端,當然這里是個簡單的例子,我這么寫問題不大,但你要考慮,如果是特別局部的狀態,請將ChangeNotifierProvider放到局部的地方而不是全局,希望你能明白我的用意
void main() {
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: MyApp(),
),
);
}
step 3
第三步,接收數據通過Consumer<Counter>,Consumer是個消費者,它負責消費ChangeNotifierProvider生產的數據
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('MyHomePage build');
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
/// Extracted as a separate widget for performance optimization.
/// As a separate widget, it will rebuild independently from [MyHomePage].
///
/// This is totally optional (and rarely needed).
/// Similarly, we could also use [Consumer] or [Selector].
Consumer<Counter>(
builder: (BuildContext context, Counter value, Widget child) {
return Text('${value.count}');
},
),
OtherWidget(),
const OtherWidget2()
],
),
),
floatingActionButton: FloatingActionButton(
/// Calls `context.read` instead of `context.watch` so that it does not rebuild
/// when [Counter] changes.
onPressed: () => context.read<Counter>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
通過這個例子,可以判斷出Provider封裝的足夠易用,而且Counter作為Model層使用的with ChangeNotifier 而不是extends ,所以說侵入性也比較低,感覺還不錯,那么InheritedWidget的缺點它規避了嗎?
- 容易造成不必要的刷新(解決了嗎?)
我們多加兩個子WIdget進去,排在Consumer的后面,OtherWidget什么都不干,不去訂閱Counter,OtherWidget2通過context.watch<Counter>().count函數監聽而不是Consumer,來看下效果一樣不,然后在build函數中都加入了print
class OtherWidget extends StatelessWidget {
const OtherWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('OtherWidget build');
// Provider.of<Counter>(context);
return Text(
/// Calls `context.watch` to make [MyHomePage] rebuild when [Counter] changes.
'OtherWidget',
style: Theme.of(context).textTheme.headline4);
}
}
class OtherWidget2 extends StatelessWidget {
const OtherWidget2({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('OtherWidget2 build');
return Text(
/// Calls `context.watch` to make [MyHomePage] rebuild when [Counter] changes.
'${context.watch<Counter>().count}',
style: Theme.of(context).textTheme.headline4);
}
}
項目運行看下效果,跑起來是這樣的print日志
點擊刷新后
分析結論如下:
- Consumer、context.watch都可以監聽Counter變化
- Consumer只會刷新自己
- context.watch所在子widget不管是否是const都被重建后刷新數據
- OtherWidget并沒有被重建,因為它沒有訂閱Counter
局部刷新確實實現了但要通過Consumer,第二個問題不支持跨頁面(route)的狀態,這個可以確定的說不支持,第三個問題數據是不可變的(只讀),經過這個例子可以分辨出數據確實是可變的對吧,那么數據是如何變化的呢?留個懸念,下面分析源碼中來看本質。
當然要想更完整的理解ChangeNotifier、ChangeNotifierProvider、Consumer的關系
設計模式真是無處不在哈,ChangeNotifier與ChangeNotifierProvider實現了觀察者模式,ChangeNotifierProvider與Consumer又實現了生產者消費者模式,這里不具體聊這倆個模式,如果還不了解,請你自行搜索學習哦。下面直接源碼分析
源碼分析
ChangeNotifier
在包package:meta/meta.dart下,是flutter sdk的代碼,并不屬于Provider框架的一部分哦,通過下方代碼可以看出,這是一個標準的觀察者模型,而真正的監聽者就是typedef VoidCallback = void Function(); 是dart.ui包下定義的一個函數,沒人任何返回參數的函數。ChangerNotifier實現自抽象類Listenable,通過源碼的注釋我們看到Listenable是一個專門負責維護監聽列表的一個抽象類。
ChangeNotifierProvider
class ChangeNotifierProvider<T extends ChangeNotifier>
extends ListenableProvider<T> {
static void _dispose(BuildContext context, ChangeNotifier notifier) {
notifier?.dispose();
}
/// 使用`create`創建一個[ChangeNotifier]
/// 當ChangeNotifierProvider從樹中被移除時,自動取消訂閱通過
/// notifier?.dispose();
ChangeNotifierProvider({
Key key,
@required Create<T> create,
bool lazy,
TransitionBuilder builder,
Widget child,
}) : super(
key: key,
create: create,
dispose: _dispose,
lazy: lazy,
builder: builder,
child: child,
);
/// 生成一個已存在ChangeNotifier的Provider
ChangeNotifierProvider.value({
Key key,
@required T value,
TransitionBuilder builder,
Widget child,
}) : super.value(
key: key,
builder: builder,
value: value,
child: child,
);
}
分析下構造
- Create<T> create
是個通用函數typedef Create<T> = T Function(BuildContext context)用于創建T類,這里負責創建ChangeNotifier - bool lazy
是否懶加載 -
TransitionBuilder builder
當builder存在時將不會用child做為子Widget,追蹤到源碼實現可以看到如下圖
- Widget child
builder不存在時就用child
繼承自ListenableProvider<T>,來繼續分析它的源碼
class ListenableProvider<T extends Listenable> extends InheritedProvider<T> {
/// 使用 [create] 創建一個 [Listenable] 訂閱它
/// [dispose] 可以選擇性的釋放資源當 [ListenableProvider] 被移除樹的時候
/// [create] 不能為空
ListenableProvider({
Key key,
@required Create<T> create,
Dispose<T> dispose,
bool lazy,
TransitionBuilder builder,
Widget child,
}) : assert(create != null),
super(
key: key,
startListening: _startListening,
create: create,
dispose: dispose,
debugCheckInvalidValueType: kReleaseMode
? null
: (value) {
if (value is ChangeNotifier) {
// ignore: invalid_use_of_protected_member
...
}
},
lazy: lazy,
builder: builder,
child: child,
);
/// 生成已存在 [Listenable] 的Provider
ListenableProvider.value({
Key key,
@required T value,
UpdateShouldNotify<T> updateShouldNotify,
TransitionBuilder builder,
Widget child,
}) : super.value(
key: key,
builder: builder,
value: value,
updateShouldNotify: updateShouldNotify,
startListening: _startListening,
child: child,
);
static VoidCallback _startListening(
InheritedContext<Listenable> e,
Listenable value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
}
- Listenable 上面已經分析,它是負責管理觀察者列表的抽象
- 它比子類ChangeNotifierProvider多了一個構造參數dispose,這個函數是typedef Dispose<T> = void Function(BuildContext context, T value); 是個回調,應該是當頁面被銷毀時觸發(等再深入了源碼才能認證,目前只是猜測,我們繼續看)
又繼承自InheritedProvider<T> ,別放棄,來跟我一起往下看
class InheritedProvider<T> extends SingleChildStatelessWidget {
/// 創建數據value并共享給子Widget
/// 當 [InheritedProvider] 從樹中被釋放時,將自動釋放數據value
InheritedProvider({
Key key,
Create<T> create,
T update(BuildContext context, T value),
UpdateShouldNotify<T> updateShouldNotify,
void Function(T value) debugCheckInvalidValueType,
StartListening<T> startListening,
Dispose<T> dispose,
TransitionBuilder builder,
bool lazy,
Widget child,
}) : _lazy = lazy,
_builder = builder,
_delegate = _CreateInheritedProvider(
create: create,
update: update,
updateShouldNotify: updateShouldNotify,
debugCheckInvalidValueType: debugCheckInvalidValueType,
startListening: startListening,
dispose: dispose,
),
super(key: key, child: child);
/// 暴漏給子孫一個已存在的數據value
InheritedProvider.value({
Key key,
@required T value,
UpdateShouldNotify<T> updateShouldNotify,
StartListening<T> startListening,
bool lazy,
TransitionBuilder builder,
Widget child,
}) : _lazy = lazy,
_builder = builder,
_delegate = _ValueInheritedProvider(
value: value,
updateShouldNotify: updateShouldNotify,
startListening: startListening,
),
super(key: key, child: child);
InheritedProvider._constructor({
Key key,
_Delegate<T> delegate,
bool lazy,
TransitionBuilder builder,
Widget child,
}) : _lazy = lazy,
_builder = builder,
_delegate = delegate,
super(key: key, child: child);
final _Delegate<T> _delegate;
final bool _lazy;
final TransitionBuilder _builder;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
_delegate.debugFillProperties(properties);
}
@override
_InheritedProviderElement<T> createElement() {
return _InheritedProviderElement<T>(this);
}
@override
Widget buildWithChild(BuildContext context, Widget child) {
assert(
_builder != null || child != null,
'$runtimeType used outside of MultiProvider must specify a child',
);
return _InheritedProviderScope<T>(
owner: this,
child: _builder != null
? Builder(
builder: (context) => _builder(context, child),
)
: child,
);
}
}
構造中多出來的參數
- T update(BuildContext context, T value) 該函數返回數據變更值value,具體實現在_CreateInheritedProvider類中,說白了InheritedProvider<T>是個無狀態組件對嗎?那么它要變更狀態肯定要依賴于別人,而它創建出一個_CreateInheritedProvider類,_CreateInheritedProvider是_Delegate的實現類,_Delegate就是一個狀態的代理類,來看下_Delegate具體實現
@immutable
abstract class _Delegate<T> {
_DelegateState<T, _Delegate<T>> createState();
void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}
abstract class _DelegateState<T, D extends _Delegate<T>> {
_InheritedProviderScopeElement<T> element;
T get value;
D get delegate => element.widget.owner._delegate as D;
bool get hasValue;
bool debugSetInheritedLock(bool value) {
return element._debugSetInheritedLock(value);
}
bool willUpdateDelegate(D newDelegate) => false;
void dispose() {}
void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
void build(bool isBuildFromExternalSources) {}
}
這是用到了委托模式,這里就有點類似StatefulWidget和State的關系,同樣的_DelegateState提供了類似生命周期的函數,如willUpdateDelegate更新新的委托,dispose注銷等
- UpdateShouldNotify<T> updateShouldNotify,
void Function(T value) debugCheckInvalidValueType,
StartListening<T> startListening,
Dispose<T> dispose, 這些函數全部交給了委托類 - 最關鍵的實現來了,到目前位置還沒看到InheritedWidget的邏輯對吧,它來了Widget buildWithChild(BuildContext context, Widget child),我們傳入的Widget就被叫_InheritedProviderScope的類給包裹了,看下源碼
class _InheritedProviderScope<T> extends InheritedWidget {
_InheritedProviderScope({
this.owner,
@required Widget child,
}) : super(child: child);
final InheritedProvider<T> owner;
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
@override
_InheritedProviderScopeElement<T> createElement() {
return _InheritedProviderScopeElement<T>(this);
}
}
至此你有沒有發現一個特點,所有的函數都被_Delegate帶走了,剩下的只有Widget交給了_InheritedProviderScope,這里設計的也很好,畢竟InheritedWidget其實也就只能做到數據共享,跟函數并沒有什么關系對吧。唯一有關系的地方,我猜測就是在InheritedWidget提供的Widget中調用
一個細節 owner: this 在 buildWithChild函數中,將InheritedProvider本身傳遞給InheritedWidget,應該是為了方便調用它的_Delegate委托類,肯定是用來回調各種函數。
... 快一點了,睡了,明天再更
繼續分享,_InheritedProviderScope唯一特殊的地方,我們發現它自己創建了一個Element實現通過覆蓋createElement函數,返回_InheritedProviderScopeElement實例,flutter三板斧 Widget、Element、RenderObject,該框架自己實現一層Element,我們都知道Widget是配置文件只有build和rebuild以及remove from the tree,而Element作為一層虛擬Dom,主要負責優化,優化頁面刷新的邏輯,那我們來詳細的分析一下_InheritedProviderScopeElement,看它都做了什么?
/// 繼承自InheritedElement,因為InheritedWidget對應的Element就是它
/// 實現 InheritedContext,InheritedContext繼承自BuildContext,多了個T范型
class _InheritedProviderScopeElement<T> extends InheritedElement
implements InheritedContext<T> {
/// 構造函數,將Element對應的widget傳進來
_InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
: super(widget);
/// 是否需要通知依賴的Element變更
bool _shouldNotifyDependents = false;
/// 是否允許通知變更
bool _isNotifyDependentsEnabled = true;
/// 第一次構建
bool _firstBuild = true;
/// 是否更新newWidget的Delegate委托
bool _updatedShouldNotify = false;
/// 這個變量就是控制的數據變更,在Widget變更和Element依賴變更的時候都會被設置為true
bool _isBuildFromExternalSources = false;
/// 委托類的狀態(我們猜測對了, owner: this 就是為了拿到上層的委托類)
_DelegateState<T, _Delegate<T>> _delegateState;
@override
_InheritedProviderScope<T> get widget =>
super.widget as _InheritedProviderScope<T>;
@override
void updateDependencies(Element dependent, Object aspect) {
final dependencies = getDependencies(dependent);
// once subscribed to everything once, it always stays subscribed to everything
if (dependencies != null && dependencies is! _Dependency<T>) {
return;
}
if (aspect is _SelectorAspect<T>) {
final selectorDependency =
(dependencies ?? _Dependency<T>()) as _Dependency<T>;
if (selectorDependency.shouldClearSelectors) {
selectorDependency.shouldClearSelectors = false;
selectorDependency.selectors.clear();
}
if (selectorDependency.shouldClearMutationScheduled == false) {
selectorDependency.shouldClearMutationScheduled = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
selectorDependency
..shouldClearMutationScheduled = false
..shouldClearSelectors = true;
});
}
selectorDependency.selectors.add(aspect);
setDependencies(dependent, selectorDependency);
} else {
// subscribes to everything
setDependencies(dependent, const Object());
}
}
@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
final dependencies = getDependencies(dependent);
var shouldNotify = false;
if (dependencies != null) {
if (dependencies is _Dependency<T>) {
for (final updateShouldNotify in dependencies.selectors) {
try {
assert(() {
_debugIsSelecting = true;
return true;
}());
shouldNotify = updateShouldNotify(value);
} finally {
assert(() {
_debugIsSelecting = false;
return true;
}());
}
if (shouldNotify) {
break;
}
}
} else {
shouldNotify = true;
}
}
if (shouldNotify) {
dependent.didChangeDependencies();
}
}
@override
void performRebuild() {
if (_firstBuild) {
_firstBuild = false;
_delegateState = widget.owner._delegate.createState()..element = this;
}
super.performRebuild();
}
@override
void update(_InheritedProviderScope<T> newWidget) {
_isBuildFromExternalSources = true;
_updatedShouldNotify =
_delegateState.willUpdateDelegate(newWidget.owner._delegate);
super.update(newWidget);
_updatedShouldNotify = false;
}
@override
void updated(InheritedWidget oldWidget) {
super.updated(oldWidget);
if (_updatedShouldNotify) {
notifyClients(oldWidget);
}
}
@override
void didChangeDependencies() {
_isBuildFromExternalSources = true;
super.didChangeDependencies();
}
@override
Widget build() {
if (widget.owner._lazy == false) {
value; // this will force the value to be computed.
}
_delegateState.build(_isBuildFromExternalSources);
_isBuildFromExternalSources = false;
if (_shouldNotifyDependents) {
_shouldNotifyDependents = false;
notifyClients(widget);
}
return super.build();
}
@override
void unmount() {
_delegateState.dispose();
super.unmount();
}
@override
bool get hasValue => _delegateState.hasValue;
@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) return;
markNeedsBuild();
_shouldNotifyDependents = true;
}
@override
T get value => _delegateState.value;
@override
InheritedWidget dependOnInheritedElement(
InheritedElement ancestor, {
Object aspect,
}) {
return super.dependOnInheritedElement(ancestor, aspect: aspect);
}
}
- void update(_InheritedProviderScope<T> newWidget) 讓頁面重新build的是在這里,因為InheritedElement 繼承自ProxyElement,而ProxyElement的update函數調用了兩個函數updated(已更新完成),rebuild函數觸發重新build邏輯,下面為跟蹤到的代碼
abstract class ProxyElement extends ComponentElement {
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
assert(widget != null);
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
}
- performRebuild() 是在update觸發真正調用rebuild之后被調用
- updateDependencies、notifyDependent處理Element依賴邏輯
- update、updated處理的widget更新邏輯
- didChangeDependencies當此State對象的依賴項更改時調用,子類很少重寫此方法,因為框架總是在依賴項更改后調用build。一些子類確實重寫了此方法,因為當它們的依存關系發生變化時,它們需要做一些昂貴的工作(例如,網絡獲取),并且對于每個構建而言,這些工作將太昂貴。
- build() 構建需要的widget,Element在調用build的時候也會觸發Widget的build
- void unmount() 這里看到了_delegateState.dispose();的調用,現在找到了吧,當Element從樹中移除的時候,回掉了dispose函數。
來看一個生命周期的圖,輔助你理解源碼的調用關系
此圖引自大佬Reactive,他記錄了很詳細的生命周期圖,感謝作者的貢獻
notifyClients 這個函數干嘛的?它是InheritedElement中實現的函數,通過官方文檔了解到,它是通過調用Element.didChangeDependencies通知所有從屬Element此繼承的widget已更改,此方法只能在構建階段調用,通常,在重建inherited widget時會自動調用此方法,還有就是InheritedNotifier,它是InheritedWidget的子類,在其Listenable發送通知時也調用此方法。
markNeedsNotifyDependents 如果你調用它,會強制build后 通知所以依賴Element刷新widget,看下面代碼,發現該函數在InheritedContext中定義,所以我們可以通過InheritedContext上下文來強制頁面的構建
abstract class InheritedContext<T> extends BuildContext {
/// [InheritedProvider] 當前共享的數據
/// 此屬性是延遲加載的,第一次讀取它可能會觸發一些副作用,
T get value;
/// 將[InheritedProvider]標記為需要更新依賴項
/// 繞過[InheritedWidget.updateShouldNotify]并將強制rebuild
void markNeedsNotifyDependents();
/// setState是否至少被調用過一次
/// [DeferredStartListening]可以使用它來區分
/// 第一次監聽,在“ controller”更改后進行重建。
bool get hasValue;
}
小結一下
我們先回顧一下我們是如何使用InheritedWidget的,為了能讓InheritedWidget的子Widget能夠刷新,我們不得不依賴于Statefulwidget,并通過State控制刷新Element,調用setState刷新頁面,其實底層是調用的_element.markNeedsBuild() 函數,這樣我們明白了,其實最終控制頁面的還是Element,那么Provider 它也巧妙的封裝了自己的_delegateState,是私有的,并沒有給我們公開使用,也沒有提供類似setState,但可以通過markNeedsNotifyDependents函數達到了和setState一樣的調用效果,一樣的都是讓所有子Widget進行重建,可我們要的局部刷新呢?是在Consumer里?,來吧,不要走開,沒有廣告,精彩繼續,接下來研究Consumer源碼
Consumer
class Consumer<T> extends SingleChildStatelessWidget {
/// 構造函數,必傳builder
Consumer({
Key key,
@required this.builder,
Widget child,
}) : assert(builder != null),
super(key: key, child: child);
/// 根據 [Provider<T>] 提供的value,構建的widget
final Widget Function(BuildContext context, T value, Widget child) builder;
@override
Widget buildWithChild(BuildContext context, Widget child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
}
- 這里源碼稍微有一點繞,Widget child傳給了父類SingleChildStatelessWidget,最終通過buildWithChild函數的參數child傳遞回來,而builder函數有收到了此child,然后再組合child和需要刷新的widget組合一個新的widget給Consumer。一句話就是說Consumer的構造函數可以傳兩個widget一個是builder,一個是child,最終是通過builder構建最終的widget,如果child不為空,那么你需要自己組織child和builder中返回widget的關系。
- Provider.of<T>(context) 獲取了共享數據value
Provider.of<T>(context) 是如何獲取數據的呢?繼續看源碼
/// 調用_inheritedElementOf函數
static T of<T>(BuildContext context, {bool listen = true}) {
assert(context != null);
final inheritedElement = _inheritedElementOf<T>(context);
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}
static _InheritedProviderScopeElement<T> _inheritedElementOf<T>(
BuildContext context) {
_InheritedProviderScopeElement<T> inheritedElement;
if (context.widget is _InheritedProviderScope<T>) {
// An InheritedProvider<T>'s update tries to obtain a parent provider of
// the same type.
context.visitAncestorElements((parent) {
inheritedElement = parent.getElementForInheritedWidgetOfExactType<
_InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>;
return false;
});
} else {
inheritedElement = context.getElementForInheritedWidgetOfExactType<
_InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>;
}
if (inheritedElement == null) {
throw ProviderNotFoundException(T, context.widget.runtimeType);
}
return inheritedElement;
}
- 通過 visitAncestorElements 往父級查找_InheritedProviderScope的實現類也就是InheritedWidget,當找到是就返回_InheritedProviderScopeElement,而_InheritedProviderScopeElement正好可以拿到value,這個value也就是 _delegateState的value
@override
T get value => _delegateState.value;
走到這其實只是實現了讀取數據,那么數據到底是如何刷新的呢?我們回過頭來看下面幾段代碼
- Model數據調用ChangeNotifier提供的函數notifyListeners
void notifyListeners() {
assert(_debugAssertNotDisposed());
if (_listeners != null) {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (final VoidCallback listener in localListeners) {
try {
if (_listeners.contains(listener))
listener();
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'foundation library',
context: ErrorDescription('while dispatching notifications for $runtimeType'),
informationCollector: () sync* {
yield DiagnosticsProperty<ChangeNotifier>(
'The $runtimeType sending notification was',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
}
}
}
這個時候遍歷所有的監聽,然后執行函數listener(),這里其實等于執行VoidCallback的實例,那這個listener到底是哪個函數?
- 在ChangeNotifierProvider父類ListenableProvider的靜態函數中,自動訂閱了為觀察者
前面說了觀察者就是個普通函數,而e.markNeedsNotifyDependents就是InheritedContext的一個函數,當你notifyListeners的時候執行的就是它markNeedsNotifyDependents,上面我們知道markNeedsNotifyDependents類似setState效果,就這樣才實現了UI的刷新。
/// ListenableProvider 的靜態函數
static VoidCallback _startListening(
InheritedContext<Listenable> e,
Listenable value,
) {
value?.addListener(e.markNeedsNotifyDependents); /// 添加觀察者
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
/// InheritedContext 上下文
abstract class InheritedContext<T> extends BuildContext {
...
void markNeedsNotifyDependents();
...
}
到此位置局部刷新是不是還沒揭開面紗?到底是如何做的呢?跟我一起尋找,首先我們來看一個東西
@override
Widget buildWithChild(BuildContext context, Widget child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
Consumer通過Provider.of<T>(context)這句話我們才能監聽到數據的對吧,而且刷新的內容也只是這一部分,我們再看下它的實現發現了另一個細節
static T of<T>(BuildContext context, {bool listen = true}) {
assert(context != null);
final inheritedElement = _inheritedElementOf<T>(context);
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}
它調用了BuildContext的dependOnInheritedElement函數,這個函數做了啥?
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
...
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@override
void updateDependencies(Element dependent, Object aspect) {
print("updateDependencies===================dependent ${dependent.toString()}");
final dependencies = getDependencies(dependent);
...
setDependencies(dependent, const Object());
...
}
/// to manage dependency values.
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
final Map<Element, Object> _dependents = HashMap<Element, Object>();
觸發updateDependencies,通過setDependencies,將Element緩存到_dependents Map中
最后通過如下代碼更新
@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
print("notifyDependent===================oldWidget ${oldWidget.toString()}");
final dependencies = getDependencies(dependent);
var shouldNotify = false;
if (dependencies != null) {
if (dependencies is _Dependency<T>) {
for (final updateShouldNotify in dependencies.selectors) {
try {
assert(() {
_debugIsSelecting = true;
return true;
}());
shouldNotify = updateShouldNotify(value);
} finally {
assert(() {
_debugIsSelecting = false;
return true;
}());
}
if (shouldNotify) {
break;
}
}
} else {
shouldNotify = true;
}
}
if (shouldNotify) {
dependent.didChangeDependencies(); /// 更新方法
}
}
所以說整體流程是這樣當notifyListeners的時候其實是觸發了InheritedWidget的performRebuild,再到 build ,build后觸發 notifyClients,notifyClients觸發notifyDependent,notifyDependent這個時候通過getDependencies獲取緩存好的Element,最終確定是否需要刷新然后調用dependent.didChangeDependencies();更新,哈哈,終于明白了,只要widget中通過Provider.of函數訂閱后,就會被InheritedWidget緩存在一個Map中,然后刷新頁面的時候,如果子Widget不在緩存的Map中,根本不會走刷新,而且如果shouldNotify變量是false也不會刷新,這個控制肯定是雖然子Widget訂閱了,但它自己就是不刷新,可以更加細粒度的控制。
源碼分析總結
至此明白
- Provider 通過緩存 inheritedElement 實現局部刷新
- 通過控制自己實現的Element 層來 更新UI
- 通過Element提供的unmount函數回調dispose,實現選擇性釋放
厲害嗎?還不錯哦。
冰山一角
其實我們明白了它的核心原理之后,剩下的就是擴展該框架了,我目前只分析了ChangeNotifierProvider、Consumer,其實它還有很多很多,來一張圖嚇嚇你
圖片很大,請看原圖哦
看到這個圖,是不是覺得冰山一角呢?哈哈,不過還好,核心原理就是在InheritedProvider里面,我已經帶你趟了一遍,剩下的就靠你自己了,加油。
結語
大家還有沒有喜歡的Flutter狀態管理框架,如果你想看到更多的狀態管理框架源碼分析,請你關注我哦,如果你讀到最后,如果你覺得還不錯,也請你點個贊,感謝??