熱過載
使用 Flutter 的熱過載功能加速開發。
Flutter 的熱過載功能可幫助您快速輕鬆地進行實驗、構建 UI、新增功能和修復錯誤。熱過載的工作原理是將更新後的原始碼檔案注入到 Dart 虛擬機器(Dart runtime)中。在 Dart 虛擬機器使用新版本的欄位和函式更新類之後,Flutter 框架會自動重建元件樹,從而讓您能夠快速檢視更改的效果。

DartPad 中的熱過載演示
如何執行熱過載
#要熱過載 Flutter 應用
-
從受支援的 Flutter 編輯器或終端視窗執行應用。目標可以是物理裝置或虛擬裝置。只有處於除錯模式(debug mode)的 Flutter 應用才能進行熱過載或熱重啟。
-
修改專案中的其中一個 Dart 檔案。大多數型別的程式碼更改都可以進行熱過載;有關需要熱重啟的更改列表,請參閱特殊情況。
-
如果您使用的 IDE/編輯器支援 Flutter 的 IDE 工具並且啟用了“儲存時熱過載”(hot reload on save),請選擇 全部儲存 (
cmd-s/ctrl-s),或點選工具欄上的熱過載按鈕。如果您正在使用命令列透過
flutter run執行應用,請在終端視窗中輸入r。
熱過載操作成功後,您會在控制檯中看到類似於以下的資訊
Performing hot reload...
Reloaded 1 of 448 libraries in 978ms.
應用會更新以反映您的更改,並且應用的當前狀態得以保留。您的應用會從執行熱過載命令之前的位置繼續執行。程式碼已更新,執行繼續進行。

Android Studio 中執行、除錯執行、熱過載和熱重啟的控制元件
只有在更改後的 Dart 程式碼再次執行時,程式碼更改才會產生可見效果。具體來說,熱過載會導致所有現有元件重建。只有參與元件重建的程式碼才會被自動重新執行。例如,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 的構建器
#熱過載不會應用對 CupertinoTabView 的 builder 所做的更改。有關更多資訊,請參閱 Issue 43574。
列舉型別
#當列舉型別更改為常規類,或者常規類更改為列舉型別時,熱過載不起作用。
例如
更改前
enum Color { red, green, blue }
更改後
class Color {
Color(this.i, this.j);
final int i;
final int j;
}
泛型型別
#當修改泛型型別宣告時,熱過載不起作用。例如,以下操作將無法生效
更改前
class A<T> {
T? i;
}
更改後
class A<T, V> {
T? i;
V? v;
}
原生程式碼
#如果您更改了原生程式碼(例如 Kotlin、Java、Swift 或 Objective-C),則必須執行完全重啟(停止並重啟應用)才能使更改生效。
舊狀態與新程式碼結合
#Flutter 的有狀態熱過載可以保留應用的狀態。這種方法使您可以僅檢視最近更改的效果,而無需丟棄當前狀態。例如,如果您的應用要求使用者登入,您可以修改並熱過載導航層級中深層的頁面,而無需重新輸入登入憑據。狀態得以保留,這通常是我們想要的行為。
如果程式碼更改影響了應用的狀態(或其依賴項),則應用所處理的資料可能與從頭開始執行時的資料不完全一致。熱過載之後的結果可能會與熱重啟之後的結果不同。
包含了最近的程式碼更改,但未包含應用狀態
#在 Dart 中,靜態欄位是延遲初始化的。這意味著第一次執行 Flutter 應用並讀取靜態欄位時,它會被設定為初始化器評估後的值。全域性變數和靜態欄位被視為狀態,因此在熱過載期間不會重新初始化。
如果您更改了全域性變數和靜態欄位的初始化器,則需要進行熱重啟或完全重啟以檢視更改。例如,考慮以下程式碼
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')]),
],
),
];
執行應用後,您進行了以下更改
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
),
],
),
];
您執行了熱過載,但更改未生效。
相反,在以下示例中
const foo = 1;
final bar = foo;
void onClick() {
print(foo);
print(bar);
}
首次執行應用時列印 1 和 1。然後,您進行了以下更改
const foo = 2; // modified
final bar = foo;
void onClick() {
print(foo);
print(bar);
}
雖然 const 欄位值的更改總是會熱過載,但靜態欄位的初始化器不會重新執行。從概念上講,const 欄位被視為別名而不是狀態。
Dart 虛擬機器能夠檢測到初始化器的更改,並在需要熱重啟才能生效時進行標記。上述示例中的大多數初始化工作都會觸發該標記機制,但以下情況除外
final bar = foo;
要更新 foo 並在熱過載後檢視更改,請考慮將該欄位重新定義為 const,或者使用 getter 返回值,而不是使用 final。例如,以下任一解決方案都有效
const foo = 1;
const bar = foo; // Convert foo to a const...
void onClick() {
print(foo);
print(bar);
}
const foo = 1;
int get bar => foo; // ...or provide a getter.
void onClick() {
print(foo);
print(bar);
}
有關更多資訊,請閱讀關於 Dart 中 const 和 final 關鍵字的區別。
排除了最近的 UI 更改
#即使熱過載操作看起來成功且沒有生成異常,某些程式碼更改也可能在重新整理後的 UI 中不可見。這種行為在更改應用的 main() 或 initState() 方法後很常見。
通常情況下,如果修改後的程式碼位於根元件的 build() 方法之下,則熱過載會按預期工作。但是,如果修改後的程式碼不會因為元件樹的重建而重新執行,那麼您在熱過載後將看不到其效果。
例如,考慮以下程式碼
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'));
}
}
執行此應用後,將程式碼更改為如下所示
import 'package:flutter/widgets.dart';
void main() {
runApp(const Center(child: Text('Hello', textDirection: TextDirection.ltr)));
}
透過熱重啟,程式從頭開始,執行新版本的 main(),並構建一個顯示文字 Hello 的元件樹。
但是,如果您在此更改後熱過載應用,則不會重新執行 main() 和 initState(),元件樹將使用未更改的 MyApp 例項作為根元件進行重建。這導致熱過載後沒有可見的變化。
工作原理
#當呼叫熱過載時,宿主機(host machine)會檢視自上次編譯以來編輯的程式碼。以下庫將被重新編譯
- 任何包含已更改程式碼的庫
- 應用的主庫
- 從主庫到受影響庫路徑上的所有庫
這些庫中的原始碼被編譯成 kernel 檔案併發送到移動裝置的 Dart 虛擬機器。
Dart 虛擬機器從新的 kernel 檔案中重新載入所有庫。到目前為止,還沒有程式碼被重新執行。
熱過載機制隨後觸發 Flutter 框架對所有現有元件和渲染物件進行重建/重新佈局/重繪。