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
Key 是所有 key 的基类,内部实现了一个工厂构造函数,默认创建 String 类型的 ValueKey。内部还是先了一个 empty 的构造函数,主要是给子类用的。
LocalKey
LocalKey 没有实际作用,主要是用来区分 GlobalKey 的,其具体的实现类有 ValueKey、ObjectKey、UniqueKey。
ValueKey
内部维护了泛型类型的 value 属性,并实现了==和 hashCode 方法。只要两个 ValueKey 的 value 属性相等,那么就认为两个 Key 相等。
ObjectKey
ObjectKey 是继承自 LocalKey 的,可以将其理解成泛型类型为 Object 的 ValueKey。但是注意两者的==方法是不一样的,ValueKey 根据 value 的值是否相等来判断 ValueKey 是否相等(相当于 java 的 equals 方法),而 ObjectKey 根据 indentical 方法(判断两个引用是否指向同一个对象,相当于 java 的==操作符)来判断两个 ObjectKey 是否相等的。
UniqueKey
唯一的 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 的方法。
下面看下其源码:
其和 Key 类差不多,也有一个工厂构造函数,默认创建的是 LabeledGlobalKey,其构造函数的 debugLabel 仅仅是为了 debug 时使用,并不会用来标识 element。
如何获取其关联的 element?从源码来看,其直接访问的是 BuildOwner 里用来保存 GlobalKey 和 Element 对应关系的 map。获取到了其关联的 element,那么就能获取到其对应的 widget 以及 state,详细的可以看上面的源码。
需要注意的是其并没有重写==和 hashCode 方法,构造函数也没有被 const 修饰,这也就使 LabeledGlobalKey 天然就是全局唯一的。
LabeledGlobalKey
这是 GlobalKey 的默认实现,内部仅有一个 debugLabel 属性,其他的也没啥。
GlobalObjectKey
特殊的 GlobalKey,重写了==和 hashCode 方法,内部维护了一个 Object 对象,通过判断此 Object 是否指向同一块内存地址来判断两个 GlobalObjectKey 是否相等。
GlobalKey 被要求全局唯一,其默认实现 LabeledGloalKey 因为其并没有重写==和 hashCode 方法,也不支持 const 构造函数,所以天然是全局唯一的。但是 GlobalObjectKey 不然,如果有两个或者多个地方使用到了拥有同一个 Object 的 GlobalObjectKey,那么就不能保证其全局唯一性,造成程序出错。此时,可以继承 GlobalObjectKey,实现一个 private 的内部类,比如:
总结
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 冲突的问题。
作者:京东物流 沈明亮
内容来源:京东云开发者社区
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/cc1a0806b4d835871eeb7db02】。文章转载请联系作者。
评论