跳至主要內容

熱重載

Flutter 的熱重載功能可協助您快速輕鬆地進行實驗、建構 UI、新增功能和修正錯誤。熱重載的運作方式是將更新後的原始碼檔案注入正在執行的 Dart 虛擬機器 (VM) 中。在 VM 使用新版本的欄位和函式更新類別之後,Flutter 架構會自動重建 widget 樹,讓您能夠快速檢視變更的效果。

如何執行熱重載

#

要熱重載 Flutter 應用程式

  1. 從支援的 Flutter 編輯器或終端機視窗執行應用程式。實體或虛擬裝置都可以是目標。只有除錯模式中的 Flutter 應用程式才能進行熱重載或熱重新啟動。

  2. 修改專案中的其中一個 Dart 檔案。大多數類型的程式碼變更都可以熱重載;如需需要熱重新啟動的變更清單,請參閱特殊情況

  3. 如果您正在使用支援 Flutter IDE 工具的 IDE/編輯器,請選取全部儲存 (cmd-s/ctrl-s),或按一下工具列上的熱重載按鈕。

    如果您正在使用 flutter run 在命令列中執行應用程式,請在終端機視窗中輸入 r

成功執行熱重載操作後,您會在主控台中看到類似如下的訊息

Performing hot reload...
Reloaded 1 of 448 libraries in 978ms.

應用程式會更新以反映您的變更,並且會保留應用程式的目前狀態。您的應用程式會從執行熱重載命令之前的位置繼續執行。程式碼會更新,執行會繼續。

Android Studio UI
Android Studio 中的執行、執行除錯、熱重載和熱重新啟動控制項

只有在變更後重新執行修改後的 Dart 程式碼時,程式碼變更才會產生可見的效果。具體來說,熱重載會導致所有現有的 widget 重建。只有參與重建 widget 的程式碼會自動重新執行。例如,main()initState() 函式不會再次執行。

特殊情況

#

接下來的章節將說明涉及熱重載的特定情境。在某些情況下,對 Dart 程式碼進行小幅變更可讓您繼續將熱重載用於應用程式。在其他情況下,則需要熱重新啟動或完整重新啟動。

應用程式被終止

#

當應用程式被終止時,熱重載可能會中斷。例如,如果應用程式在背景中停留太久。

編譯錯誤

#

當程式碼變更導致編譯錯誤時,熱重載會產生類似如下的錯誤訊息

Hot reload was rejected:
'/path/to/project/lib/main.dart': warning: line 16 pos 38: unbalanced '{' opens here
  Widget build(BuildContext context) {
                                     ^
'/path/to/project/lib/main.dart': error: line 33 pos 5: unbalanced ')'
    );
    ^

在這種情況下,只要修正 Dart 程式碼中指定行的錯誤,即可繼續使用熱重載。

CupertinoTabView 的 builder

#

熱重載不會套用對 CupertinoTabViewbuilder 所做的變更。如需詳細資訊,請參閱 Issue 43574

枚舉類型

#

當枚舉類型變更為一般類別,或一般類別變更為枚舉類型時,熱重載將無法運作。

例如

變更之前

dart
enum Color {
  red,
  green,
  blue,
}

變更之後

dart
class Color {
  Color(this.i, this.j);
  final int i;
  final int j;
}

泛型類型

#

修改泛型類型宣告時,熱重載將無法運作。例如,下列方式將無法運作

變更之前

dart
class A<T> {
  T? i;
}

變更之後

dart
class A<T, V> {
  T? i;
  V? v;
}

原生程式碼

#

如果您已變更原生程式碼(例如 Kotlin、Java、Swift 或 Objective-C),則必須執行完整重新啟動(停止並重新啟動應用程式)才能看到變更生效。

先前的狀態與新程式碼合併

#

Flutter 的具狀態熱重載會保留應用程式的狀態。此方法可讓您只檢視最近變更的效果,而不會捨棄目前的狀態。例如,如果您的應用程式需要使用者登入,您可以修改並熱重載導覽階層中較下層的頁面,而無需重新輸入您的登入憑證。狀態會保留,這通常是預期的行為。

