Flutter进阶篇-Local Key和Global Key

简介:
key是widget、element和semanticsNode的唯一标识,同一个parent下的所有element的key不能重复,但是在特定条件下可以在不同parent下使用相同的key,比如page1和page2都可以使用ValueKey(1) 。

常用key的UML关系图如上,整体上key分为两大类-LocalKey和GlobalKey,这两个key都是抽象类,LocalKey的实现类有 ValueKey、ObjectKey和UniqueKey,GlobalKey实现类有LabeledGlobalKey和GlobalObjectKey。

 

@immutable
abstract class Key {
  const factory Key(String value) = ValueKey<String>;


  @protected
  const Key.empty();
}

 Key是所有类型key的基类,内部实现了一个工厂构造函数,默认创建String类型的ValueKey。内部还实现了一个empty的构造函数,主要是给子类用的。

 外观是跟着Widget(在Flutter框架中Widget是immutable 不可改变的,一旦建立在运行的时候就不可改变它的值)走,State状态(在运行的时候可以改变,每次setState(() {})通知直接换掉旧的Widget而不是修改旧的Widget)是跟着Element Tree Instance 实例对象走,Element负责管理状态

一、Local Key

局部key,包含三种类型的key:ValueKeyObjectKeyUniqueKey

  1. ValueKey : 比较两个ValueKey的值是否相等,比较的是Value值是否相等,(有点类似于java中的equals),比如1,2,3。用在学生的id等唯一信息上。
  2. ObjectKey :以Object对象作为Key,比较两个ValueKey是否相等,比较的是Instance值是否相等,有点类似于c++指针的概念,对比的是在内存中是不是同一个Object,(有点类似于java中的== 恒等于号),通过指针地址来对比。new一个对象,对象的指针地址进行对比。
  3. UniqueKey:UniqueKey唯一的,可以保证Key的唯一性。使用之后就不存在Element的复用了,因为每次都是不同的。如果实在没有唯一标识了,可以使用UniqueKey来标识。
abstract class LocalKey extends Key {
  const LocalKey() : super.empty();
}
class ObjectKey extends LocalKey {
  /// Creates a key that uses [identical] on [value] for its [operator==].
  const ObjectKey(this.value); 
//如果是一个不复杂的String类型的Key时用identical的方式比较内存中两个指针是否相同

  /// The object whose identity is used by this key's [operator==].
  final Object? value;

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    return other is ObjectKey
        && identical(other.value, value);
  }
class Student {
  final String name;
  final String age;

  const Student({required this.name, required this.age});

  @override
  bool operator ==(covariant Student other) {
    //identical 检测在内存中是不是同一个东西
    if (identical(this, other)) return true;

    return
      other.name == name &&
      other.age == age;
  }

  @override
  int get hashCode => name.hashCode ^ age.hashCode;
}

二、Global Key

GlobalKey是全局唯一的,其默认实现是LabeledGlobalKey,所以每次创建的都是新GlobalKey。所有的GlobalKey都保存在BuildOwner类中的一个map里,此map的key为GlobalKey,此map的value则为GlobalKey关联的element。

对于GlobalKey,需要知道如下几点:

