您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Flutter三棵树系列之详解各种Key
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Flutter三棵树系列之详解各种Key
自猿其说Tech
2022-06-07
IP归属:未知
22960浏览
前端
Flutter
### 1 Overview 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。 ![](//img1.jcloudcs.com/developer.jdcloud.com/aa1cf605-a6bd-44fc-8733-46df09f6f7a620220607155502.png) 关键词:Flutter Key GlobalKey LocalKey ### 2 Key的分类 #### 2.1 Key ```java @immutable abstract class Key { const factory Key(String value) = ValueKey<String>; @protected const Key.empty(); } ``` Key是所有key的基类,内部实现了一个工厂构造函数,默认创建String类型的ValueKey。内部还是先了一个empty的构造函数,主要是给子类用的。 #### 2.2 LocalKey ```java abstract class LocalKey extends Key { const LocalKey() : super.empty(); } ``` LocalKey没有实际作用,主要是用来区分GlobalKey的,其具体的实现类有ValueKey、ObjectKey、UniqueKey。 ##### 2.2.1 ValueKey ```java 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相等。 ##### 2.2.2 ObjectKey ```java 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是否相等的。 ##### 2.2.3 UniqueKey ```java 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,就不能保证其唯一性。 #### 2.3 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的方法。 为了更好的理解,看下面这张图。这是一个简单的Element Tree,#3代表其关联的key为GlobalKey,当发生reparent操作时,其所有的子节点,也就是#4 到 #7这些子节点,都会被重新调用deactivate方法和activate方法,完成在树上的移动。 ![](//img1.jcloudcs.com/developer.jdcloud.com/eb66cbcd-7ccf-4ca0-8d95-17d7bc2839e020220607161549.png) 下面看下其源码: ```java 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天然就是全局唯一的。 ##### 2.3.1 LabeledGlobalKey 这是GlobalKey的默认实现,内部仅有一个debugLabel属性,其他的也没啥。 ```java 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; } ``` ##### 2.3.2 GlobalObjectKey ```java 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的内部类,比如: ```java class _MyGlobalObjectKey extends GlobalObjectKey { const _MyGlobalObjectKey(Object value) : super(value); } ``` ### 3 key使用场景 在实际的开发过程中,我们往往会出于以下两种需要去使用key。 #### 3.1 实现复杂动效 ![](//img1.jcloudcs.com/developer.jdcloud.com/eb15c183-9f14-4645-a287-3bcae09b9ce120220607161756.gif) 当我们实现一些复杂的动画效果时,比如上图中的tabbar吸顶操作,需要去获取指定widget在屏幕上的实时位置信息,那么我们可以给这个widget赋值GlobalKey,通过GlobalKey来获取。具体用法如下: ```java GlobalKey? _tabPositionKey = GlobalKey(debugLabel: 'get_tab_realtime_position'); double _getTabRealtimeDy() { if (_tabPositionKey!.currentContext != null) { RenderBox box = _tabPositionKey!.currentContext!.findRenderObject() as RenderBox; Offset offset = box.localToGlobal(Offset.zero); return offset.dy; } else { return 0.0; } } ``` #### 3.2 提升性能 如果开发过程中使用了可以设置多个child的widget,比如常用的Row和Column,出于性能考虑,可以为每个child widget设置一个key。至于背后的原理,简单来讲,如何设置了key,那么会有可能复用old child element,如果没有设置key,就只能重新创建element,此element对应的所有子节点也会重新创建,造成性能的损失(可以参考MultiChildRenderOjectElement#updateChildren方法)。 ### 4 总结 - 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冲突的问题。 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:沈明亮
原创文章,需联系作者,授权转载
上一篇:springboot的项目如何既要用jar包启动,同时还可以为不同的机房设置不同的配置文件
下一篇:测试用例设计方法六脉神剑——第六剑:心法至简,百家之长集成
相关文章
前端十年回顾 | 漫画前端的前世今生
Taro小程序跨端开发入门实战
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号