likes
comments
collection
share

Flutter架构初探(二)

作者站长头像
站长
· 阅读数 4
  • Widget
  • Element
  • BuildContext 等基础组件,这次主要介绍
  • 构造函数
  • factory关键字
  • extension关键字
  • InheritWidget 的含义和基础用法

构造函数

dart中如果不提供构造函数则默认提供无参构造函数,构造函数有两种

  • 普通构造函数
  • 命名构造函数

普通构造函数

class Point{
  num x,y;
  
  Point(num x,num y){
    // 可以认为在函数刚开始实例对象就已经创建成功了
    // 所以可以在下面👇使用this关键字获取当前实例对象
    this.x = x ;
    this.y = y ; 
  }
}

还可以这样写,代码简化了一些

class Point{
  num x,y;
  Point(this.x,this.y);
}

还可以这样写

class Point{
  num? x,y;
  
  //冒号后面可以用this说明此时实例对象已经创建成功了
  Point(num a,num b):this.x=a,y=b;
}

还可以包含初始化列表

class Point{
  num? x,y;
  Point(num a,num b):x=a,y=b{
    x=b;
    y=a;
  }
}

Point p = Point(1,2);
print('x=${p.x} '+'y=${p.y}');

//执行结果
x=2 y=1

可以看到:后面的代码先执行{}里面的代码后执行

注意一个类里面最多只能有一个普通构造函数

class Point{
  num? x,y;
  Point(num x);
  Point(num y); //这里会报错
}

命名构造函数

在一个类里面普通构造函数不能定义多个,如果我们想实现多个构造函数怎么办呢???使用命名构造函数!

class Point{
  num? x,y;
  Point(num x);
  Point.initY(num y); //定义一个命名构造函数
}

命名构造函数也有上面的几种写法,相比于普通构造函数,命名构造函数更加直观,代码可读性更高,例如我们平时使用频率很高的

Point.fromJson(Map<String, num> json){
    ...
}

还可以实现构造函数重定向

class Rect{
  num x ,y , wid , hei ;
  Rect(this.x , this.y ,this.wid ,this.hei);
  Rect.withSize(num width,num height) : this(0 , 0 , width ,height) ;
}

通过上面我们介绍的构造函数相关的内容我们引出下一节的内容,就是factory关键字

factory

Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype.

当你使用factory关键词时,你能控制在使用构造函数时,并不总是创建一个新的该类的对象,比如它可能会从缓存中返回一个已有的实例,或者是返回子类的实例。

