Dart SDK 的 2.15 版本新增了具备有更快并发能力的 isolates 、支持 tear-off 的构造函数 、关于 dart:core 库枚举支持的改进、包发布者相关的新功能等等。
如今的物理设备几乎都有多核的 CPU ,能够并行地执行多个任务,而对于大多数 Dart 程序而言,这些内核的使用过程对开发人员来说是透明的:
默认情况下 Dart 运行时所有 Dart 代码只会在单个内核上运行,但会使用其他内核来执行系统级任务,例如:异步的输入/输出、读写文件或者网络调用等。
但有时候 Dart 代码本身可能需要用到并发执行的场景,例如可能同时有“连续的动画和一个需要长时间运行的任务”,或者解析一个大型 JSON 文件等等场景。
如果附加需要执行的任务花费时间太长,可能就会导致 UI 卡顿或运行滞后,所以通过将这些额外的任务移动到另外一个单独的核心运行,保证动画可以继续在主执行线程上运行而不受干扰是必要的支持。
Dart 的并发模型是基于isolates设计的——一种相互隔离的独立执行单元。这是为了防止在共享内存时,出现相关的并发编程错误问题,例如data races.等。
Dart 通过不允许在 isolates 之间共享任何可变对象来防止这些错误,而是使用 消息传递 在 isolates 之间交换状态,而如今在 Dart 2.15 中对 isolates 进行了许多实质性的改进。
Dart 2.15 重新设计和实现了 isolates 的工作方式,引入了一个新概念:isolate groups, isolate groups 中的 isolate 共享正在运行的程序中的各种内部数据结构,这使得 groups 中的个体 isolates 变得更加轻便。
如今在 isolate groups 中启动额外的 isolate 可以快近 100 倍,因为现在不需要初始化程序结构,并且产生新的 isolate 所需要的内存减少了 10-100 倍。
虽然 isolate groups 还是不允许 isolate 之间共享可变对象,但该 group 可以通过共享堆来实现的,所以能够解锁更多功能,比如可以将对象从一个 isolate 传递到另一 isolate,这样就可以用于执行需要返回大量内存数据的任务。
例如通过网络调用获取数据,将该数据解析为一个大型 JSON 对象,然后将该 JSON 对象返回到主isolates。 在 Dart 2.15 之前执行该操作需要“深度复制”,如果复制花费的时间超过帧预算时间,就可能会导致 UI 卡顿。
在 2.15 中工作的 isolates 可以调用 Isolate.exit()
将其结果作为参数传递。将运行的 isolates 结果的内存传递给主 isolates ,而不是进行复制,主 isolates 可以在指定时间内接收结果。
这个行为在更新的Flutter 2.8 中 的 compute()
函数里,同样已经改变成这种实现, 如果你已经在使用 Isolate.exit()
和 compute()
函数,那么在升级到 Flutter 2.8 后将自动获得这些性能提升。
最后 Dart 2.15 还重新设计了 isolates 消息传递机制的实现方式,使得中小型的消息传递速度提高了大约 8 倍。另外扩展了 isolates 可以相互发送的对象种类,增加了对函数类型、闭包和堆栈跟踪对象的支持,有关详细信息,请参阅 API 文档 SendPort.send()
:
要了解有关如何使用隔离的更多信息,请参阅 2.15 添加的 Dart 文档中的并发介绍, 另外还有许多代码示例可以查看。
在 Dart 中可以通过使用函数的名称创建一个函数对象,该对象指向另一个对象上的函数,如下代码所示,main()
方法的第二行声明了“将 g
设置为 m.greet
”的语法:
class Greeter {
final String name;
Greeter(this.name);
void greet(String who) {
print('$name says: Hello $who!');
}
}void main() {
final m = Greeter('Michael');
final g = m.greet; // g holds a function pointer to m.greet.
g('Leaf'); // Invokes and prints "Michael says: Hello Leaf!"
}
在使用 Dart core libraries 时,这种类函数指针(也称为函数 tear-offs)经常出现,如下是foreach()
通过传递函数指针来调用可迭代对象的示例:
final m = Greeter('Michael');['Lasse', 'Bob', 'Erik'].forEach(m.greet);// Prints "Michael says: Hello Lasse!", "Michael says: Hello Bob!",
// "Michael says: Hello Erik!"
在之前的版本中 Dart SDK 不支持从构造函数创建 tear-offs(语言问题#216),这就显得很烦人,因为在许多情况下,构建 Flutter UI 时构造函数 tear-offs 会是开发所需要的,所以从 Dart 2.15 开始支持这种语法。
如下是构建 Column
包含三个 Text
Widget 的示例,通过调用 .map()
它并将其传递给Text
:
class FruitWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: ['Apple', 'Orange'].map(Text.new).toList());
}
}
Text.new
指 Text
类的默认构造函数,还可以引用命名构造函数,例如: .map(Text.rich)
。
当实现构造函数的 tear-offs 时,Dart 2.15 借此机会修复了现有的函数指针支持中的一些不一致问题,现在可以专门化一个泛型方法来创建一个非泛型方法:
T id<T>(T value) => value;\
var intId = id<int>; // New in 2.15.\
int Function(int) intId = id; // Pre-2.15 workaround.
你甚至可以特化一个泛型函数对象来创建一个非泛型函数对象:
const fo = id; // Tear off `id`, creating a function object.\
const c1 = fo<int>; // New in 2.15; error before.
最后还清理了涉及泛型的类型文字:
var y = List; // Already supported.\
var z = List<int>; // New in 2.15.\
var z = typeOf<List<int>>(); // Pre-2.15 workaround.
Dart 2.15 为 dart:core
库(#1511)中的枚举 API 添加了更多的优化,现在开发者可以通过 .name
来获取每个枚举值的 String
值:
enum MyEnum {
one, two, three
}
void main() {
print(MyEnum.one.name); // Prints "one".
}
还可以按名称查找枚举值:
print(MyEnum.values.byName('two') == MyEnum.two); // Prints "true".
最后还可以获得所有名称-值对的映射:
final map = MyEnum.values.asNameMap();\
print(map['three'] == MyEnum.three); // Prints "true".
有关使用这些新 API 的示例可以参阅 Flutter PR #94496。
Dart 2.15 添加了对 Compressed pointers 的支持,如果只需要支持 32 位的地址空间(最多 4 GB 内存),则 64 位 SDK 可以使用更节省空间的指针表示形式。
压缩指针导致显着的内存减少,在对 GPay 应用程序的内部测试中,我们看到 Dart 堆大小减少了大约 10%。
由于压缩指针意味着无法寻址 4 GB 以上的 RAM,因此该功能位于 Dart SDK 中的配置选项之后,只能在构建 SDK 时由 Dart SDK 的嵌入器切换。
Flutter SDK 2.8 版已为 Android 构建中启用此配置,Flutter 团队正在考虑在未来版本中为 iOS 构建启用此配置。
以前的 DevTools 调试和性能工具套件 不在 Dart SDK 中,所以开发者需要单独下载。
从 Dart 2.15 开始,现在可以在下载的 Dart SDK 里直接获取 DevTools,而无需进一步的安装步骤。
有关将 DevTools 与 Dart 命令行应用结合使用的更多信息,请参阅 DevTools 文档。
Dart 2.15 SDK 在 dart pub
开发者命令和pub.dev包存储库中还有两个新的功能。
首先包发布者有了一个新的安全功能,目的是用于检测发布者在发布包中意外发布的机密(例如 Cloud 或 CI 凭据)。
在了解到 GitHub 存储库中每天有数以千计的秘密被泄露后,Dart SDK 受到启发添加了此泄漏检测。
泄漏检测作为 dart pub publish
命令中预发布验证运行的一部分运行,如果它在即将发布的文件中检测到潜在的秘密,该 publish
命令将退出而不发布,并打印如下输出:
Publishing my_package 1.0.0 to [https://pub.dartlang.org](https://pub.dartlang.org/):\
Package validation found the following errors:\
* line 1, column 1 of lib/key.pem: Potential leak of Private Key detected.\
╷\
1 │ ┌ - - -BEGIN PRIVATE KEY - - -\
2 │ │ H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ\
3 │ └ - - -END PRIVATE KEY - - -\
╵\
* line 2, column 23 of lib/my_package.dart: Potential leak of Google OAuth Refresh Token detected.\
╷\
2 │ final refreshToken = "1//042ys8uoFwZrkCgYIARAAGAQSNwF-L9IrXmFYE-sfKefSpoCnyqEcsHX97Y90KY-p8TPYPPnY2IPgRXdy0QeVw7URuF5u9oUeIF0";
在极少数情况下此检测可能会出现误报,在这些情况下可以将文件添加到许可白名单。
其次还为发布者添加了另一个功能:支持收回已发布的软件包版本。
当发布了有问题的包版本时,通常建议是发布一个小增量的新版本,以修复意外问题。
在极少数情况下,例如当开发者还没有这样的修复能力时,或者是不小心发布了一个新的主要版本,就可以使用新的包收回功能作为最后的手段,此功能在 pub.dev 上的管理 UI 中可用:
当一个包版本被收回时,pub 客户端不再为 pub get
或者 pub upgrade
解析那个版本,如果开发者使用了成功撤回的版本(因此在他们的pubspec.lock
文件中),他们将在下次运行时看到警告pub
:
$ dart pub get\
Resolving dependencies…\
mypkg 0.0.181-buggy (retracted, 0.0.182-fixed available)\
Got dependencies!
最近发现了一个涉及双向 Unicode 字符的通用编程语言漏洞 ( CVE-2021–42574 ),此漏洞影响大多数支持 Unicode 的现代编程语言,下面的 Dart 源代码演示了这个问题:
main() {
final accessLevel = 'user';
if (accessLevel == 'user .// Check if admin ') {
print('You are a regular user.');
} else {
print('You are an admin.');
}
}
你可能会认为该程序打印出 You are a regular user.
,但实际上它可能会打印 You are an admin
。 !
通过使用包含双向 Unicode 字符的字符串,就可以利用此漏洞,例如上述都在一行中的这些字符,将文本的方向从左到右更改为从右到左和回退。
对于双向字符,文本在屏幕上的呈现与实际文本内容截然不同,开发者可以在GitHub code gist 中看到这样的示例。
针对此漏洞的缓解措施包括使用检测双向 Unicode 字符的工具(编辑器、代码审查工具等)以便开发人员了解它们,并在知情的情况下接受它们的使用,上面链接的 GitHub gist 文件查看器是显示这些字符的工具的一个示例。
Dart 2.15 引入了进一步的缓解措施(Dart 安全公告 CVE-2021–22567):Dart 分析器现在扫描双向 Unicode 字符,并标记它们的任何使用:
$ dart analyze
Analyzing cvetest... 2.6sinfo • bin/cvetest.dart:4:27 • The Unicode code point 'U+202E'
changes the appearance of text from how it's interpreted
by the compiler. Try removing the code point or using the
Unicode escape sequence '\u202E'. •
text_direction_code_point_in_literal
我们建议用 Unicode 转义序列替换这些字符,让它们在任何文本编辑器或查看器中可见,或者如果开发者觉得确实合法使用了这些字符,则可以通过在使用前的行中添加覆盖来禁用警告:
// 忽略:text_direction_code_point_in_literal
Dart 2.15 还发布了第二个与 pub.dev 相关的 Dart 安全公告 CVE-2021–22568。
此公告面向可能涉及已将包发布到第三方发布包服务器(例如私人或公司内部包服务器)的包发布者,仅发布到公共 pub.dev 存储库(标准配置)的开发人员不受此漏洞的影响。
如果开发者已发布到第三方存储库,则该漏洞是在该第三方存储库中提供用于身份验证的 OAuth2 临时(一小时)访问令牌,可能会被滥用来针对公共 pub.dev 存储库进行身份验证。
因此恶意的第三方 pub 服务器可能会使用访问令牌在 pub.dev 上冒充开发者并在那里发布包。
如果开发者已将软件包发布到不受信任的第三方软件包存储库,请考虑对 pub.dev 公共软件包存储库中的所有帐户活动进行审核,推荐可以使用 pub.dev 的活动日志。