InheritWidget原理解析

1,342

先上代码

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: BodyWidget(),
        ),
      ),
    );
  }
}

class BodyWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return BodyWidgetState();
  }
}

class BodyWidgetState extends State<BodyWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    print("BodyWidgetState build:$hashCode");
    return CustomInheritedWidget(
      data: count,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          DependOnInheritedWidget<int>(),
          Builder(builder: (context) {
            return CustomRaisedButton(
              onPressed: () {
                setState(() {
                  count++;
                });
              },
              child: Text("数字+1"),
            );
          })
        ],
      ),
    );
  }
}

class CustomRaisedButton extends RaisedButton {
  const CustomRaisedButton({
    @required VoidCallback onPressed,
    Widget child,
  }) : super(onPressed: onPressed, child: child);

  @override
  Widget build(BuildContext context) {
    print("CustomRaisedButton build:$hashCode");
    return super.build(context);
  }
}

class DependOnInheritedWidget<T> extends StatefulWidget {

  DependOnInheritedWidget(){
    print("DependOnInheritedWidget:$hashCode");
  }

  @override
  State<StatefulWidget> createState() {
    return DependOnInheritedWidgetState<T>();
  }

}

class DependOnInheritedWidgetState<T> extends State<DependOnInheritedWidget> {
  @override
  Widget build(BuildContext context) {
    print("DependOnInheritedWidgetState build:$hashCode");
    return Text(CustomInheritedWidget.of<T>(context).data.toString());
  }

  @override
  void didChangeDependencies() {
    print("DependOnInheritedWidgetState didChangeDependencies:$hashCode");
    super.didChangeDependencies();
  }
}

class CustomInheritedWidget<T> extends InheritedWidget {
  CustomInheritedWidget({Key key, this.data, Widget child}) : super(key: key, child: child);

  final T data;

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static CustomInheritedWidget<T> of<T>(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();
  }

  @override
  bool updateShouldNotify(CustomInheritedWidget oldWidget) {
    return oldWidget.data != data;
  }
}

刚开始时,文本为0,当点击按钮的时候,数值+1并刷新页面,这时后台新增打印如下:

I/flutter (24452): BodyWidgetState build:293287865
I/flutter (24452): DependOnInheritedWidget:698685804
I/flutter (24452): DependOnInheritedWidgetState didChangeDependencies:185018106
I/flutter (24452): DependOnInheritedWidgetState build:185018106
I/flutter (24452): CustomRaisedButton build:266734984

通过打印的日志我们会发现DependOnInheritedWidgetState调用了didChangeDependencies()方法,我们来分析一下,点击按钮的时候都发生了什么。

_inheritedWidgets的传递

首先说一下流程,在Elementactivatemount的时候,会调用_updateInheritance方法,把自己的_inheritedWidgets指向_parent_inheritedWidgets,而InheritedElement覆盖了该方法,代码分别如下

mount()是首次把element添加到element Tree后,然后 element变为active状态。activate()是把deactivate element重新标记为active状态。 总之是从无到有或者从不可见到可见。

//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树是这样的

BodyWidget

-CustomInheritedWidget

--Column

---DependOnInheritedWidget

---Builder

----CustomRaisedButton

CustomInheritedWidget拷贝了BodyWidget_inheritedWidgets到自己的_inheritedWidgets并将自己保存其中,然后ColumnDependOnInheritedWidgetBuilderCustomRaisedButton_inheritedWidgets直接指向了CustomInheritedWidget_inheritedWidgets

InheritedElement的查找

DependOnInheritedWidgetState里调用CustomInheritedWidget.of方法,of方法调用context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>()可以查找到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变量里,然后在自己(DependOnInheritedWidget)的_dependencies集合中添加ancestor,并把自己保存到找到ancestor_dependents集合里,这样在InheritedElement更新内部数据的时候,就可以通知到所有依赖了本InheritedElementWidget。同时DependOnInheritedWidget_dependencies集合中添加了ancestor,所以在DependOnInheritedWidget销毁的时候,可以把自己(DependOnInheritedWidget)从ancestor_dependents集合中移除,避免不必要的更新和内存泄漏。

总结

总结一下上面的内容,在Elementactivatemount的时候,会调用_updateInheritance方法,把自己的_inheritedWidgets指向_parent_inheritedWidgets,而InheritedElement覆盖了该方法,将自己保存到了_inheritedWidgets中,所以在层层向下传递的时候,_inheritedWidgets就包含了传递过程中所有的InheritedElement,然后在通过dependOnInheritedWidgetOfExactType方法获取InheritedElement的时候,方法调用方把自己保存到了InheritedElement_dependencies集合中,并且方法调用方在自己的_dependencies集合中也添加了自己所依赖的InheritedElement的引用,在方法调用方销毁的时候,把自己从自己所依赖的InheritedElement_dependencies集合中删除自己,避免不必要的更新和内存泄漏。

思考题

  1. Elementactivatemount方法在什么时候被调用?
  2. CustomInheritedWidget改变内部的数据的时候,为什么DependOnInheritedWidgetState会调用didChangeDependencies()