有三个使用场景

    1. A factory constructor can check if it has a prepared reusable instance in an internal cache and return this instance or otherwise create a new one. 避免创建过多的重复实例,如果已创建该实例,则从缓存中拿出来。其实也是一种单例模式,只不过将容器搞成了单例而不是某一个实例对象。
    1. You can for example have an abstract class A (which can't be instantiated) with a factory constructor that returns an instance of a concrete subclass of A depending for example on the arguments passed to the factory constructor. 调用子类的构造函数(工厂模式 factory pattern)
    1. singleton pattern 实现单例模式
单例模式
class Singleton {
    //我们看到普通的构造函数的写法不许有返回值
    Singleton();
}

//执行代码
var s1 = Singleton();
var s2 = Singleton();

//`identical`会对两个对象的地址进行比较,相同返回true,
// 等同于 == ,好处是如果重写了==,那用`identical`会更方便。
print(identical(s1, s2));

//执行结果
false

每次执行构造函数都会重新创建一个对象

class Singleton {
  // 私有命名构造函数
  Singleton._();
  // 创建静态变量_singleton
  static final Singleton _singleton = Singleton._();

  factory Singleton(){
    // 注意这里不能使用this关键字
    return _singleton;
  }
}

//执行代码
var s1 = Singleton();
var s2 = Singleton();
print(identical(s1, s2));

//执行结果
true

当你需要构造函数不是每次都创建一个新的对象时,使用factory关键字

缓存
class Logger {
  final String name;
  // 缓存已创建的对象
  static final Map<String, Logger> _cache = <String, Logger>{}; 

  factory Logger(String name) {
  // 不理解putIfAbsent可以查看文末的简述
    return _cache.putIfAbsent(  
        name, () => Logger._internal(name));
  }

// 私有的构造函数
  Logger._internal(this.name){
    print("生成新实例:$name");
  }
}

var p1 = new Logger("1");
var p2 = new Logger('22');
var p3 = new Logger('1');// 相同对象直接访问缓存

print('p1和p2是否相同:'+identical(p1,p2).toString());
print('p1和p3是否相同:'+identical(p1,p3).toString());

// 执行结果
生成新实例:1
生成新实例:22
p1和p2是否相同:false
p1和p3是否相同:true
工厂模式
abstract class Animal {
  String name = '';
  void getNoise();
  factory Animal(String type,String name) {
    switch(type) {
      case "猫":
        return new Cat(name);
      case "狗":
        return new Dog(name);
      default:
        throw "你输入的:'$type' 不知道是个什么品种";
    }
  }
}

class Cat implements Animal {
  String name;
  Cat(this.name);
  @override
  void getNoise() {
    print("${this.name}: 喵~~");
  }
}

class Dog implements Animal {
  String name;
  Dog(this.name);
  @override
  void getNoise() {
    print("${this.name}: 汪汪~");
  }
}

var cat = new Animal("猫","狸花猫");
var dog = new Animal("狗","旺财");
cat.getNoise();
dog.getNoise();

// 执行结果
狸花猫: 喵~~
旺财: 汪汪~

通过这个例子我们知道返回的确实是子类的实例对象,因为父类没有实现getNoise方法

通过上面三个例子我们简单思考🤔一下factory这个关键字究竟起到了什么作用??

  • factory修饰的构造函数必须得有对应类型或者子类的实例对象作为返回值
  • 如果要实现单例模式,那么返回值的唯一性需要开发者自己来保证 所以factory只是表明他有那个带返回值的特性且死死的绑定了构造函数,如果我们实现下面的代码
class SingleInstance {
  static final SingleInstance _singleInstance = SingleInstance._internal();

  factory SingleInstance(){
    return SingleInstance._internal();
  }

  SingleInstance._internal();
}

那么此时factory球用都没有!!!!

前两部分有承接关系,第三部分extension跟前两节没啥关系

extension

我在研究Provider过程中发现有这种用法

context.read<T>
context.watch<T>

点进去发现是通过extension实现的

extension ReadContext on BuildContext {

    T read<T>() {
      return Provider.of<T>(this, listen: false);
    }
}

注意:这个关键字只有在 Dart 版本 2.7 及其以上才支持。

extensionOC中很常用,但是dart中的extension更类似于OC中的category,是在不改变某一个类本身的情况下给其添加一些功能,例如我们想添加一个通过字符串色值设置颜色的属性,想给导航控制器设置一个全屏幕测滑返回的手势等等,在Dart中我们同样可以实现类似功能。

例一

想给String添加一个转换为Color的方法

extension StringExtension1 on String {
  //字符转换成Color对象
  toColor() {
    var hexColor = this.replaceAll("#", "");
    if (hexColor.length == 6) {
      hexColor = "FF" + hexColor;
    }
    if (hexColor.length == 8) {
      return Color(int.parse("0x$hexColor"));
    }
  }
}

使用方法

Container(color: '#333333'.toColor())

但是如果声明为dynamic的变量使用起来会有问题

dynamic c = '#ffffff';
Container(color: c.toColor())

报错

NoSuchMethodError: Class 'String' has no instance method 'toColor'.
Receiver: "#ffffff"
Tried calling: toColor()

例二

extension DateTimeExtension on DateTime {
  // 还可以传入时间格式 yyyy-MM......
  toFormatString() {
    DateFormat dateFormat = new DateFormat("yyyy-MM-dd HH:mm:ss");
    return dateFormat.format(this);
  }
}

OC中我们可以给某个类添加方法,但是没有办法添加成员变量,这是因为一个实例对象在内存中的布局已经确定了,而通过类扩展添加成员变量会改变这种内存布局,假如类的布局规则改变了,那么之前创建的实例对象他就不认了,这是很不好的设计。那么我盲猜dart中也不可以通过extension添加成员变量

Flutter架构初探(二)

果然添加成员变量报错了!!!但是在OC中可以通过关联对象的方式实现类似添加成员变量的操作,在dart中我还不知道如何实现。

InheritWidget

先看一个小问题

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

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MyAppState();
  }
}


class MyAppState extends State<MyApp>{

  GlobalKey _globalKey = GlobalKey();

  int count = 0;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        key: _globalKey,
        body: CustomerInheritedWidget(
          count: count,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                MaterialButton(
                    onPressed: () {
                      setState(() {
                        count++;
                      });
                    },
                    child: Text('模拟添加购物车操作'+count.toString()+'次',style: TextStyle(color: Colors.black,fontSize: 20),)),
                MaterialButton(
                    onPressed: () {
                      Navigator.of(_globalKey.currentContext).push(
                          MaterialPageRoute(builder: (context) => SecondPage()));
                    },
                    child: Text('跳转到购物车页面',style: TextStyle(color: Colors.black,fontSize: 20),))
              ],
            ),
          ),
        ),
      ),
    );
  }
}



