热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Flutter状态管理Provider的使用和源码解析

前言在各种前端开发中,由于状态管理对于App的开发维护成本,性能等方面都起着至关重要的作用,所以选择合适的状态管理框架显得尤为重要。Flutter作为跨平台框架的后起之秀,背靠Go

前言

在各种前端开发中,由于状态管理对于App的开发维护成本,性能等方面都起着至关重要的作用,所以选择合适的状态管理框架显得尤为重要。Flutter作为跨平台框架的后起之秀,背靠Google大树,短时间内开发者们在开源社区提供了多种状态管理框架。而Provider是官方推荐的状态管理方式之一,可用作跨组件的数据共享。本文将针对Provider框架的使用及实现原理作详细的说明,并在最后对主流的状态管理框架进行比较。

使用

Provider的使用非常简单,通常使用ChangeNotifierProvider配合ChangeNotifier一起使用来实现状态的管理与Widget的更新。其中ChangeNotifier是系统提供的,用来负责数据的变化通知。ChangeNotifierProvider本质上其实就是Widget,它作为父节点Widget,可将数据共享给其所有子节点Widget使用或更新。具体的原理解析在后续章节会进行说明。所以通常我们只需要三步即可利用Provider来实现状态管理。

1.创建混合或继承ChangeNotifierModel,用来实现数据更新的通知并监听数据的变化。

2.创建ChangeNotifierProvider,用来声明Provider,实现跨组建的数据共享。

3.接收共享数据。

我们来举个例子,看看它是怎么在父子之间进行数据共享的:

例1 Provider的使用:

  • 创建Model

class ProviderViewModel with ChangeNotifier { int _number = 0; get number => _number; void addNumber() { _number++; notifyListeners(); } }

上面的代码很简单,调用addNumber()方法让_number加1,并调用notifyListeners()通知给监听方。

  • 创建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("我是父节点"), Text( "Parent number is: ${Provider.of(context).number}"), ChildA(), //ChildB(), //ChildC() ], ); }, ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () { _providerViewModel.addNumber(); }, //使用context.read不会调用rebuild ), ); } }

我们用ChangeNotifierProvider将父布局包裹,在父或子节点ChildA通过Provider.of(BuildContext context, {bool listen = true})进行数据操作,可同步更新父与子的数据与UI。其中listen默认为true可监听数据的变化,为false的情况只可读取数据的值。

  • 接收共享数据:

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(context).number}"), MaterialButton( child: const Text("Add Number"), color: Colors.white, onPressed: () { Provider.of(context, listen: false) .addNumber(); }) ], ), ); } }

我们来看一下效果: 

我们可以看到不管是在父节点还是在子节点,都可以对ProviderViewModel的数据进行操作和监听。例1在操作与读取时使用的是Provider.of(BuildContext context, {bool listen = true})的方式,为了可以更明确对于Provider的操作,我们可将它替换为context.watch<>()和context.read<>()方式。 我们可以通过源码看到,context.watch<>()context.read<>()方法其实都是调用Provider.of(BuildContext context, {bool listen = true})来实现的:

T watch() { return Provider.of(this); } T read() { return Provider.of(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("我是子节点"), Text("Child B number: ${context.watch().number}"), MaterialButton( child: const Text("Add Number"), color: Colors.white, onPressed: () { context.read().addNumber(); }) ], ), ); } }

ChildBChildA实际上是一致的。我们把ProviderTestPageChildB()放开: 

其中,每点击一次父Widget右下角的加号或子Widget的Add Number按钮,我们看一下Log打印的结果: 

我们会发现每一次的操作,都会导致ChildAChildB整体重新build。但实际上从代码中我们可知,在ChildAChildB中,只有以下的Text()会监听ProviderViewModel的数据更新:

//ChildA: Text("Child A number: ${Provider.of(context).number}") //ChildB: Text("Child B number: ${context.watch().number}")

那么我们希望可以实现局部的更新该如何实现?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("我是子节点"), Consumer(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().addNumber(); }) ], ), ); } }

由于我们只希望Text()来监听ProviderViewModel的数据更新,我们用Consumer<>()包裹住Text(),其中builder的传参value即是ProviderViewModel对象,把ProviderTestPageChildC()放开,我们看一下结果: 