如果程式碼變更影響應用程式的狀態(或其依附元件),則應用程式必須處理的資料可能與從頭執行時的資料不完全一致。熱重載之後的行為可能與熱重新啟動不同。

包含最近的程式碼變更,但不包含應用程式狀態

#

在 Dart 中,靜態欄位會延遲初始化。這表示當您第一次執行 Flutter 應用程式且讀取靜態欄位時,它會設定為其初始化程式所評估的值。全域變數和靜態欄位會被視為狀態,因此不會在熱重載期間重新初始化。

如果您變更全域變數和靜態欄位的初始化程式,則必須熱重新啟動或重新啟動持有初始化程式的狀態,才能看到變更。例如,請考慮下列程式碼

dart
final sampleTable = [
  Table(
    children: const [
      TableRow(
        children: [Text('T1')],
      )
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T2')],
      )
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T3')],
      )
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T4')],
      )
    ],
  ),
];

執行應用程式後,您會進行下列變更

dart
final sampleTable = [
  Table(
    children: const [
      TableRow(
        children: [Text('T1')],
      )
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T2')],
      )
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T3')],
      )
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T10')], // modified
      )
    ],
  ),
];

您進行熱重載,但變更未反映出來。

相反地,在下列範例中

dart
const foo = 1;
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}

第一次執行應用程式會列印 11。然後,您會進行下列變更

dart
const foo = 2; // modified
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}

雖然對 const 欄位值的變更一律會熱重載,但不會重新執行靜態欄位初始化程式。從概念上來說,const 欄位會被視為別名而不是狀態。

Dart VM 會偵測初始化程式變更,並在需要熱重新啟動才能使一組變更生效時加以標示。標示機制會針對上述範例中的大多數初始化工作觸發,但對於以下情況則不會觸發

dart
final bar = foo;

若要更新 foo 並在熱重載後檢視變更,請考慮將欄位重新定義為 const 或使用 getter 來傳回值,而不是使用 final。例如,下列任一解決方案都適用

dart
const foo = 1;
const bar = foo; // Convert foo to a const...
void onClick() {
  print(foo);
  print(bar);
}
dart
const foo = 1;
int get bar => foo; // ...or provide a getter.
void onClick() {
  print(foo);
  print(bar);
}

如需詳細資訊,請閱讀關於 Dart 中 constfinal 關鍵字之間差異的資訊。

排除最近的 UI 變更

#

即使熱重載操作看似成功且未產生任何例外狀況,某些程式碼變更可能在重新整理的 UI 中不可見。這種行為在變更應用程式的 main()initState() 方法之後很常見。

一般來說,如果修改後的程式碼位於根 widget 的 build() 方法的下游,則熱重載會如預期般運作。但是,如果修改後的程式碼不會因重建 widget 樹而重新執行,則您在熱重載後將看不到其效果。

例如,請考慮下列程式碼

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(onTap: () => print('tapped'));
  }
}

執行此應用程式後,請依如下方式變更程式碼

dart
import 'package:flutter/widgets.dart';

void main() {
  runApp(const Center(child: Text('Hello', textDirection: TextDirection.ltr)));
}

透過熱重新啟動,程式會從頭開始,執行新版本的 main(),並建構顯示文字 Hello 的 widget 樹。

但是,如果您在此變更後熱重載應用程式,則不會重新執行 main()initState(),並且 widget 樹會使用未變更的 MyApp 執行個體重建為根 widget。這會導致熱重載後沒有可見的變更。

運作方式

#

當叫用熱重載時,主機將會查看自上次編譯以來已編輯的程式碼。將會重新編譯下列程式庫

  • 具有已變更程式碼的任何程式庫
  • 應用程式的主要程式庫
  • 從主要程式庫導向受影響程式庫的程式庫

這些程式庫中的原始碼會編譯為 核心檔案,並傳送到行動裝置的 Dart VM。

Dart VM 會從新的核心檔案重新載入所有程式庫。到目前為止,沒有任何程式碼重新執行。

然後,熱重載機制會導致 Flutter 架構觸發所有現有 widget 和轉譯物件的重建/重新配置/重新繪製。