Flutter狀態管理Provider(二)過程分析

mwq30123發表於2020-04-05

接前一篇文章 Flutter狀態管理Provider(一) 簡易使用

過程分析

State之setState

我們debug上面示例的程式碼。進入setState

 void setState(VoidCallback fn) {
    final dynamic result = fn() as dynamic;
    _element.markNeedsBuild();///這一步Element
  }
複製程式碼

我們進入Element

void markNeedsBuild() {
   if (!_active) return;
   if (dirty) return;
   _dirty = true;
   owner.scheduleBuildFor(this); ///BuildOwner
 }
複製程式碼

BuildOwner.scheduleBuildFor: 把當前element加入到dirty列表。drawFrame重新構建dirty element

/// Adds an element to the dirty elements list
/// so that it will be rebuilt  
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
  if (element._inDirtyList) {
      return true;
    }());
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
    _scheduledFlushDirtyElements = true;
    onBuildScheduled();
  }
  _dirtyElements.add(element);
  element._inDirtyList = true;
}
複製程式碼

重新構建的呼叫棧是這樣的。

--> onBuildScheduled() (非必須)
···
--> SchedulerBinding.scheduleFrame() 
···
--> WidgetsBinding.drawFrame()
--> BuildOwner.buildScope()
--> Element.rebuild() 
--> ComponentElement.performBuild()
--> StatefulElement.build()/updateChild(_child, built, slot)
--> State.build()
複製程式碼

更多細節可以參考 文章

InheritedWidget

setState 通過 Element.markNeedsBuild(),實現了無腦rebuild一切。接下來是InheritedWidget,關注兩個問題:

  • 如何共享資料?
  • 如何控制rebuild?

首先要從InheritedWidget的使用入手

////CountProvider繼承InheritedWidget,儲存我們的資料
class CountProvider extends InheritedWidget {
  final int count;

  CountProvider({Key key, this.count, Widget child})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(CountProvider old) {
    return true;
  }
}
//拿到CountProvider例項
CountProvider provider1 = BuildContext.getElementForInheritedWidgetOfExactType<CountProvider>().widget;
var count=provider1.count;

複製程式碼

BuildContext物件開始有段註釋說明

[BuildContext] objects are actually [Element] objects. The [BuildContext] interface is used to discourage direct manipulation of [Element] objects.

BuildContext物件 其實就是Element物件。BuildContext介面是用來阻止對Element物件的直接訪問。顯而易見,Element是實現細節,系統不希望我們直接操作Element。

InheritedWidget教會我們,有兩種方式獲取InheritedWidget。1.12.13 替換了1.6的老方法,更加明確了這兩個方法的字面意思。

///第一種,拿到InheritedElement獲取它的InheritedWidget
 InheritedElement Element.getElementForInheritedWidgetOfExactType<T extends InheritedWidget>()

///第二種
T Element.dependOnInheritedWidgetOfExactType<T extends InheritedWidget>();

複製程式碼

InheritedElement是啥?我們進入到InheritedWidget。

InheritedWidget告訴我們兩個事情:

  • 對應Element是InheritedElement。那重點就在InheritedElement裡面了。
  • 解釋updateShouldNotify,大概意思呢,當InheritedWidget rebuilt時,我們並不總是需要rebuilt 依賴它的widget。需要或者不需要,這取決於你的updateShouldNotify方法。
abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
複製程式碼

接下來我們進入第一種

class Element{
 
 Map<Type, InheritedElement> _inheritedWidgets;
  
 @override
 InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
 ///key為T的Type,從_inheritedWidgets中獲取 InheritedElement
   final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
   return ancestor;
 }
}
///我們發現_updateInheritance() 修改了_inheritedWidgets
void _updateInheritance() {
   assert(_active);
   _inheritedWidgets = _parent?._inheritedWidgets;
 }
 
 ///而_updateInheritance()是在mount()或者active()中呼叫 
 ///mount()是首次把element新增到element Tree,然後 element是active狀態
 ///active()是把deactivate element ,重新標記為active狀態
 ///總之是從無到有或者從不可見到可見。
 @mustCallSuper
 void mount(Element parent, dynamic newSlot) {
   _parent = parent;
   _slot = newSlot;
   _depth = _parent != null ? _parent.depth + 1 : 1;
   _active = true;
   _updateInheritance();
 }
 
 @mustCallSuper
 void activate() {
   _active = true;
   _dependencies?.clear();
   _hadUnsatisfiedDependencies = false;
   _updateInheritance();
 }
複製程式碼

InheritedWidget對應的是 InheritedElement。它過載了_updateInheritance方法,把自身加了進去,也是可以想象到的操作。

