"Zone mismatch" 消息
概述
#从 Flutter 3.10 开始,框架会检测使用 Zones 时出现的失配,并在调试构建中向控制台报告。
背景
#Zones 是 Dart 中管理回调的机制。虽然主要用于在测试中覆盖 print
和 Timer
逻辑,以及在测试中捕获错误,但有时也用于将全局变量的作用域限制在应用程序的某些部分。
Flutter 要求(并且一直要求)所有框架代码都在同一个 Zone 中运行。特别重要的是,这意味着对 WidgetsFlutterBinding.ensureInitialized()
的调用应该与对 runApp()
的调用在同一个 Zone 中进行。
过去,Flutter 并没有检测到这种失配。这有时会导致晦涩难懂且难以调试的问题。例如,键盘输入的事件回调可能使用一个无法访问其期望的 zoneValues
的 Zone 来调用。根据我们的经验,绝大多数(如果不是全部)不保证 Flutter 框架所有部分都在同一个 Zone 中工作的 Zones 使用方式的代码都存在一些潜在的 bug。通常这些 bug 与 Zones 的使用无关。
为了帮助那些意外违反此约束的开发者,从 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
。
迁移指南
#使此消息消失的最佳方法是从应用程序中移除 Zones 的使用。Zones 非常难以调试,因为它们本质上是全局变量,并且会破坏封装。最佳实践是避免使用全局变量和 Zones。
如果移除 Zones 不可行(例如,因为应用程序依赖于一个依赖 Zones 进行配置的第三方库),那么对 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
调用中,以提供适当的值。
强烈建议以这种方式使用 Zones 的包迁移到更易于维护的解决方案。
时间线
#已合并版本:3.9.0-9.0.pre
稳定版本:3.10.0
参考资料
#API 文档
相关问题
- Issue 94123: Flutter framework does not warn when ensureInitialized is called in a different zone than runApp
相关 PR
- PR 122836: Assert that runApp is called in the same zone as binding.ensureInitialized