乐趣区

关于flutter:Flutter三棵树系列之详解各种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。

Key

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


  @protected
  const Key.empty();}

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

LocalKey

abstract class LocalKey extends Key {const LocalKey() : super.empty();}

LocalKey 没有理论作用,次要是用来辨别 GlobalKey 的,其具体的实现类有 ValueKey、ObjectKey、UniqueKey。

ValueKey

class ValueKey<T> extends LocalKey {const ValueKey(this.value);


  final T value;


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


  @override
  int get hashCode => hashValues(runtimeType, value);

外部保护了泛型类型的 value 属性,并实现了 == 和 hashCode 办法。只有两个 ValueKey 的 value 属性相等,那么就认为两个 Key 相等。

ObjectKey

class ObjectKey extends LocalKey {const ObjectKey(this.value);


  final Object? value;


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


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

ObjectKey 是继承自 LocalKey 的,能够将其了解成泛型类型为 Object 的 ValueKey。然而留神两者的 == 办法是不一样的,ValueKey 依据 value 的值是否相等来判断 ValueKey 是否相等(相当于 java 的 equals 办法 ),而 ObjectKey 依据 indentical 办法( 判断两个援用是否指向同一个对象,相当于 java 的 == 操作符)来判断两个 ObjectKey 是否相等的。

UniqueKey

class UniqueKey extends LocalKey {UniqueKey();


  @override
  String toString() => '[#${shortHash(this)}]';
}

惟一的 key,其并未重写 == 和 hashCode 办法,所有它只和本人相等。留神看 UniqueKey 的构造函数,并没有像下面介绍的几个 key 的构造函数一样应用 const 润饰,这样做的目标是为了进一步保障 UniqueKey 的唯一性。这样在调用 Element 的 updateChild 办法时,此办法外部调用的 Widget.canUpdate 办法就会始终返回 false,从而每次都会创立新的 child element。

所以,如果你想让某一个 widget 每一次都不复用 old element,而是去从新创立新的 element,那么就给他增加 UniqueKey 吧。

const 是编译时常量,在编译期,其值就曾经确定。背地利用的相似于常量池的概念,被 const 润饰的对象会保留在常量池中,前面会对其进行复用。如果 UniqueKey 构造函数增加了 const 关键词,那么有如下代码 var k1 = const UniqueKey(); var k2 = const UniqueKey(); 此时 k1==k2 永远为 true,就不能保障其唯一性。

GlobalKey

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 分为两类,一类是 LocalKey,实现类有 ValueKey、ObjectKey、UniqueKey;一类是 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 抵触的问题。

作者:京东物流 沈亮堂

内容起源:京东云开发者社区

退出移动版