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冲突的问题。
作者:京东物流 沈明亮
内容来源:京东云开发者社区
Flutter三棵树系列之详解各种Key的更多相关文章
- 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程
反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) 背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...
- PHP输出缓存ob系列函数详解
PHP输出缓存ob系列函数详解 ob,输出缓冲区,是output buffering的简称,而不是output cache.ob用对了,是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额 ...
- Flutter完整开发实战详解
Flutter完整开发实战详解(一.Dart语言和Flutter基础) Flutter完整开发实战详解(二. 快速开发实战篇) Flutter完整开发实战详解(三. 打包与填坑篇)
- SignalR新手系列教程详解总结(转)
SignalR新手系列教程详解总结 GlobalHost.ConnectionManager.GetHubContext<TodoListHub>() .Clients.Clients(l ...
- 猫哥网络编程系列:详解 BAT 面试题
从产品上线前的接口开发和调试,到上线后的 bug 定位.性能优化,网络编程知识贯穿着一个互联网产品的整个生命周期.不论你是前后端的开发岗位,还是 SQA.运维等其他技术岗位,掌握网络编程知识均是岗位的 ...
- 【WebApi系列】详解WebApi如何传递参数
WebApi系列文章 [01]浅谈HTTP在WebApi开发中的运用 [02]聊聊WebApi体系结构 [03]详解WebApi参数的传递 [04]详解WebApi测试和PostMan [05]浅谈W ...
- css3系列之详解perspective
perspective 简单来说,就是设置这个属性后,那么,就可以模拟出像我们人看电脑上的显示的元素一样.比如说, perspective:800px 意思就是,我在离屏幕800px 的地方观看这 ...
- 【tomcat系列】详解tomcat架构(上篇)
java中,常用的web服务器一般由tomcat,weblogic,jetty,undertwo等,但从用户使用广泛度来说,tomcat用户量相对比较大一些,当然这也基于它开源和免费的特点. 从软件架 ...
- Java并发包源码学习系列:详解Condition条件队列、signal和await
目录 Condition接口 AQS条件变量的支持之ConditionObject内部类 回顾AQS中的Node void await() 添加到条件队列 Node addConditionWaite ...
- pssh系列命令详解
安装 pssh提供OpenSSH和相关工具的并行版本.包括pssh,pscp,prsync,pnuke和pslurp.该项目包括psshlib,可以在自定义应用程序中使用.pssh是python写的可 ...
随机推荐
- 微软wsl2启用天父行程systemd
默认情况下 微软wsl2的天父行程是init,没办法使用systemctl相关指令,所以想使用天父行程 systemd. 本文以Wsl2 Alma Linux为例,启用systemd 上帝与你同在,阿 ...
- Tomcat启动报错,Server Tomcat v8.0 Server at localhost failed to start
Eclipse 中Tomcat 启动报错Eclipse的提示窗口 Server Tomcat v8.0 Server at localhost failed to start .日志输出中报 F ...
- NodeJS V8引擎的内存和垃圾回收器(GC)
一.为什么需要GC 程序应用运行需要使用内存,其中内存的两个分区是我们常常会讨论的概念:栈区和堆区. 栈区是线性的队列,随着函数运行结束自动释放的,而堆区是自由的动态内存空间.堆内存是手动分配释放或者 ...
- style中加了scoped无法更改element ui样式解决办法
第一种方法 原因:scoped 解决方法:去掉scoped 注意:此时该样式会污染全局样式,可以把它放在公共的css里面 为了不让所有的 el-input标签都是该样式,可以在HTML给改input加 ...
- Json和对象之间的转换
JSON是一种字符: json转对象: var str = '{"name":"admin","age":16,"sex" ...
- Alchemy Nft黑客松任务(第一周)
Alchemy是什么项目? 2019年12月,Alchemy完成1500万美元A轮融资,资方为Pantera Capital,斯坦福大学,Coinbase,三星等. 2021年4月,Alchemy以5 ...
- 如何在Solidity中建立DAO(去中心化自治组织)?
本文将帮助您理解 DAO 的概念,并帮助您构建一个基本的 DAO. 什么是 DAO? 您可以将 DAO 视为基于互联网的实体(比如企业),由其股东(拥有代币和比例投票权的成员)共同拥有和管理.在 DA ...
- python之修改本地Ip地址
安装模块pip install wmi # -*- coding: cp936 -*- # # FileName: ModifyIP.py # Date : 2008-01-15 # import w ...
- 隐私安全常用网站备忘#privacy
在线查询浏览器WebRTC漏洞 地址 个人数据泄露(#包含扣扣和phone,微博等) 地址 最全的隐私保护指南 地址 钟馗之眼 地址 shodan#暗黑版goole搜索引擎(需代理访问) 地址 社工查 ...
- Java中ThreadLocal的用法和原理
用法 隔离各个线程间的数据 避免线程内每个方法都进行传参,线程内的所有方法都可以直接获取到ThreadLocal中管理的对象. package com.example.test1.service; i ...