1.前言
在各種前端開發(fā)中,由于狀態(tài)管理對于App的開發(fā)維護成本,性能等方面都起著至關(guān)重要的作用,所以選擇合適的狀態(tài)管理框架顯得尤為重要。Flutter作為跨平臺框架的后起之秀,背靠Google大樹,短時間內(nèi)開發(fā)者們在開源社區(qū)提供了多種狀態(tài)管理框架。而Provider是官方推薦的狀態(tài)管理方式之一,可用作跨組件的數(shù)據(jù)共享。本文將針對Provider框架的使用及實現(xiàn)原理作詳細的說明,并在最后對主流的狀態(tài)管理框架進行比較。
2.使用
Provider的使用非常簡單,通常使用ChangeNotifierProvider配合ChangeNotifier一起使用來實現(xiàn)狀態(tài)的管理與Widget的更新。其中ChangeNotifier是系統(tǒng)提供的,用來負責(zé)數(shù)據(jù)的變化通知。ChangeNotifierProvider本質(zhì)上其實就是Widget,它作為父節(jié)點Widget,可將數(shù)據(jù)共享給其所有子節(jié)點Widget使用或更新。具體的原理解析在后續(xù)章節(jié)會進行說明。所以通常我們只需要三步即可利用Provider來實現(xiàn)狀態(tài)管理。
1.創(chuàng)建混合或繼承ChangeNotifier的Model,用來實現(xiàn)數(shù)據(jù)更新的通知并監(jiān)聽數(shù)據(jù)的變化。
2.創(chuàng)建ChangeNotifierProvider,用來聲明Provider,實現(xiàn)跨組建的數(shù)據(jù)共享。
3.接收共享數(shù)據(jù)。
我們來舉個例子,看看它是怎么在父子之間進行數(shù)據(jù)共享的:
例1 Provider的使用:
- 創(chuàng)建
Model
class ProviderViewModel with ChangeNotifier {
int _number = 0;
get number => _number;
void addNumber() {
_number++;
notifyListeners();
}
}
上面的代碼很簡單,調(diào)用addNumber()方法讓_number加1,并調(diào)用notifyListeners()通知給監(jiān)聽方。
- 創(chuàng)建
ChangeNotifierProvider
class ProviderTestPage extends StatelessWidget {
final _providerViewModel = ProviderViewModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Provider Test"),
),
body: ChangeNotifierProvider.value(
value: _providerViewModel,
builder: (context, child) {
return Column(
children: [
const Text("我是父節(jié)點"),
Text(
"Parent number is: ${Provider.of<ProviderViewModel>(context).number}"),
ChildA(),
//ChildB(),
//ChildC()
],
);
},
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_providerViewModel.addNumber();
}, //使用context.read不會調(diào)用rebuild
),
);
}
}
我們用ChangeNotifierProvider將父布局包裹,在父或子節(jié)點ChildA通過Provider.of<T>(BuildContext context, {bool listen = true})進行數(shù)據(jù)操作,可同步更新父與子的數(shù)據(jù)與UI。其中listen默認(rèn)為true可監(jiān)聽數(shù)據(jù)的變化,為false的情況只可讀取數(shù)據(jù)的值。
- 接收共享數(shù)據(jù):
class ChildA extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("childA build");
return Container(
width: double.infinity,
color: Colors.amberAccent,
child: Column(
children: [
Text(
"Child A number: ${Provider.of<ProviderViewModel>(context).number}"),
MaterialButton(
child: const Text("Add Number"),
color: Colors.white,
onPressed: () {
Provider.of<ProviderViewModel>(context, listen: false)
.addNumber();
})
],
),
);
}
}
我們來看一下效果:
我們可以看到不管是在父節(jié)點還是在子節(jié)點,都可以對
ProviderViewModel的數(shù)據(jù)進行操作和監(jiān)聽。例1在操作與讀取時使用的是Provider.of<T>(BuildContext context, {bool listen = true})的方式,為了可以更明確對于Provider的操作,我們可將它替換為context.watch<>()和context.read<>()方式。我們可以通過源碼看到,
context.watch<>()和context.read<>()方法其實都是調(diào)用Provider.of<T>(BuildContext context, {bool listen = true})來實現(xiàn)的:
T watch<T>() {
return Provider.of<T>(this);
}
T read<T>() {
return Provider.of<T>(this, listen: false);
}
語義更加清晰明確。
如:
class ChildB extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("childB build");
return Container(
width: double.infinity,
color: Colors.red,
child: Column(
children: [
const Text("我是子節(jié)點"),
Text("Child B number: ${context.watch<ProviderViewModel>().number}"),
MaterialButton(
child: const Text("Add Number"),
color: Colors.white,
onPressed: () {
context.read<ProviderViewModel>().addNumber();
})
],
),
);
}
}
ChildB與ChildA實際上是一致的。我們把ProviderTestPage的ChildB()放開:
其中,每點擊一次父
Widget右下角的加號或子Widget的Add Number按鈕,我們看一下Log打印的結(jié)果:
我們會發(fā)現(xiàn)每一次的操作,都會導(dǎo)致
ChildA與ChildB整體重新build。但實際上從代碼中我們可知,在ChildA和ChildB中,只有以下的Text()會監(jiān)聽ProviderViewModel的數(shù)據(jù)更新:
//ChildA:
Text("Child A number: ${Provider.of<ProviderViewModel>(context).number}")
//ChildB:
Text("Child B number: ${context.watch<ProviderViewModel>().number}")
那么我們希望可以實現(xiàn)局部的更新該如何實現(xiàn)?Flutter提供了Consumer<>()來進行支持。下面我們來看一下Consumer<>()的用法:
class ChildC extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("childC build");
return Container(
width: double.infinity,
color: Colors.blue,
child: Column(
children: [
const Text("我是子節(jié)點"),
Consumer<ProviderViewModel>(builder: (context, value, child) {
print("ChildC Consumer builder");
return Text("Child C number: ${value.number}");
}),
MaterialButton(
child: const Text("Add Number"),
color: Colors.white,
onPressed: () {
context.read<ProviderViewModel>().addNumber();
})
],
),
);
}
}
由于我們只希望Text()來監(jiān)聽ProviderViewModel的數(shù)據(jù)更新,我們用Consumer<>()包裹住Text(),其中builder的傳參value即是ProviderViewModel對象,把ProviderTestPage的ChildC()放開,我們看一下結(jié)果:
再打印一下
Log:
從
Log中我們可以得知,ChildC并沒有被rebuild,而是由Consumer調(diào)用內(nèi)部的builder來實現(xiàn)局部更新的。到此為止,一個簡單的
Provider使用就介紹完成。另外Provider還提供了ProxyProvider,從名字上來看,我們可知這是個代理Provider,它是用來協(xié)調(diào)Model與Model之間的更新,比如一個ModelA依賴另一個ModelB,ModelB更新,他就要讓依賴它的ModelA也隨之更新。我們直接上代碼來看一下它的用法,還是分三步:
例2 ProxyProvider的使用:
- 創(chuàng)建
ProxyProviderViewModel:
class ProxyProviderViewModel with ChangeNotifier {
int number;
ProxyProviderViewModel(this.number);
String get title {
return "The number is: $number";
}
}
這個類只是簡單的在構(gòu)造方法例傳入int值,并創(chuàng)建get方法得到一段文本。
- 創(chuàng)建
Provider:
class ProxyProviderTestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Provider Test"),
),
body: MultiProvider(
providers: [
ChangeNotifierProvider<ProviderViewModel>(
create: (_) => ProviderViewModel()),
ChangeNotifierProxyProvider<ProviderViewModel,
ProxyProviderViewModel>(
create: (context) => ProxyProviderViewModel(
context.read<ProviderViewModel>().number),
update: (context, providerViewModel, proxyProviderViewModel) =>
ProxyProviderViewModel(providerViewModel.number))
],
builder: (context, child){
return Column(
children: [
ChildProxy(),
MaterialButton(
child: const Text("Add Number"),
color: Colors.amberAccent,
onPressed: () {
context.read<ProviderViewModel>().addNumber();
})
],
);
},
),
);
}
}
我們在body中用MultiProvider來包裹布局。MultiProvider的作用是同時可聲明多個Provider供使用,為參數(shù)providers添加Provider數(shù)組。我們首先聲明一個ChangeNotifierProvider,同例1中的ProviderViewModel。接著我們聲明一個ChangeNotifierProxyProvider用來做代理Provider。其中create參數(shù)是ProxyProvider的創(chuàng)建,update參數(shù)是ProxyProvider的更新。在我們的例子中,實際上是對ProviderViewModel進行數(shù)據(jù)操作,由ProxyProviderViewModel監(jiān)聽ProviderViewModel的數(shù)據(jù)變化。
- 接收共享數(shù)據(jù):
class ChildProxy extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: Column(
children: [
Text(context.watch<ProxyProviderViewModel>().title),
],
),
);
}
}
在ChildProxy中,我們監(jiān)聽ProxyProviderViewModel的數(shù)據(jù)變化,并將title顯示在Text()中,進行UI上的更新。
我們看一下效果:
我們調(diào)用
context.read<ProviderViewModel>().addNumber()對ProviderViewModel的數(shù)據(jù)進行更新,可同時更新ProxyProviderViewModel的number對象,而ChildProxy由于監(jiān)聽了ProxyProviderViewModel的數(shù)據(jù)變化,會因此更新UI中title的內(nèi)容。好了,到此為止,
Provider的使用介紹到這里,下面我們將針對Provider的原理進行說明。Provider實際上是對InheritedWidget進行了封裝,它才是真正實現(xiàn)父與子數(shù)據(jù)共享的重要元素,所以為了理清Provider的原理,我們必須先弄清楚InheritedWidget的實現(xiàn)過程。
3.InheritedWidget的原理及解析
InheritedWidget提供了沿樹向下,共享數(shù)據(jù)的功能,系統(tǒng)中的Provider,Theme等實現(xiàn)都是依賴于它。弄清楚它的原理,對于理解Flutter的數(shù)據(jù)共享方式會有很大的幫助。本章節(jié)將先通過實例說明InheritedWidget的用法,然后進行原理的解析。
3.1.使用
我們舉個簡單的例子:

這是一個簡單的樹結(jié)構(gòu),其中
ChildA和ChildC需要共享Data這個數(shù)據(jù),ChildB不需要。傳遞方式有很多種,比如說通過構(gòu)造方法傳遞,通過函數(shù)調(diào)用,函數(shù)回調(diào)傳遞等。但是如果樹層級非常多的話,剛才提到的傳遞方式將會對整個代碼結(jié)構(gòu)帶來災(zāi)難,包括代碼耦合度過高,回調(diào)地獄等。我們看看InheritedWidget是怎么處理的:1.作為整個樹的父節(jié)點,需要使
ChildA繼承InheritedWidget:
class ChildA extends InheritedWidget {
int number;
ChildA({required Widget child, required this.number}) : super(child: child);
static ChildA? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ChildA>();
}
@override
bool updateShouldNotify(covariant ChildA oldWidget) {
return oldWidget.number != number;
}
}
- 其中
updateShouldNotify()方法需要被重寫,用來判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致,是否需要傳遞給已注冊的子組件。 -
of()方法是一種約定俗成的通用寫法,只是起到方便調(diào)用的作用。其中context.dependOnInheritedWidgetOfExactType()是為它的子組件注冊了依賴關(guān)系。
通過這樣的方式,將ChildA聲明成了一個給子組件共享數(shù)據(jù)的Widget。
ChildB就是一個中間層級的普通Widget,用來連接樹結(jié)構(gòu):
class ChildB extends StatefulWidget {
final Widget child;
ChildB({Key? key, required this.child}) : super(key: key);
@override
_ChildBState createState() => _ChildBState();
}
class _ChildBState extends State<ChildB> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("ChildB didChangeDependencies");
}
@override
Widget build(BuildContext context) {
print("ChildB build");
return Container(
width: double.infinity,
color: Colors.amberAccent,
child: Column(
children: [const Text("我是子節(jié)點 ChildB"), widget.child],
),
);
}
}
ChildC依賴ChildA,并讀取ChildA的共享數(shù)據(jù),代碼如下:
class ChildC extends StatefulWidget {
ChildC({Key? key}) : super(key: key);
@override
_ChildCState createState() => _ChildCState();
}
class _ChildCState extends State<ChildC> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("ChildC didChangeDependencies");
}
@override
Widget build(BuildContext context) {
print("ChildC build");
return Container(
width: double.infinity,
color: Colors.red,
child: Column(
children: [
const Text("我是子節(jié)點 ChildC"),
Text("Child C number: ${ChildA.of(context)?.number}"),
],
),
);
}
}
ChildC通過ChildA.of(context)?.number的方式讀取ChildA的共享數(shù)據(jù)。
我們把這個樹串起來:
class InheritedWidgetTestPage extends StatefulWidget {
InheritedWidgetTestPage({Key? key}) : super(key: key);
@override
_InheritedWidgetTestPageState createState() =>
_InheritedWidgetTestPageState();
}
class _InheritedWidgetTestPageState extends State<InheritedWidgetTestPage> {
int _number = 10;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("InheritedWidget Test"),
),
body: ChildA(
number: _number,
child: ChildB(
child: ChildC(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_number++;
});
},
),
);
}
}
在點擊floatingActionButton的時候,修改_number的值,通過構(gòu)造方法傳遞給ChildA,整個樹重新build。ChildC讀取ChildA的數(shù)據(jù),并進行UI的更新,我們看一下結(jié)果:
ChildC接收到了數(shù)據(jù)變化并進行了更新,我們再來看一下Log:
在這個過程中,會發(fā)現(xiàn)
ChildB和ChildC都進行了rebuild,由于ChildC依賴ChildA的共享數(shù)據(jù),ChildC在rebuild之前執(zhí)行了didChangeDependencies()方法,說明ChildC的依賴關(guān)系發(fā)生了改變;而ChildB由于不依賴ChildA的共享數(shù)據(jù)所以并沒有執(zhí)行didChangeDependencies()。這個例子給出了
InheritedWidget的一個基本使用方式,但需要注意的是,在整個樹結(jié)構(gòu)中,其實ChildB是不依賴ChildA的共享數(shù)據(jù)的,按理來說,在數(shù)據(jù)發(fā)生變化,我們是不希望ChildB進行rebuild的。所以需要說明的是,InheritedWidget的正確用法并不是通過setState()來實現(xiàn)rebuild的,這里用setState()舉例僅僅是為了將整個流程串起來。這個例子的重點在于,依賴父組件的共享數(shù)據(jù)的子組件,將在生命周期中執(zhí)行didChangeDependencies()方法。我們可以通過ValueNotifier+ValueListenable來進行局部的更新,這部分出離了本文的內(nèi)容,先不作展開。接下來我們分析一下
InheritedWidget是如何實現(xiàn)父與子之間的數(shù)據(jù)共享的。
3.2原理及解析
為了實現(xiàn)父與子的數(shù)據(jù)共享,我們需要弄清楚兩件事:
- 父綁定子的方式
- 父通知子的方式
3.2.1父綁定子的方式
我們先來看一下InheritedWidget這個類:
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key? key, required Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
InheritedWidget繼承ProxyWidget,最終繼承的是Widget。它只有兩個方法,一個是updateShouldNotify(),在上面的例子中可知是用來判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致,是否需要傳遞給已注冊的子組件的。另外還重寫了createElement()方法,創(chuàng)建一個InheritedElement對象。InheritedElement最終繼承Element,我們先看一下它的結(jié)構(gòu):