再打印一下Log: 

Log中我们可以得知,ChildC并没有被rebuild,而是由Consumer调用内部的builder来实现局部更新的。 到此为止,一个简单的Provider使用就介绍完成。另外Provider还提供了ProxyProvider,从名字上来看,我们可知这是个代理Provider,它是用来协调Model与Model之间的更新,比如一个ModelA依赖另一个ModelB,ModelB更新,他就要让依赖它的ModelA也随之更新。我们直接上代码来看一下它的用法,还是分三步:

例2 ProxyProvider的使用:

  • 创建ProxyProviderViewModel

class ProxyProviderViewModel with ChangeNotifier { int number; ProxyProviderViewModel(this.number); String get title { return "The number is: $number"; } }

这个类只是简单的在构造方法例传入int值,并创建get方法得到一段文本。

  • 创建Provider:

class ProxyProviderTestPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Provider Test"), ), body: MultiProvider( providers: [ ChangeNotifierProvider( create: (_) => ProviderViewModel()), ChangeNotifierProxyProvider( create: (context) => ProxyProviderViewModel( context.read().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().addNumber(); }) ], ); }, ), ); } }

我们在body中用MultiProvider来包裹布局。MultiProvider的作用是同时可声明多个Provider供使用,为参数providers添加Provider数组。我们首先声明一个ChangeNotifierProvider,同例1中的ProviderViewModel。接着我们声明一个ChangeNotifierProxyProvider用来做代理Provider。其中create参数是ProxyProvider的创建,update参数是ProxyProvider的更新。在我们的例子中,实际上是对ProviderViewModel进行数据操作,由ProxyProviderViewModel监听ProviderViewModel的数据变化。

  • 接收共享数据:

class ChildProxy extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: double.infinity, child: Column( children: [ Text(context.watch().title), ], ), ); } }

ChildProxy中,我们监听ProxyProviderViewModel的数据变化,并将title显示在Text()中,进行UI上的更新。 我们看一下效果: 

我们调用context.read().addNumber()ProviderViewModel的数据进行更新,可同时更新ProxyProviderViewModelnumber对象,而ChildProxy由于监听了ProxyProviderViewModel的数据变化,会因此更新UI中title的内容。

好了,到此为止,Provider的使用介绍到这里,下面我们将针对Provider的原理进行说明。 Provider实际上是对InheritedWidget进行了封装,它才是真正实现父与子数据共享的重要元素,所以为了理清Provider的原理,我们必须先弄清楚InheritedWidget的实现过程。

InheritedWidget的原理及解析

InheritedWidget提供了沿树向下,共享数据的功能,系统中的ProviderTheme等实现都是依赖于它。弄清楚它的原理,对于理解Flutter的数据共享方式会有很大的帮助。本章节将先通过实例说明InheritedWidget的用法,然后进行原理的解析。

使用

我们举个简单的例子:

这是一个简单的树结构,其中ChildAChildC需要共享Data这个数据,ChildB不需要。传递方式有很多种,比如说通过构造方法传递,通过函数调用,函数回调传递等。但是如果树层级非常多的话,刚才提到的传递方式将会对整个代码结构带来灾难,包括代码耦合度过高,回调地狱等。我们看看InheritedWidget是怎么处理的:

1.作为整个树的父节点,需要使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(); } @override bool updateShouldNotify(covariant ChildA oldWidget) { return oldWidget.number != number; } }

  • 其中updateShouldNotify()方法需要被重写,用来判断现有共享数据和旧的共享数据是否一致,是否需要传递给已注册的子组件。
  • of()方法是一种约定俗成的通用写法,只是起到方便调用的作用。其中context.dependOnInheritedWidgetOfExactType()是为它的子组件注册了依赖关系。 通过这样的方式,将ChildA声明成了一个给子组件共享数据的Widget

ChildB就是一个中间层级的普通Widget,用来连接树结构:

class ChildB extends StatefulWidget { final Widget child; ChildB({Key? key, required this.child}) : super(key: key); @override _ChildBState createState() => _ChildBState(); } class _ChildBState extends State { @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("我是子节点 ChildB"), widget.child], ), ); } }

