跳至主要內容

「區域不匹配」訊息

摘要

#

從 Flutter 3.10 開始,當使用 Zones 時,框架會偵測到不匹配的情況,並在偵錯建置中將其報告到主控台。

背景

#

Zones 是一種在 Dart 中管理回呼的機制。雖然主要用於覆寫測試中的 printTimer 邏輯,以及在測試中捕獲錯誤,但有時也會用於將全域變數的作用域限定於應用程式的某些部分。

Flutter 要求(而且一直以來都要求)所有框架程式碼都在同一個區域中執行。值得注意的是,這表示對 WidgetsFlutterBinding.ensureInitialized() 的呼叫應與對 runApp() 的呼叫在同一個區域中執行。

過去,Flutter 並未偵測到此類不匹配的情況。這有時會導致難以理解和難以偵錯的問題。例如,使用沒有存取其預期的 zoneValues 的區域可能會調用鍵盤輸入的回呼。根據我們的經驗,大多數(如果不是全部)以無法保證 Flutter 框架的所有部分都在同一個區域中工作的方式使用 Zones 的程式碼都存在一些潛在的錯誤。這些錯誤通常與 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 框架的各種呼叫應移至都在同一個區域中。通常,這表示將對 WidgetsFlutterBinding.ensureInitialized() 的呼叫移至與對 runApp() 的呼叫相同的閉包中。

當執行 runApp 的區域使用從外掛程式獲得的 zoneValues 初始化時,可能會很麻煩(這需要先呼叫 WidgetsFlutterBinding.ensureInitialized())。

在這種情況下的一種選項是在 zoneValues 中放置一個可變物件,並在值可用時使用該值更新該物件。

dart
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:當 ensureInitialized 在與 runApp 不同的區域中呼叫時,Flutter 框架不會發出警告

相關的 PR

  • PR 122836:斷言 runApp 與 binding.ensureInitialized 在同一個區域中呼叫