class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text('购物车中共有'+CustomerInheritedWidget.of(context).count.toString()+'件商品'),
      ),
    );
  }
}


class CustomerInheritedWidget<T> extends InheritedWidget{
  final int count;
  
  CustomerInheritedWidget({Key key,this.count,Widget child}):super(key: key,child: child);
  
  static CustomerInheritedWidget<T> of<T>(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<CustomerInheritedWidget<T>>();
  }

  @override
  bool updateShouldNotify(covariant CustomerInheritedWidget oldWidget) {
    return oldWidget.count != count;
  }

}

Flutter架构初探(二)

点击跳转按钮SecondPage直接报错

The getter 'count' was called on null.
Receiver: null
Tried calling: count

猜测应该是

CustomerInheritedWidget.of(context).count

这行代码没有获取到CustomerInheritedWidget对象,相当于null.count,我们分析一下这棵UI树

Flutter架构初探(二)

为什么在SecondPage获取不到CustomerInheritedWidget对象呢??再来看一个正确的小例子

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter InheritWidget',
      home: Scaffold(
        appBar: AppBar(),
        body: GestureDetector(
          onTap: (){
            Navigator.of(context);
          },
          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 MaterialButton {
  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;
  }
}

这是一个简单小例子,点击按钮数字+1

Flutter架构初探(二)

此时这颗UI树是这样的

Flutter架构初探(二)

按钮是如何触发数据的修改的

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

  @override
  Widget build(BuildContext context) {
    print("BodyWidgetState build:$hashCode");
    return CustomInheritedWidget(
      data: count, //将数据赋值给InheritedWidget组件
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          DependOnInheritedWidget<int>(),
          Builder(builder: (context) {
            return CustomRaisedButton(
              onPressed: () {
                setState(() {//通过setState就可以实现数据刷新
                  count++;
                });
              },
              child: Text("数字+1"),
            );
          })
        ],
      ),
    );
  }
}

我们看到这里就是简单的将创建的数据赋值给InheritedWidget组件,然后在setState中修改数据就可以了。

如何获取到InheritedWidget实例

static CustomInheritedWidget<T> of<T>(BuildContext context) {
  return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();
}

是通过dependOnInheritedWidgetOfExactType方法获取到的,我们后面分析

是否刷新页面的判断条件

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

默认的这种写法

  • 如果是值类型则判断值是否相同
  • 如果是引用类型则判断对象地址是否相同 当然我们也可以根据需要随意修改这个判断条件
@override
bool updateShouldNotify(CustomInheritedWidget oldWidget) {
  return oldWidget.data.xxx != data.xxx;
}

原理解析

要想了解原理我们首先需要了解以下几个概念

  • mount() 是首次把element添加到element Tree后,然后将当前element状态变为active状态,active状态表示已经准备好了可以被加载了。
  • activate() 是把deactivate element重新标记为active状态
  • _inheritedWidgets 存储当前UI树中所有的InheritedWidget结点,子节点的_inheritedWidgets指针默认指向父节点的_inheritedWidgets,但是InheritedWidget中对此做了一些修改,我们下面会讲到
Element源码

看源码之前首先确定我们要解决的问题是什么??

// 1、这行代码是怎么查找到InheritedElement对象的?
context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();

// 2、组件为什么可以监听到InheritedElement数据的改变?
CustomInheritedWidget.of<T>(context).data
abstract class Element extends DiagnosticableTree implements BuildContext {

    Element? _parent;

    //这里我们看到了identical其实也是==的符号重定义
    @nonVirtual
    @override
    // ignore: avoid_equals_and_hash_code_on_mutable_classes
    bool operator ==(Object other) => identical(this, other);
    

    @mustCallSuper
    void mount(Element? parent, Object? newSlot) {
      ...
      _parent = parent;
      ...
      _updateInheritance();
    }
    
    @mustCallSuper
    void activate() {
        ...
        _dependencies?.clear();
        ...
        _updateInheritance();
        ...
    }
    
    void _updateInheritance() {
      assert(_lifecycleState == _ElementLifecycle.active);
      _inheritedWidgets = _parent?._inheritedWidgets;
    }
    

    @mustCallSuper
    void deactivate() {
        if (_dependencies != null && _dependencies!.isNotEmpty) {
          for (final InheritedElement dependency in _dependencies!)
            dependency._dependents.remove(this);
        }
    }
    
