扣丁書屋

Flutter狀態管理-Provider的使用和源碼解析

Flutter狀態管理-Provider的使用和源碼解析

前言

在各種前端開發中,由于狀態管理對于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<ProviderViewModel>(context).number}"),
              ChildA(),
            //ChildB(),
            //ChildC()
            ],
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          _providerViewModel.addNumber();
        }, //使用context.read不會調用rebuild
      ),
    );
  }
}

我們用ChangeNotifierProvider將父布局包裹,在父或子節點ChildA通過Provider.of<T>(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<ProviderViewModel>(context).number}"),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                Provider.of<ProviderViewModel>(context, listen: false)
                    .addNumber();
              })
        ],
      ),
    );
  }
}

我們來看一下效果: 我們可以看到不管是在父節點還是在子節點,都可以對ProviderViewModel的數據進行操作和監聽。例1在操作與讀取時使用的是Provider.of<T>(BuildContext context, {bool listen = true})的方式,為了可以更明確對于Provider的操作,我們可將它替換為context.watch<>()和context.read<>()方式。 我們可以通過源碼看到,context.watch<>()context.read<>()方法其實都是調用Provider.of<T>(BuildContext context, {bool listen = true})來實現的:

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("我是子節點"),
          Text("Child B number: ${context.watch<ProviderViewModel>().number}"),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                context.read<ProviderViewModel>().addNumber();
              })
        ],
      ),
    );
  }
}

ChildBChildA實際上是一致的。我們把ProviderTestPageChildB()放開: 其中,每點擊一次父Widget右下角的加號或子Widget的Add Number按鈕,我們看一下Log打印的結果: 我們會發現每一次的操作,都會導致ChildAChildB整體重新build。但實際上從代碼中我們可知,在ChildAChildB中,只有以下的Text()會監聽ProviderViewModel的數據更新:

//ChildA:
Text("Child A number: ${Provider.of<ProviderViewModel>(context).number}")

//ChildB:
Text("Child B number: ${context.watch<ProviderViewModel>().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<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()來監聽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<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供使用,為參數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<ProxyProviderViewModel>().title),
        ],
      ),
    );
  }
}

ChildProxy中,我們監聽ProxyProviderViewModel的數據變化,并將title顯示在Text()中,進行UI上的更新。 我們看一下效果:

我們調用context.read<ProviderViewModel>().addNumber()ProviderViewModel的數據進行更新,可同時更新ProxyProviderViewModelnumber對象,而ChildProxy由于監聽了ProxyProviderViewModel的數據變化,會因此更新UI中title的內容。

好了,到此為止,Provider的使用介紹到這里,下面我們將針對Provider的原理進行說明。Provider實際上是對InheritedWidget進行了封裝,它才是真正實現父與子數據共享的重要元素,所以為了理清Provider的原理,我們必須先弄清楚InheritedWidget的實現過程。

InheritedWidget的原理及解析

InheritedWidget提供了沿樹向下,共享數據的功能,系統中的Provider,Theme等實現都是依賴于它。弄清楚它的原理,對于理解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<ChildA>();
  }

  @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<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("我是子節點 ChildB"), widget.child],
      ),
    );
  }
}

ChildC依賴ChildA,并讀取ChildA的共享數據,代碼如下:

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("我是子節點 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<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的值,通過構造方法傳遞給ChildA,整個樹重新build。ChildC讀取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<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;
  }

真正的實現是在Element中進行的。其中_inheritedWidgets是個Map,keyT的類型。從上面代碼我們可以知道,先從_inheritedWidgets里尋找類型為TInheritedElement,即父的InheritedElement。_updateInheritance()是在mount()activate()調用的,_inheritedWidgets的初始化在子類InheritedElement_updateInheritance()中的實現如下:

//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中的實現如下:

//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<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類型的父組件,父組件調用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()的時候將數據通知給_listeners。ChangeNotifier非常簡單,我們接著來分析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<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創建了一個VoidCallback對象,其中value是個Listenable對象,就是我們傳入的ChangeNotifier對象。它的實現是為ChangeNotifier添加listener,這個listener將會執行InheritedContext.markNeedsNotifyDependents()方法,這個我們之后再做討論??偠灾?,ListenableProvider的作用就是幫我們為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這一層,我們發現builder沒有被繼續傳下去了,InheritedProvider持有了一個_ValueInheritedProvider類型的_delegate。它的父類_Delegate的代碼如下:

abstract class _Delegate<T> {
  _DelegateState<T, _Delegate<T>> createState();

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}

看到有個看上去跟狀態相關的方法需要重寫:createState(),我們繼續看一下_ValueInheritedProvider重寫的createState()的實現:

@override
_ValueInheritedProviderState<T> createState() {
  return _ValueInheritedProviderState<T>();
}

返回了_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<T>(
    owner: this,
    // ignore: no_runtimetype_tostring
    debugType: kDebugMode ? '$runtimeType' : '',
    child: builder != null
        ? Builder(
      builder: (context) => builder!(context, child),
    )
        : child!,
  );
}

我們看到我們所創建的builderchild實際上是被_InheritedProviderScope()進行了包裹。我們繼續分析_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,說明我們的創建的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<ProviderViewModel>(context).number;,來看of()方法的實現:


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對象,然后調用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<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,這時候shouldNotifytrue,當前Widget將會進行rebuild。那么如果我們并沒有用Consumer,這時候的Provider.of<ProviderViewModel>(context)context實際上是ChangeNotifierProvider對應的context,整個ChangeNotifierProvider都會進行rebuild操作。Consumer的局部更新如何實現的呢?

Consumer

其實這個很簡單,看一下Consumer的實現:

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是當前組件的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的數據共享機制,并根據自身需求選擇合適的框架應用到實際項目中。

參考文獻

https://www.cnblogs.com/mengqd/p/14300373.html

https://www.jianshu.com/p/bf2f33b2b5ef


https://mp.weixin.qq.com/s/vUhDvHaStrTwbE4tovOqtA

最多閱讀

如何有效定位Flutter內存問題? 1年以前  |  11725次閱讀
Flutter的手勢GestureDetector分析詳解 3年以前  |  7686次閱讀
Flutter插件詳解及其發布插件 3年以前  |  6371次閱讀
在Flutter中添加資源和圖片 3年以前  |  5201次閱讀
Flutter 狀態管理指南之 Provider 3年以前  |  4388次閱讀
發布Flutter開發的iOS程序 3年以前  |  4365次閱讀
Flutter for Web詳細介紹 3年以前  |  4229次閱讀
在Flutter中發起HTTP網絡請求 3年以前  |  4005次閱讀
使用Inspector檢查用戶界面 3年以前  |  3923次閱讀
Flutter Widget框架概述 3年以前  |  3301次閱讀
Flutter路由詳解 3年以前  |  3141次閱讀
JSON和序列化 3年以前  |  3060次閱讀
Flutter框架概覽 3年以前  |  3025次閱讀
推薦5個Flutter重磅開源項目! 1年以前  |  2983次閱讀
為Flutter應用程序添加交互 3年以前  |  2982次閱讀
使用自定義字體 3年以前  |  2879次閱讀
處理文本輸入 3年以前  |  2870次閱讀
編寫國際化Flutter App 3年以前  |  2868次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>