從命名中我們就可知
setDependencies()是用來綁定依賴關(guān)系的。接下來我們從子組件獲取InheritedWidget實例開始看起,看看具體的綁定流程。如實例中的如下代碼:
static ChildA? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ChildA>();
}
我們看一下context.dependOnInheritedWidgetOfExactType<ChildA>()的流程:
//BuildContext
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
//Element
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
真正的實現(xiàn)是在Element中進行的。其中_inheritedWidgets是個Map,key為T的類型。從上面代碼我們可以知道,先從_inheritedWidgets里尋找類型為T的InheritedElement,即父的InheritedElement。_updateInheritance()是在mount()和activate()調(diào)用的,_inheritedWidgets的初始化在子類InheritedElement的_updateInheritance()中的實現(xiàn)如下:
//InheritedElement
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets![widget.runtimeType] = this;
}
在Element中的實現(xiàn)如下:
//Element
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
從上面的代碼我們可以得知,普通Element組件在生命周期的初始階段,它的_inheritedWidgets為父組件的_inheritedWidgets。而_inheritedWidgets的InheritedElement,會將自己添加到_inheritedWidgets中,從而通過此方式將組件和InheritedWidgets的依賴關(guān)系層層向下傳遞,每一個Element中都含有_inheritedWidgets集合,此集合中包含了此組件的父組件且是InheritedWidgets組件的引用關(guān)系。接下來我們回到dependOnInheritedWidgetOfExactType()方法,ancestor已經(jīng)被加到_inheritedWidgets中,所以它不為空,我們繼續(xù)看里面dependOnInheritedElement()的實現(xiàn):
//Element
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
//InheritedElement
@protected
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
//InheritedElement
@protected
void setDependencies(Element dependent, Object? value) {
_dependents[dependent] = value;
}
好到此為止,我們證實了之前的猜測,子組件找到InheritedElement類型的父組件,父組件調(diào)用setDependencies(),為子組件向_dependents中添加注冊, InheritedWidget組件更新時可以根據(jù)此列表通知子組件。將以上過程總結(jié)一下,如下圖:

- 父組件在
InheritedElement的初始階段:mount()和activate()的時候調(diào)用_updateInheritance()方法將自己添加到_inheritedWidgets中。其他Element子組件會直接拿父組件的_inheritedWidgets。 - 子組件在調(diào)用
context.dependOnInheritedWidgetOfExactType<>()時,將自己注冊給_inheritedWidgets中獲取的InheritedElement類型的父組件的dependents中,從而實現(xiàn)了依賴關(guān)系的確定。
接下來我們看一下當(dāng)組件發(fā)生變化時,父通知子的方式。
3.2.2父通知子的方式
在實例中,當(dāng)setState()發(fā)生數(shù)據(jù)改變的時候,經(jīng)過一系列處理后,會走到InheritedElement的updated()方法中去:
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
當(dāng)執(zhí)行了我們自定義InheritedWidget的updateShouldNotify()判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致需要更新后,繼續(xù)執(zhí)行父類ProxyElement的updated()方法:
//ProxyElement
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
//InheritedElement
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent);
}
}
從這段代碼中我們可以看出,在notifyClients()中會對_dependents的key進行遍歷,然后執(zhí)行notifyDependent()進行通知。接著我們看notifyDependent()都做了什么:
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
@mustCallSuper
void didChangeDependencies() {
assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
它調(diào)用了每個dependent的didChangeDependencies()方法,來通知InheritedWidget依賴發(fā)生了變化,當(dāng)前element需要被標(biāo)記為dirty,重新進行build。到此為止,完成了當(dāng)數(shù)據(jù)發(fā)生變化時,父通知子的流程。我們看一下父通知子的流程圖:

總結(jié)一下就是當(dāng)
InheritedElement數(shù)據(jù)發(fā)生變化而更新的時候,父InheritedWidget會遍歷_dependents,子會執(zhí)行didChangeDependencies()方法將子組件標(biāo)記為dirty而重新build。了解了
InheritedWidget的實現(xiàn)后,我們下個章節(jié)對Provider進行解析。
4.Provider解析
接下來我們分析一下Provider的實現(xiàn)。還記著文章開頭,我們說明需要三步來利用Provider來實現(xiàn)狀態(tài)管理。
1.創(chuàng)建混合或繼承ChangeNotifier的Model,用來實現(xiàn)數(shù)據(jù)更新的通知并監(jiān)聽數(shù)據(jù)的變化。
2.創(chuàng)建ChangeNotifierProvider,用來聲明Provider,實現(xiàn)跨組建的數(shù)據(jù)共享。
3.接收共享數(shù)據(jù)。
我們從創(chuàng)建Model開始講起:
4.1ChangeNotifier
ChangeNotifier實現(xiàn)了Listenable接口,而Listenable實際上就是一個觀察者模型。我們先來看一下ChangeNotifier的結(jié)構(gòu):

在
ChangeNotifier里維護了一個_listeners對象,通過addListener()和removeListener()進行添加或刪除。在調(diào)用notifyListeners()的時候?qū)?shù)據(jù)通知給_listeners。ChangeNotifier非常簡單,我們接著來分析ChangeNotifierProvider的實現(xiàn)。
4.2ChangeNotifierProvider
ChangeNotifierProvider繼承了多個層級:ListenableProvider->InheritedProvider->SingleChildStatelessWidget->StatelessWidget,實際上它是個StatelessWidget。我們從ChangeNotifierProvider.value()方法開始:
//ChangeNotifierProvider
ChangeNotifierProvider.value({
Key? key,
required T value,
TransitionBuilder? builder,
Widget? child,
}) : super.value(
key: key,
builder: builder,
value: value,
child: child,
);
其中required T value是ChangeNotifier對象,我們繼續(xù)看super()的調(diào)用:
//ListenableProvider
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 e,
Listenable? value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
ListenableProvider創(chuàng)建了一個VoidCallback對象,其中value是個Listenable對象,就是我們傳入的ChangeNotifier對象。它的實現(xiàn)是為ChangeNotifier添加listener,這個listener將會執(zhí)行InheritedContext.markNeedsNotifyDependents()方法,這個我們之后再做討論??偠灾?code>ListenableProvider的作用就是幫我們?yōu)?code>ChangeNotifier添加了listener。我們接著往下看:
//InheritedProvider
InheritedProvider.value({
Key? key,
required T value,
UpdateShouldNotify<T>? updateShouldNotify,
StartListening<T>? startListening,
bool? lazy,
this.builder,
Widget? child,
})
: _lazy = lazy,
_delegate = _ValueInheritedProvider(
value: value,
updateShouldNotify: updateShouldNotify,
startListening: startListening,
),
super(key: key, child: child);
到了InheritedProvider這一層,我們發(fā)現(xiàn)builder沒有被繼續(xù)傳下去了,InheritedProvider持有了一個_ValueInheritedProvider類型的_delegate。它的父類_Delegate的代碼如下:
abstract class _Delegate<T> {
_DelegateState<T, _Delegate<T>> createState();
void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}
看到有個看上去跟狀態(tài)相關(guān)的方法需要重寫:createState(),我們繼續(xù)看一下_ValueInheritedProvider重寫的createState()的實現(xiàn):
@override
_ValueInheritedProviderState<T> createState() {
return _ValueInheritedProviderState<T>();
}
返回了_ValueInheritedProviderState對象,_ValueInheritedProviderState繼承了_DelegateState,而_DelegateState持有了一個_InheritedProviderScopeElement對象。繼續(xù)看一下_ValueInheritedProviderState的結(jié)構(gòu):