    Map<Type, InheritedElement>? _inheritedWidgets;
    Set<InheritedElement>? _dependencies;

    @override
    InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
      assert(ancestor != null);
      _dependencies ??= HashSet<InheritedElement>();
      _dependencies!.add(ancestor);
      ancestor.updateDependencies(this, aspect);
      return ancestor.widget;
    }

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

Flutter架构初探(二)

查找InheritedElement的流程就是按照_inheritedWidgets指针的流程向上查找的

Flutter架构初探(二)

那些节点需要监听InheritedElement数据的改变就将InheritedElement添加到那些节点的_dependencies集合中

在这里我们只是看到了_inheritedWidgets的查找流程,但是从这里我们并没有看到_inheritedWidgets中的值是那里来的,继续看源码

InheritedElement源码
class InheritedElement extends ProxyElement {
    // InheritedElement定义了一个_dependents
    // 这里要和Element中的_dependencies分清楚
    final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
    
    // 这里覆盖了父类的方法
    @override
    void _updateInheritance() {
      assert(_lifecycleState == _ElementLifecycle.active);
      final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
      
      // 这里_inheritedWidgets不再指向父类的_inheritedWidgets
      // 将父类中的_inheritedWidgets中的值拷贝了一份过来
      if (incomingWidgets != null)
        _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
      else
        _inheritedWidgets = HashMap<Type, InheritedElement>();
        
      // 然后将自己添加进去
      _inheritedWidgets![widget.runtimeType] = this;
    }
    
    // 下面这三个方法都是对_dependents的操作
    @protected
    Object? getDependencies(Element dependent) {
      return _dependents[dependent];
    }
    
    @protected
    void setDependencies(Element dependent, Object? value) {
      _dependents[dependent] = value;
    }

    @protected
    void updateDependencies(Element dependent, Object? aspect) {
      setDependencies(dependent, null);
    }

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

    @override
    void notifyClients(InheritedWidget oldWidget) {
      for (final Element dependent in _dependents.keys) {
        notifyDependent(oldWidget, dependent);
      }
    }
    
    @protected
    void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
      dependent.didChangeDependencies();
    }
}

Flutter架构初探(二)

先分析一下_inheritedWidgets的传递过程,在Element执行activatemount的时候,会调用_updateInheritance方法,把自己的_inheritedWidgets指向_parent_inheritedWidgets,而InheritedElement覆盖了该方法,将_parent_inheritedWidgets拷贝了一份过来,并且将自己也添加了进去,代码如下

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

这了就回答了上面的第一个问题

// 1、这行代码是怎么查找到InheritedElement对象的?
context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();

沿着_inheritedWidgets指针向上查找,直到节点是InheritedElement节点,InheritedElement节点拷贝了父节点中的_inheritedWidgets并将自己添加进去了,所以可以查到。

Flutter架构初探(二)

update(...)方法中判断了是否需要执行super.update(...),先看一下父类中的update

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

    // Element
    void updated(covariant ProxyWidget oldWidget) {
      notifyClients(oldWidget);
    }
    
    // InheritedElement
    @override
    void notifyClients(InheritedWidget oldWidget) {
      for (final Element dependent in _dependents.keys) {
        notifyDependent(oldWidget, dependent);
      }
    }
    
    // InheritedElement
    @protected
    void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
      dependent.didChangeDependencies();
    }

这就回答了第二个问题

// 2、组件为什么可以监听到InheritedElement数据的改变?
CustomInheritedWidget.of<T>(context).data

InheritedElement中数据更新的时候会遍历InheritedElement中的_denpendents集合,并执行集合中所有节点的didChangeDependencies()函数,之后就是刷新的流程了 (吧?)

关于内存泄漏的疑虑?会不会循环引用?

Flutter架构初探(二)Element中有如下代码

@mustCallSuper
void deactivate() {
    if (_dependencies != null && _dependencies!.isNotEmpty) {
      for (final InheritedElement dependency in _dependencies!)
        dependency._dependents.remove(this);
    }
    _inheritedWidgets = null;
}


@mustCallSuper
void unmount() {
    _dependencies = null;
}

unmount()时会清空_dependencies。 在deactivate()时会清空InheritedElement中的_dependents。所以不会造成内存泄漏问题

InheritedWidget应用很广泛,例如ThemeProviderMediaQuery等等。这里只是简单介绍了一点儿原理,很多细节都还没有深入了解,对于页面刷新的具体流程也没有研究。

参考文章