"Zone 不匹配" 消息
概述
#从 Flutter 3.10 开始,框架在使用 Zone 时会检测不匹配项,并在调试构建中将其报告到控制台。
背景
#Zone 是 Dart 中用于管理回调的机制。虽然它们主要用于在测试中覆盖 print
和 Timer
逻辑以及捕获测试中的错误,但有时也用于将全局变量限定在应用程序的特定部分。
Flutter 要求(并且一直要求)所有框架代码都在同一个 Zone 中运行。值得注意的是,这意味着对 WidgetsFlutterBinding.ensureInitialized()
的调用应与对 runApp()
的调用在同一个 Zone 中运行。
从历史上看,Flutter 并未检测到此类不匹配。这有时会导致隐晦且难以调试的问题。例如,键盘输入的 callback 可能会使用一个无法访问其预期 zoneValues
的 Zone 来调用。根据我们的经验,大多数(如果不是全部)以不保证 Flutter 框架的所有部分都在同一个 Zone 中运行的方式使用 Zone 的代码都存在一些潜在错误。这些错误通常看起来与 Zone 的使用无关。
为了帮助意外违反此不变量的开发者,从 Flutter 3.10 开始,当检测到不匹配时,调试构建中会打印一条非致命警告。该警告如下所示:
════════ Exception caught by Flutter framework ════════════════════════════════════
The following assertion was thrown during runApp:
Zone mismatch.
The Flutter bindings were initialized in a different zone than is now being used.
This will likely cause confusion and bugs as any zone-specific configuration will
inconsistently use the configuration of the original binding initialization zone or
this zone based on hard-to-predict factors such as which zone was active when a
particular callback was set.
It is important to use the same zone when calling `ensureInitialized` on the
binding as when calling `runApp` later.
To make this warning fatal, set BindingBase.debugZoneErrorsAreFatal to true before
the bindings are initialized (i.e. as the first statement in `void main() { }`).
[...]
═══════════════════════════════════════════════════════════════════════════════════
可以通过将 BindingBase.debugZoneErrorsAreFatal
设置为 true
来使该警告成为致命错误。此标志在 Flutter 的未来版本中可能会更改为默认 true
。
迁移指南
#消除此消息的最佳方法是从应用程序中移除对 Zone 的使用。Zone 可能非常难以调试,因为它们本质上是全局变量,并且破坏了封装性。最佳实践是避免使用全局变量和 Zone。
如果无法移除 Zone(例如,因为应用程序依赖于某个第三方库,该库依赖于 Zone 进行配置),那么对 Flutter 框架的各种调用都应移至同一个 Zone 中。通常,这意味着将对 WidgetsFlutterBinding.ensureInitialized()
的调用移动到与 runApp()
调用相同的闭包中。
当运行 runApp
的 Zone 正在使用从插件获取的 zoneValues
进行初始化时(这需要先调用 WidgetsFlutterBinding.ensureInitialized()
),这可能会很麻烦。
在这种情况下,一种选择是将一个可变对象放置在 zoneValues
中,并在值可用后使用该值更新该对象。
import 'dart:async';
import 'package:flutter/material.dart';
class Mutable<T> {
Mutable(this.value);
T value;
}
void main() {
var myValue = Mutable<double>(0.0);
Zone.current.fork(
zoneValues: {
'myKey': myValue,
}
).run(() {
WidgetsFlutterBinding.ensureInitialized();
var newValue = ...; // obtain value from plugin
myValue.value = newValue; // update value in Zone
runApp(...);
});
}
在需要使用 myKey
的代码中,可以通过 Zone.current['myKey'].value
间接获取。
当此类解决方案不起作用时(因为第三方依赖项要求为特定的 zoneValues
键使用特定类型),所有对该依赖项的调用都可以封装在提供适当值的 Zone
调用中。
强烈建议以这种方式使用 Zone 的包迁移到更易于维护的解决方案。
时间线
#已发布版本:3.9.0-9.0.pre
稳定版本:3.10.0
参考资料
#API 文档
相关问题
- 问题 94123:当 ensureInitialized 在与 runApp 不同的 Zone 中调用时,Flutter 框架不发出警告
相关 PR
- PR 122836:断言 runApp 与 binding.ensureInitialized 在同一 Zone 中调用