它定義了
willUpdateDelegate()和dispose()這兩個方法,用來做更新和注銷。這么看來_ValueInheritedProviderState這個類實際上是個狀態(tài)的代理類,類似StatefulWidget和State的關(guān)系。我們一開始提到其實ChangeNotifierProvider是個StatelessWidget,那么它的狀態(tài)肯定是由其他類代理的,由此可知,ChangeNotifierProvider的狀態(tài)是由_ValueInheritedProviderState來代理。ChangeNotifierProvider對于Widget的實現(xiàn)實際上是在父類InheritedProvider進行的,我們看一下InheritedProvider的結(jié)構(gòu):
終于看到了
buildWithChild()這個方法,這是真正我們想看的Widget的內(nèi)部結(jié)構(gòu)的創(chuàng)建:
@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,
// ignore: no_runtimetype_tostring
debugType: kDebugMode ? '$runtimeType' : '',
child: builder != null
? Builder(
builder: (context) => builder!(context, child),
)
: child!,
);
}
我們看到我們所創(chuàng)建的builder或child實際上是被_InheritedProviderScope()進行了包裹。我們繼續(xù)分析_InheritedProviderScope:
class _InheritedProviderScope<T> extends InheritedWidget {
const _InheritedProviderScope({
required this.owner,
required this.debugType,
required Widget child,
}) : super(child: child);
final InheritedProvider<T> owner;
final String debugType;
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
@override
_InheritedProviderScopeElement<T> createElement() {
return _InheritedProviderScopeElement<T>(this);
}
}
到這我們終于看到_InheritedProviderScope繼承了我們熟悉的InheritedWidget,說明我們的創(chuàng)建的Widget都是被InheritedWidget進行了包裹。在createElement()時返回了_InheritedProviderScopeElement對象。_InheritedProviderScopeElement繼承InheritedElement,并實現(xiàn)了InheritedContext接口。我們先看一下它的結(jié)構(gòu):