ChildC依赖ChildA,并读取ChildA的共享数据,代码如下:

class ChildC extends StatefulWidget { ChildC({Key? key}) : super(key: key); @override _ChildCState createState() => _ChildCState(); } class _ChildCState extends State { @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("我是子节点 ChildC"), Text("Child C number: ${ChildA.of(context)?.number}"), ], ), ); } }

ChildC通过ChildA.of(context)?.number的方式读取ChildA的共享数据。 我们把这个树串起来:

class InheritedWidgetTestPage extends StatefulWidget { InheritedWidgetTestPage({Key? key}) : super(key: key); @override _InheritedWidgetTestPageState createState() => _InheritedWidgetTestPageState(); } class _InheritedWidgetTestPageState extends State { 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的值,通过构造方法传递给ChildA,整个树重新buildChildC读取ChildA的数据,并进行UI的更新,我们看一下结果: 

ChildC接收到了数据变化并进行了更新,我们再来看一下Log: 

在这个过程中,会发现ChildBChildC都进行了rebuild,由于ChildC依赖ChildA的共享数据,ChildCrebuild之前执行了didChangeDependencies()方法,说明ChildC的依赖关系发生了改变;而ChildB由于不依赖ChildA的共享数据所以并没有执行didChangeDependencies()

这个例子给出了InheritedWidget的一个基本使用方式,但需要注意的是,在整个树结构中,其实ChildB是不依赖ChildA的共享数据的,按理来说,在数据发生变化,我们是不希望ChildB进行rebuild的。所以需要说明的是,InheritedWidget的正确用法并不是通过setState()来实现rebuild的,这里用setState()举例仅仅是为了将整个流程串起来。这个例子的重点在于,依赖父组件的共享数据的子组件,将在生命周期中执行didChangeDependencies()方法。我们可以通过ValueNotifier+ValueListenable来进行局部的更新,这部分出离了本文的内容,先不作展开。

接下来我们分析一下InheritedWidget是如何实现父与子之间的数据共享的。

原理及解析

为了实现父与子的数据共享,我们需要弄清楚两件事:

  • 父绑定子的方式
  • 父通知子的方式

父绑定子的方式

我们先来看一下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(),在上面的例子中可知是用来判断现有共享数据和旧的共享数据是否一致,是否需要传递给已注册的子组件的。另外还重写了createElement()方法,创建一个InheritedElement对象。InheritedElement最终继承Element,我们先看一下它的结构: 

从命名中我们就可知setDependencies()是用来绑定依赖关系的。接下来我们从子组件获取InheritedWidget实例开始看起,看看具体的绑定流程。如实例中的如下代码:

static ChildA? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); }

我们看一下context.dependOnInheritedWidgetOfExactType()的流程:

//BuildContext T? dependOnInheritedWidgetOfExactType({ Object? aspect }); //Element @override T? dependOnInheritedWidgetOfExactType({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; }

真正的实现是在Element中进行的。其中_inheritedWidgets是个MapkeyT的类型。从上面代码我们可以知道,先从_inheritedWidgets里寻找类型为TInheritedElement,即父的InheritedElement_updateInheritance()是在mount()activate()调用的,_inheritedWidgets的初始化在子类InheritedElement_updateInheritance()中的实现如下:

//InheritedElement @override void _updateInheritance() { assert(_lifecycleState == _ElementLifecycle.active); final Map? incomingWidgets = _parent?._inheritedWidgets; if (incomingWidgets != null) _inheritedWidgets = HashMap.from(incomingWidgets); else _inheritedWidgets = HashMap(); _inheritedWidgets![widget.runtimeType] = this; }

Element中的实现如下:

//Element void _updateInheritance() { assert(_active); _inheritedWidgets = _parent?._inheritedWidgets; }

从上面的代码我们可以得知,普通Element组件在生命周期的初始阶段,它的_inheritedWidgets为父组件的_inheritedWidgets。而_inheritedWidgetsInheritedElement,会将自己添加到_inheritedWidgets中,从而通过此方式将组件和InheritedWidgets的依赖关系层层向下传递,每一个Element中都含有_inheritedWidgets集合,此集合中包含了此组件的父组件且是InheritedWidgets组件的引用关系。接下来我们回到dependOnInheritedWidgetOfExactType()方法,ancestor已经被加到_inheritedWidgets中,所以它不为空,我们继续看里面dependOnInheritedElement()的实现:

//Element @override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) { assert(ancestor != null); _dependencies ??= HashSet(); _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类型的父组件,父组件调用setDependencies(),为子组件向_dependents中添加注册, InheritedWidget组件更新时可以根据此列表通知子组件。将以上过程总结一下,如下图: 

  • 父组件在InheritedElement的初始阶段:mount()activate()的时候调用_updateInheritance()方法将自己添加到_inheritedWidgets中。其他Element子组件会直接拿父组件的_inheritedWidgets
  • 子组件在调用context.dependOnInheritedWidgetOfExactType<>()时,将自己注册给_inheritedWidgets中获取的InheritedElement类型的父组件的 dependents中,从而实现了依赖关系的确定。

接下来我们看一下当组件发生变化时,父通知子的方式。

父通知子的方式

在实例中,当setState()发生数据改变的时候,经过一系列处理后,会走到InheritedElementupdated()方法中去:

@override void updated(InheritedWidget oldWidget) { if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget); }

当执行了我们自定义InheritedWidgetupdateShouldNotify()判断现有共享数据和旧的共享数据是否一致需要更新后,继续执行父类ProxyElementupdated()方法:

//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()中会对_dependentskey进行遍历,然后执行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(); }

它调用了每个dependentdidChangeDependencies()方法,来通知InheritedWidget依赖发生了变化,当前element需要被标记为dirty,重新进行build。到此为止,完成了当数据发生变化时,父通知子的流程。我们看一下父通知子的流程图: 

总结一下就是当InheritedElement数据发生变化而更新的时候,父InheritedWidget会遍历_dependents,子会执行didChangeDependencies()方法将子组件标记为dirty而重新build

了解了InheritedWidget的实现后,我们下个章节对Provider进行解析。

Provider解析

接下来我们分析一下Provider的实现。还记着文章开头,我们说明需要三步来利用Provider来实现状态管理。

1.创建混合或继承ChangeNotifierModel,用来实现数据更新的通知并监听数据的变化。

2.创建ChangeNotifierProvider,用来声明Provider,实现跨组建的数据共享。

3.接收共享数据。

我们从创建Model开始讲起:

ChangeNotifier

ChangeNotifier实现了Listenable接口,而Listenable实际上就是一个观察者模型。我们先来看一下ChangeNotifier的结构: 

ChangeNotifier里维护了一个_listeners对象,通过addListener()removeListener()进行添加或删除。在调用notifyListeners()的时候将数据通知给_listenersChangeNotifier非常简单,我们接着来分析ChangeNotifierProvider的实现。

ChangeNotifierProvider

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 valueChangeNotifier对象,我们继续看super()的调用:

//ListenableProvider ListenableProvider.value({ Key? key, required T value, UpdateShouldNotify? 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创建了一个VoidCallback对象,其中value是个Listenable对象,就是我们传入的ChangeNotifier对象。它的实现是为ChangeNotifier添加listener,这个listener将会执行InheritedContext.markNeedsNotifyDependents()方法,这个我们之后再做讨论。总而言之,ListenableProvider的作用就是帮我们为ChangeNotifier添加了listener。我们接着往下看:

//InheritedProvider InheritedProvider.value({ Key? key, required T value, UpdateShouldNotify? updateShouldNotify, StartListening? startListening, bool? lazy, this.builder, Widget? child, }) : _lazy = lazy, _delegate = _ValueInheritedProvider( value: value, updateShouldNotify: updateShouldNotify, startListening: startListening, ), super(key: key, child: child);

到了InheritedProvider这一层,我们发现builder没有被继续传下去了,InheritedProvider持有了一个_ValueInheritedProvider类型的_delegate。它的父类_Delegate的代码如下:

abstract class _Delegate { _DelegateState> createState(); void debugFillProperties(DiagnosticPropertiesBuilder properties) {} }

看到有个看上去跟状态相关的方法需要重写:createState(),我们继续看一下_ValueInheritedProvider重写的createState()的实现:

@override _ValueInheritedProviderState createState() { return _ValueInheritedProviderState(); }

返回了_ValueInheritedProviderState对象,_ValueInheritedProviderState继承了_DelegateState,而_DelegateState持有了一个_InheritedProviderScopeElement对象。继续看一下_ValueInheritedProviderState的结构: 

它定义了willUpdateDelegate()dispose()这两个方法,用来做更新和注销。这么看来_ValueInheritedProviderState这个类实际上是个状态的代理类,类似StatefulWidgetState的关系。我们一开始提到其实ChangeNotifierProvider是个StatelessWidget,那么它的状态肯定是由其他类代理的,由此可知,ChangeNotifierProvider的状态是由_ValueInheritedProviderState来代理。

ChangeNotifierProvider对于Widget的实现实际上是在父类InheritedProvider进行的,我们看一下InheritedProvider的结构: 

终于看到了buildWithChild()这个方法,这是真正我们想看的Widget的内部结构的创建:

@override Widget buildWithChild(BuildContext context, Widget? child) { assert( builder != null || child != null, '$runtimeType used outside of MultiProvider must specify a child', ); return _InheritedProviderScope( owner: this, // ignore: no_runtimetype_tostring debugType: kDebugMode ? '$runtimeType' : '', child: builder != null ? Builder( builder: (context) => builder!(context, child), ) : child!, ); }

我们看到我们所创建的builderchild实际上是被_InheritedProviderScope()进行了包裹。我们继续分析_InheritedProviderScope

class _InheritedProviderScope extends InheritedWidget { const _InheritedProviderScope({ required this.owner, required this.debugType, required Widget child, }) : super(child: child); final InheritedProvider owner; final String debugType; @override bool updateShouldNotify(InheritedWidget oldWidget) { return false; } @override _InheritedProviderScopeElement createElement() { return _InheritedProviderScopeElement(this); } }

到这我们终于看到_InheritedProviderScope继承了我们熟悉的InheritedWidget,说明我们的创建的Widget都是被InheritedWidget进行了包裹。在createElement()时返回了_InheritedProviderScopeElement对象。_InheritedProviderScopeElement继承InheritedElement,并实现了InheritedContext接口。我们先看一下它的结构: 

首先我们关注到有个_delegateState的变量,对应的就是我们上面所提到的_ValueInheritedProvider,看一下它初始化的位置:

void performRebuild() { if (_firstBuild) { _firstBuild = false; _delegateState = widget.owner._delegate.createState() ..element = this; } super.performRebuild(); }

performRebuild的时候,调用widget_delegate对象的createState()方法,即_ValueInheritedProvidercreateState()方法,得到一个_ValueInheritedProviderState对象。并将自己赋值给_ValueInheritedProviderStateelement对象。 还记不记着在讲ListenableProvider的时候提到它添加了listener,这个listener将会执行InheritedContext.markNeedsNotifyDependents()方法,而markNeedsNotifyDependents()的定义就在_InheritedProviderScope里:

@override void markNeedsNotifyDependents() { if (!_isNotifyDependentsEnabled) { return; } markNeedsBuild(); _shouldNotifyDependents = true; }

这里我看看到它将_InheritedProviderScopeElement标志为markNeedsBuild(),即需要被rebuild的组件,然后将_shouldNotifyDependents标志为true

回到我们的ChangeNotifier:当我们调用notifyListeners()来通知数据变化的时候,如果有listener被注册,实际上会执行InheritedContext.markNeedsNotifyDependents()方法,具体会执行到的位置在ChangeNotifierProvider组件的父类InheritedProvider包裹的_InheritedProviderScope这个InheritedWidget对应的_InheritedProviderScopeElementmarkNeedsNotifyDependents()方法。

整个过程可总结为下图: 

不过到目前为止,我们只是知道了这个流程,但是listener什么时候被注册,子组件又是如何被刷新的呢?我们继续从实例中的Provider.of<>()分析起。

Provider.of<>()

在取数据的时候,如实例代码:Provider.of(context).number;,来看of()方法的实现:

static T of(BuildContext context, {bool listen = true}) { assert( context.owner!.debugBuilding || listen == false || debugIsInInheritedProviderUpdate, ); final inheritedElement = _inheritedElementOf(context); if (listen) { context.dependOnInheritedElement(inheritedElement); } return inheritedElement.value; }

首先获取context_InheritedProviderScopeElement对象,然后调用context.dependOnInheritedElement()方法。这个方法我们很熟悉了,在上个章节介绍InheritedWidget的时候了解过,作用是让子组件找到InheritedElement类型的父组件,父组件调用setDependencies(),为子组件向_dependents中添加注册以形成依赖关系,InheritedWidget组件更新时可以根据此列表通知子组件。接着调用inheritedElement.value返回一个ChangeNotifier对象。这个就是之前我们在ChangeNotifierProvider.value()过程中传入的ChangeNotifier对象。那么在取值的过程中还做了些什么呢?我们继续分析:

@override T get value => _delegateState.value;

它实际上取的是_ValueInheritedProviderStatevalue

@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; }

从这段代码中,我们看到了,在取值的过程中,调用了delegate.startListening?.call(element!, delegate.value),为上一节所提到的listener进行了注册。意味着当notifyListeners()时,这个listener将会执行InheritedContext.markNeedsNotifyDependents()方法。我们还记着在分析InheritedWidget的时候说明父通知子的时候,会调用InheritedElementnotifyDependent()方法,那么在Provider中,会在其子类_InheritedProviderScopeElement进行实现,代码如下:

@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) { 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,这时候shouldNotifytrue,当前Widget将会进行rebuild。那么如果我们并没有用Consumer,这时候的Provider.of(context)context实际上是ChangeNotifierProvider对应的context,整个ChangeNotifierProvider都会进行rebuild操作。Consumer的局部更新如何实现的呢?

Consumer

其实这个很简单,看一下Consumer的实现:

class Consumer extends SingleChildStatelessWidget { /// {@template provider.consumer.constructor} /// Consumes a [Provider] /// {@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(context), child, ); } }

buildWithChild()中实际上也是使用了Provider.of(context),不过要注意的是,这个context是当前组件的context,所以最终只有被Consumer包裹住的子组件才会向_dependents中添加注册以形成依赖关系,才会被标记为dirty从而进行rebuild

好了到此为止,Provider的解析已经完成了,总结一下:

  • Provider实际上是个无状态的StatelessWidget,通过包装了InheritedWidget实现父子组件的数据共享,通过自定义InheritedElement实现刷新。
  • Provider通过与ChangeNotifier配合使用,实现了观察者模式,Provider会将子组件添加到父组件的依赖关系中,在notifyListeners()时,会执行InheritedContext.markNeedsNotifyDependents(),将组件标记为dirty等待重绘。
  • Consumer会只将被它包裹住的子组件注册给父的_dependents形成依赖关系,从而实现了局部更新。

下面我们看一下几种在Flutter中比较流行的状态同步框架并进行比较。

几种状态同步框架的对比和选择

这几个状态同步框架,包括其衍生的一些框架的核心原理都是利用了InheritedWidget实现的。虽然Google官方推荐的使用Provider,但在开发过程中需要根据项目大小,开发人员习惯等因素去考虑。

总结

本文对Provider框架的使用及实现原理作详细的说明,为了能够更好的进行理解,也对InheritedWidget的实现进行了详细的说明,并在最后对主流的状态管理框架进行比较。希望能帮助大家更好的理解Flutter的数据共享机制,并根据自身需求选择合适的框架应用到实际项目中。


推荐阅读
  • 本次发布的Qt音乐播放器2.0版本在用户界面方面进行了细致优化,提升了整体的视觉效果和用户体验。尽管核心功能与1.0版本保持一致,但界面的改进使得操作更加直观便捷,为用户带来了更为流畅的使用体验。此外,我们还对部分细节进行了微调,以确保软件的稳定性和性能得到进一步提升。 ... [详细]
  • 尽管我们尽最大努力,任何软件开发过程中都难免会出现缺陷。为了更有效地提升对支持部门的协助与支撑,本文探讨了多种策略和最佳实践,旨在通过改进沟通、增强培训和支持流程来减少这些缺陷的影响,并提高整体服务质量和客户满意度。 ... [详细]
  • 一个建表一个执行crud操作建表代码importandroid.content.Context;importandroid.database.sqlite.SQLiteDat ... [详细]
  • 实验九:使用SharedPreferences存储简单数据
    本实验旨在帮助学生理解和掌握使用SharedPreferences存储和读取简单数据的方法,包括程序参数和用户选项。 ... [详细]
  • 本文详细介绍了一种利用 ESP8266 01S 模块构建 Web 服务器的成功实践方案。通过具体的代码示例和详细的步骤说明,帮助读者快速掌握该模块的使用方法。在疫情期间,作者重新审视并研究了这一未被充分利用的模块,最终成功实现了 Web 服务器的功能。本文不仅提供了完整的代码实现,还涵盖了调试过程中遇到的常见问题及其解决方法,为初学者提供了宝贵的参考。 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • 开发笔记:深入解析Android自定义控件——Button的72种变形技巧
    开发笔记:深入解析Android自定义控件——Button的72种变形技巧 ... [详细]
  • APKAnalyzer(1):命令行操作体验与功能解析
    在对apkChecker进行深入研究后,自然而然地关注到了Android Studio中的APK分析功能。将APK文件导入IDE中,系统会自动解析并展示其中各类文件的详细信息。官方文档提供了详细的命令行工具使用指南,帮助开发者快速上手。本文以一个RecyclerView的Adapter代理开源库为例,探讨了如何利用这些工具进行高效的APK分析。 ... [详细]
  • 在Android开发中,通过调用系统内置的音频和视频播放功能,可以实现高效、便捷的多媒体处理。本文将详细介绍如何利用Android系统的媒体播放器组件,实现对音频和视频文件的播放控制,包括基本的播放、暂停、停止等操作,以及如何处理播放过程中的各种事件,确保应用的稳定性和用户体验。 ... [详细]
  • 安装Qt时,Qt\Qt5.x.x文件夹下自动安装了example文件夹,其中包含了大量的示例。这里根据Examples\Qt-5.5\widgets\t ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 本项目通过Python编程实现了一个简单的汇率转换器v1.02。主要内容包括:1. Python的基本语法元素:(1)缩进:用于表示代码的层次结构,是Python中定义程序框架的唯一方式;(2)注释:提供开发者说明信息,不参与实际运行,通常每个代码块添加一个注释;(3)常量和变量:用于存储和操作数据,是程序执行过程中的重要组成部分。此外,项目还涉及了函数定义、用户输入处理和异常捕获等高级特性,以确保程序的健壮性和易用性。 ... [详细]
  • 本文探讨了在Android应用中实现动态滚动文本显示控件的优化方法。通过详细分析焦点管理机制,特别是通过设置返回值为`true`来确保焦点不会被其他控件抢占,从而提升滚动文本的流畅性和用户体验。具体实现中,对`MarqueeText.java`进行了代码层面的优化,增强了控件的稳定性和兼容性。 ... [详细]
  • Android目录遍历工具 | AppCrawler自动化测试进阶(第二部分):个性化配置详解
    终于迎来了“足不出户也能为社会贡献力量”的时刻,但有追求的测试工程师绝不会让自己的生活变得乏味。与其在家消磨时光,不如利用这段时间深入研究和提升自己的技术能力,特别是对AppCrawler自动化测试工具的个性化配置进行详细探索。这不仅能够提高测试效率,还能为项目带来更多的价值。 ... [详细]
  • PyQt5 QTextEdit:深入解析Python中多功能GUI库的应用与实现
    本文详细探讨了 PyQt5 中 QTextEdit 组件在 Python 多功能 GUI 库中的应用与实现。PyQt5 是 Qt 框架的 Python 绑定,提供了超过 620 个类和 6000 个函数及方法,广泛应用于跨平台应用程序开发。QTextEdit 作为其中的重要组件,支持丰富的文本编辑功能,如富文本格式、文本高亮和自定义样式等。PyQt5 的流行性不仅在于其强大的功能,还在于其易用性和灵活性,使其成为开发复杂用户界面的理想选择。 ... [详细]
author-avatar
S晓晓宋五
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有