InheritWidget原理解析

o動感超人o發表於2020-06-07

先上程式碼

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter InheritWidget',
      home: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: TestWidget(),
        ),
      ),
    );
  }
}

class TestWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return TestWidgetState();
  }
}

class TestWidgetState extends State<TestWidget> {
  String text = "init";

  @override
  Widget build(BuildContext context) {
    return CustomInheritedWidget(
      text: text, //1
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          InheritedWidgetChild(),
          CustomRaisedButton(
            onPressed: () {
              setState(() {
                text = "modify"; //2
              });
            },
            child: Text("修改值"),
          )
        ],
      ),
    );
  }
}

class CustomRaisedButton extends RaisedButton {
  const CustomRaisedButton({
    @required VoidCallback onPressed,
    Widget child,
  }) : super(onPressed: onPressed, child: child);
  @override
  Widget build(BuildContext context) {
    print("CustomRaisedButton build");
    return super.build(context);
  }
}

class InheritedWidgetChild extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return InheritedWidgetChildState();
  }
}

class InheritedWidgetChildState extends State<InheritedWidgetChild> {
  @override
  Widget build(BuildContext context) {
    print("InheritedWidgetChildState build");
    final CustomInheritedWidget widget = context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>();
    return Text(widget.text);
  }

  @override
  void didChangeDependencies() {
    print("InheritedWidgetChildState didChangeDependencies");
    super.didChangeDependencies();
  }
}

class CustomInheritedWidget extends InheritedWidget {
  CustomInheritedWidget({Key key, this.text, Widget child}) : super(key: key, child: child);

  final String text;

  @override
  bool updateShouldNotify(CustomInheritedWidget oldWidget) {
    return oldWidget.text != text;
  }
}
複製程式碼

InheritWidget原理解析

InheritWidget原理解析
在註釋1,text預設值為init,當點選按鈕的時候,修改text值為modify並重新整理頁面,我們會發現InheritedWidgetChild呼叫了didChangeDependencies()方法,我們來分析一下,點選按鈕的時候都發生了什麼。

_inheritedWidgets的傳遞

首先說一下流程,在Elementactivatemount的時候,會呼叫_updateInheritance方法,把自己的_inheritedWidgets指向_parent_inheritedWidgets,而InheritedElement覆蓋了該方法,程式碼分別如下

//Element
  void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
複製程式碼
//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;
 }
複製程式碼

可以看到InheritedElement拷貝了_parent_inheritedWidgets並把自己的 widgetruntimeType作為keythis作為value儲存在了自己的inheritedWidgets屬性裡,所以最開始的程式碼的UI樹是這樣的

TestWidget

-CustomInheritedWidget

--Column

---InheritedWidgetChild

---RaisedButton

CustomInheritedWidget拷貝了TestWidget_inheritedWidgets到自己的_inheritedWidgets並將自己儲存其中,然後Column_inheritedWidgets直接指向了CustomInheritedWidget_inheritedWidgets,然後InheritedWidgetChildRaisedButton_inheritedWidgets直接指向了Column_inheritedWidgets

InheritedElement的查詢

InheritedWidgetChild裡呼叫context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>()方法可以查詢到CustomInheritedWidget,程式碼如下:

//Element
 @override
 T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
   assert(_debugCheckStateIsActiveForAncestorLookup());
   final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
   if (ancestor != null) {
     assert(ancestor is InheritedElement);
     return dependOnInheritedElement(ancestor, aspect: aspect) as T;
   }
   _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;
 }
複製程式碼
//InheritedElement
@protected
void updateDependencies(Element dependent, Object aspect) {
  setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object value) {
  _dependents[dependent] = value;
}
複製程式碼

我們可以看到,在dependOnInheritedWidgetOfExactType這個方法裡,查詢到泛型Widget對應的InheritedElement,儲存到ancestor變數裡,然後在自己(InheritedWidgetChild)的_dependencies集合中新增ancestor,並把自己儲存到找到ancestor_dependents集合裡,這樣在InheritedElement更新內部資料的時候,就可以通知到所有依賴了本InheritedElementWidget。同時InheritedWidgetChild_dependencies集合中新增了ancestor,所以在InheritedWidgetChild銷燬的時候,可以把自己(InheritedWidgetChild)從ancestor_dependents集合中移除,避免不必要的更新和記憶體洩漏。

總結

總結一下上面的內容,在Elementactivatemount的時候,會呼叫_updateInheritance方法,把自己的_inheritedWidgets指向_parent_inheritedWidgets,而InheritedElement覆蓋了該方法,將自己儲存到了_inheritedWidgets中,所以在層層向下傳遞的時候,_inheritedWidgets就包含了傳遞過程中所有的InheritedElement,然後在通過dependOnInheritedWidgetOfExactType方法獲取InheritedElement的時候,方法呼叫方把自己儲存到了InheritedElement_dependencies集合中,並且方法呼叫方在自己的_dependencies集合中也新增了自己所依賴的InheritedElement的引用,在方法呼叫方銷燬的時候,把自己從自己所依賴的InheritedElement_dependencies集合中刪除自己除,避免不必要的更新和記憶體洩漏。

思考題

  1. Elementactivatemount方法在什麼時候被呼叫?
  2. CustomInheritedWidget改變內部的資料的時候,為什麼InheritedWidgetChild會呼叫didChangeDependencies()