首先我們關(guān)注到有個
_delegateState的變量,對應(yīng)的就是我們上面所提到的_ValueInheritedProvider,看一下它初始化的位置:
void performRebuild() {
if (_firstBuild) {
_firstBuild = false;
_delegateState = widget.owner._delegate.createState()
..element = this;
}
super.performRebuild();
}
在performRebuild的時候,調(diào)用widget的_delegate對象的createState()方法,即_ValueInheritedProvider的createState()方法,得到一個_ValueInheritedProviderState對象。并將自己賦值給_ValueInheritedProviderState的element對象。
還記不記著在講ListenableProvider的時候提到它添加了listener,這個listener將會執(zhí)行InheritedContext.markNeedsNotifyDependents()方法,而markNeedsNotifyDependents()的定義就在_InheritedProviderScope里:
@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) {
return;
}
markNeedsBuild();
_shouldNotifyDependents = true;
}
這里我看看到它將_InheritedProviderScopeElement標(biāo)志為markNeedsBuild(),即需要被rebuild的組件,然后將_shouldNotifyDependents標(biāo)志為true。
回到我們的ChangeNotifier:當(dāng)我們調(diào)用notifyListeners()來通知數(shù)據(jù)變化的時候,如果有listener被注冊,實際上會執(zhí)行InheritedContext.markNeedsNotifyDependents()方法,具體會執(zhí)行到的位置在ChangeNotifierProvider組件的父類InheritedProvider包裹的_InheritedProviderScope這個InheritedWidget對應(yīng)的_InheritedProviderScopeElement的markNeedsNotifyDependents()方法。
整個過程可總結(jié)為下圖:

不過到目前為止,我們只是知道了這個流程,但是
listener什么時候被注冊,子組件又是如何被刷新的呢?我們繼續(xù)從實例中的Provider.of<>()分析起。
4.3Provider.of<>()
在取數(shù)據(jù)的時候,如實例代碼:Provider.of<ProviderViewModel>(context).number;,來看of()方法的實現(xiàn):
static T of<T>(BuildContext context, {bool listen = true}) {
assert(
context.owner!.debugBuilding ||
listen == false ||
debugIsInInheritedProviderUpdate,
);
final inheritedElement = _inheritedElementOf<T>(context);
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}
首先獲取context的_InheritedProviderScopeElement對象,然后調(diào)用context.dependOnInheritedElement()方法。這個方法我們很熟悉了,在上個章節(jié)介紹InheritedWidget的時候了解過,作用是讓子組件找到InheritedElement類型的父組件,父組件調(diào)用setDependencies(),為子組件向_dependents中添加注冊以形成依賴關(guān)系,InheritedWidget組件更新時可以根據(jù)此列表通知子組件。接著調(diào)用inheritedElement.value返回一個ChangeNotifier對象。這個就是之前我們在ChangeNotifierProvider.value()過程中傳入的ChangeNotifier對象。那么在取值的過程中還做了些什么呢?我們繼續(xù)分析:
@override
T get value => _delegateState.value;
它實際上取的是_ValueInheritedProviderState的value:
@override
T get value {
element!._isNotifyDependentsEnabled = false;
_removeListener ??= delegate.startListening?.call(element!, delegate.value);
element!._isNotifyDependentsEnabled = true;
assert(delegate.startListening == null || _removeListener != null);
return delegate.value;
}
從這段代碼中,我們看到了,在取值的過程中,調(diào)用了delegate.startListening?.call(element!, delegate.value),為上一節(jié)所提到的listener進行了注冊。意味著當(dāng)notifyListeners()時,這個listener將會執(zhí)行InheritedContext.markNeedsNotifyDependents()方法。我們還記著在分析InheritedWidget的時候說明父通知子的時候,會調(diào)用InheritedElement的notifyDependent()方法,那么在Provider中,會在其子類_InheritedProviderScopeElement進行實現(xiàn),代碼如下:
@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
final dependencies = getDependencies(dependent);
if (kDebugMode) {
ProviderBinding.debugInstance.providerDidChange(_debugId);
}
var shouldNotify = false;
if (dependencies != null) {
if (dependencies is _Dependency<T>) {
if (dependent.dirty) {
return;
}
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();
}
}
先取shouldNotify的值,由于我們沒有用selector,這時候shouldNotify為true,當(dāng)前Widget將會進行rebuild。那么如果我們并沒有用Consumer,這時候的Provider.of<ProviderViewModel>(context)的context實際上是ChangeNotifierProvider對應(yīng)的context,整個ChangeNotifierProvider都會進行rebuild操作。Consumer的局部更新如何實現(xiàn)的呢?
4.4Consumer
其實這個很簡單,看一下Consumer的實現(xiàn):
class Consumer<T> extends SingleChildStatelessWidget {
/// {@template provider.consumer.constructor}
/// Consumes a [Provider<T>]
/// {@endtemplate}
Consumer({
Key? key,
required this.builder,
Widget? child,
}) : super(key: key, child: child);
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,
);
}
}
在buildWithChild()中實際上也是使用了Provider.of<T>(context),不過要注意的是,這個context是當(dāng)前組件的context,所以最終只有被Consumer包裹住的子組件才會向_dependents中添加注冊以形成依賴關(guān)系,才會被標(biāo)記為dirty從而進行rebuild。
好了到此為止,Provider的解析已經(jīng)完成了,總結(jié)一下:
-
Provider實際上是個無狀態(tài)的StatelessWidget,通過包裝了InheritedWidget實現(xiàn)父子組件的數(shù)據(jù)共享,通過自定義InheritedElement實現(xiàn)刷新。 -
Provider通過與ChangeNotifier配合使用,實現(xiàn)了觀察者模式,Provider會將子組件添加到父組件的依賴關(guān)系中,在notifyListeners()時,會執(zhí)行InheritedContext.markNeedsNotifyDependents(),將組件標(biāo)記為dirty等待重繪。 -
Consumer會只將被它包裹住的子組件注冊給父的_dependents形成依賴關(guān)系,從而實現(xiàn)了局部更新。
下面我們看一下幾種在Flutter中比較流行的狀態(tài)同步框架并進行比較。
5.幾種狀態(tài)同步框架的對比和選擇

這幾個狀態(tài)同步框架,包括其衍生的一些框架的核心原理都是利用了InheritedWidget實現(xiàn)的。雖然Google官方推薦的使用Provider,但在開發(fā)過程中需要根據(jù)項目大小,開發(fā)人員習(xí)慣等因素去考慮。
6.總結(jié)
本文對Provider框架的使用及實現(xiàn)原理作詳細的說明,為了能夠更好的進行理解,也對InheritedWidget的實現(xiàn)進行了詳細的說明,并在最后對主流的狀態(tài)管理框架進行比較。希望能幫助大家更好的理解Flutter的數(shù)據(jù)共享機制,并根據(jù)自身需求選擇合適的框架應(yīng)用到實際項目中。