当拥有GlobalKey的widget从tree的一个位置上移动到另一个位置时,需要reparent它的子树。为了reparent它的子树,必须在一个动画帧里完成从旧位置移动到新位置的操作。
上面说到的reparent操作是昂贵的,因为要调用所有相关联的State和所有子节点的deactive方法,并且所有依赖InheritedWidget的widget去重建。
不要在build方法里创建GlobalKey,性能肯定不好,而且也容易出现意想不到的异常,比如子树里的GestureDetector可能会由于每次build时重新创建GlobalKey而无法继续追踪手势事件。
GlobalKey提供了访问其关联的Element和State的方法。

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
  ///这里的debugLabel仅仅为了debug时使用
  factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);


  ///给子类使用的
  const GlobalKey.constructor() : super.empty();


  Element? get _currentElement => WidgetsBinding.instance!.buildOwner!._globalKeyRegistry[this];


  BuildContext? get currentContext => _currentElement;


  Widget? get currentWidget => _currentElement?.widget;


  T? get currentState {
    final Element? element = _currentElement;
    if (element is StatefulElement) {
      final StatefulElement statefulElement = element;
      final State state = statefulElement.state;
      if (state is T)
        return state;
    }
    return null;
 

其和Key类差不多,也有一个工厂构造函数,默认创建的是LabeledGlobalKey,其构造函数的debugLabel仅仅是为了debug时使用,并不会用来标识element。

如何获取其关联的element?从源码来看,其直接访问的是BuildOwner里用来保存GlobalKey和Element对应关系的map。获取到了其关联的element,那么就能获取到其对应的widget以及state,详细的可以看上面的源码。

需要注意的是其并没有重写和hashCode方法,构造函数也没有被const修饰,这也就使LabeledGlobalKey天然就是全局唯一的

LabeledGlobalKey

这是GlobalKey的默认实现,内部仅有一个debugLabel属性,其他的也没啥。

class LabeledGlobalKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
  // ignore: prefer_const_constructors_in_immutables , never use const for this class
  LabeledGlobalKey(this._debugLabel) : super.constructor();


  final String? _debugLabel;
}

GlobalObjectKey

class GlobalObjectKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
  const GlobalObjectKey(this.value) : super.constructor();


  final Object value;


  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    return other is GlobalObjectKey<T>
        && identical(other.value, value);
  }


  @override
  int get hashCode => identityHashCode(value);

特殊的GlobalKey,重写了==和hashCode方法,内部维护了一个Object对象,通过判断此Object是否指向同一块内存地址来判断两个GlobalObjectKey是否相等。

GlobalKey被要求全局唯一,其默认实现LabeledGloalKey因为其并没有重写==和hashCode方法,也不支持const构造函数,所以天然是全局唯一的。但是GlobalObjectKey不然,如果有两个或者多个地方使用到了拥有同一个Object的GlobalObjectKey,那么就不能保证其全局唯一性,造成程序出错。此时,可以继承GlobalObjectKey,实现一个private的内部类,比如:

class _MyGlobalObjectKey extends GlobalObjectKey {
  const _MyGlobalObjectKey(Object value) : super(value);
}

总结:


Flutter里的key分为两类:

  1. LocalKey,实现类有ValueKey、ObjectKey、UniqueKey;
  2. GlobalKey,实现类有LabeledGlobalKey、GlobalObjectKey。


Key是所有keys类的基类,其默认实现是String类型的ValueKey。


相同parent下的key是不能一样的,比如不能再同一个page里使用VlaueKey(1),但是不同parent下是可以存在一样的key的,比如在两个界面里都使用ValueKey(1)。
UniqueKey只和自己相等,其并没有重写==和hashCode方法,也没有const修饰的构造函数。当调用Element的updateChild方法时,Widget.canUpdate肯定返回false,所以如果你想让widget每次都去创建新的element而不复用old element,那么就给此widget使用UniqueKey。


GlobalKey的默认实现是LabeledGlobalKey,其没有实现==和hashCode方法,也没有const修饰的构造函数,所以肯定能保证其全局唯一性。
所有的GlobalKey都保存在BuildOwner类中,其内部维护了一个map用来保存GlobalKey与其对应的Element。
GlobalObjectKey是特殊的GlobalKey,内部维护了一个Object属性,并实现了== 和hashCode方法,通过判断runtimeType以及Object属性是否一致来判断两个GlobalObjectKey是否相等。
使用GlobalObjectKey时,为了保证GlobalObjectKey的全局唯一性,最佳实践是继承自GlobalObjectKey实现一个private的内部类,可以有效避免多人开发时可能造成的GlobalObjectKey冲突的问题。

猜你喜欢

转载自blog.csdn.net/RreamigOfGirls/article/details/131121261
key