class InheritedElement{
    @override
  void _updateInheritance() {
    assert(_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會繼承(儲存)parent的InheritedElement。所以不管Element 與InheritedElement相隔多少層,只要是childElement,都是可以傳遞過來的。

我們回答了第一個問題--如何共享資料。

現在我們進入到dependOnInheritedWidgetOfExactType

class Element {
 @override
 T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
   ///這段操作與getElementForInheritedWidgetOfExactType一模一樣。
   final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
   ///新增依賴。
   if (ancestor != null) {
     assert(ancestor is InheritedElement);
     return dependOnInheritedElement(ancestor, aspect: aspect);
   }
   _hadUnsatisfiedDependencies = true;
   return null;
 }
 
 @override
 InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
   assert(ancestor != null);
   _dependencies ??= HashSet<InheritedElement>();
   _dependencies.add(ancestor);
   ancestor.updateDependencies(this, aspect);
   return ancestor.widget;
 }
}
複製程式碼

我們再進入ancestor.updateDependencies,幹了哪些事情。

class InheritedElement{
   @protected
   void updateDependencies(Element dependent, Object aspect) {
       setDependencies(dependent, null);
   } 
   
   @protected
   void setDependencies(Element dependent, Object value) {
       _dependents[dependent] = value;
   }
}
複製程式碼

總結下來就是當前Element和InheritedElement互相儲存下彼此的依賴關係。

我們回憶下上篇中自定義的ChangeNotifierProvider。

class ChangeNotifierProvider extends StatefulWidget {
  final Widget child;
  final CountModel model;
  ...
}

class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
  _ChangeNotifierProviderState();

  _update() {
    setState(() => {});
  }
  
  @override
  void initState() {
    super.initState();
    widget.model.addListener(_update);
  }
  ....
  
  ///在此處build內debug
  @override
  Widget build(BuildContext context) {
    return CountProvider(
      model: widget.model,
      child: widget.child,
    );
  }
}
複製程式碼

但點選increment按鈕時,

--> ChangeNotifier._update()
--> ChangeNotifierProvider.setState()
--> ...
--> Element.rebuild() 
--> ComponentElement.performBuild()
--> StatefulElement.build()/updateChild
--> ChangeNotifierProvider.build(BuildContext)
複製程式碼

我們進入ComponentElement

class ComponentElement{
    
 ///ChangeNotifierProvider對應的ComponentElement performRebuild
  @override
  void performRebuild() {
  ///呼叫_ChangeNotifierProviderState.build(),生成新的CountProvider
    Widget built= build();
    _child = updateChild(_child, built, slot);
  }
}

  ///此時 Element是InheritedElement,newWidget是我們新構建CountProvider
  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ///newWidget為null,則讓child失活不可見,並不會清除。
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    if (child != null) {
      ///新老CountProvider是同一個物件,處理下slot,及可見性
      ///這個地方也告訴我們CountProvider的child每次都new一個物件,也會全部rebuilt
      if (child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        ///*只有這裡才會進入我們的InheritedElement
        child.update(newWidget);
        return child;
      }
      deactivateChild(child);
    }
    return inflateWidget(newWidget, newSlot);
  }
複製程式碼

接著上述的child.update(newWidget),先進入了ProxyElement

class ProxyElement{
    @override
    void update(ProxyWidget newWidget) {
        final ProxyWidget oldWidget = widget;
        super.update(newWidget);
        assert(widget == newWidget);
        updated(oldWidget);
        _dirty = true;
        rebuild();
    }
    @protected
    void updated(covariant ProxyWidget oldWidget) {
        notifyClients(oldWidget);
  }
}
複製程式碼

InheritedElement過載了updated()方法。同時實現了notifyClients()我們發現這個地方留了一手, 正是需要我們實現的updateShouldNotify

class InheritedElement{
    @override
    void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
    }
    @override
    void notifyClients(InheritedWidget oldWidget) {
        ///_dependents中儲存的都是
        ///通過dependOnInheritedWidgetOfExactType產生依賴的子孫Element
        for (Element dependent in _dependents.keys) {
            notifyDependent(oldWidget, dependent);
        }
    }
    @protected
    void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
        ///最終呼叫dependent的markNeedsBuild();
        dependent.didChangeDependencies();
    }
}

複製程式碼

到這裡我們算是把方法的呼叫棧說明白了。也對InheritedWidget的內部原理大概有了認識。 其實就是觀察者模式:註冊監聽,通知更新的邏輯。還有更多細節還在繼續學習中。

這些內容,足夠讓我們進入到Provider的使用過程中。

Provider

這個地方我們挑常用的ChangeNotifierProvider分析 回顧程式碼

class CountModel extends ChangeNotifier {
 int count;
 CountModel(this.count);
 void increment() {
   count++;
   notifyListeners();
 }
}

ChangeNotifierProvider.value(
       value: _countModel,
         child: Column(
           children: <Widget>[
             Consumer<CountModel>(
               builder: (contextC, model, child) {
                 return Text("計數:${model.count}(有依賴情況)");
               },
             ),
             Builder(builder: (context1) {
               return Text(
                   "計數:${Provider.of<CountModel>(context1, listen: false).count}(無依賴情況)");
             }),
             RaisedButton(
                 child: Text("increment"),
                 onPressed: () => _countModel.increment()),
           ],
)

複製程式碼

相關文章