"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