衆所周知,在Flutter 應用的Debug模式下,當咱們開啓【Hot Reload】功能時,不須要在重啓應用便可看到最新的代碼效果。這種相似於RN、Weex和小程序的熱加載功能是如何作到的呢,它背後的原理是什麼?小程序
Flutter的熱重載(hot reload)功能能夠幫助您在無需從新啓動應用的狀況下快速、輕鬆地進行測試、構建用戶界面、添加功能以及修復錯誤。 經過將更新後的源代碼文件注入正在運行的Dart虛擬機(VM)中來實現熱重載。在虛擬機使用新的的字段和函數更新類後,Flutter框架會自動從新構建widget樹,以便您快速查看更改的效果。數組
咱們編寫一個應用,運行應用程序,而後修改 Flutter APP 工程裏的 Dart 代碼,而後點擊【Hot Reload】按鈕開啓熱重載,以下圖所示。 bash
VS Code 開啓 Hot Reload 。 當咱們修改Dart代碼,點擊保存的時候,就會看到界面已經發生了變化,以下圖。總結一下,在Flutter中使用熱重載須要通過如下幾個步驟:框架
Hot Reload 成功後,會在 Debug Consol 中看到輸出以下相似的消息:less
Performing hot reload...
Reloaded 1 of 448 libraries in 2,777ms.
複製代碼
熱重載是指,在不中斷 App 正常運行的狀況下,動態注入修改後的代碼片斷。而這一切的背後,離不開 Flutter 所提供的運行時編譯能力。爲了更好地理解 Flutter 的熱重載實現原理,咱們先簡單回顧一下 Flutter 編譯模式背後的技術吧。ide
JIT(Just In Time),指的是即時編譯或運行時編譯,在 Debug 模式中使用,能夠動態下發和執行代碼,啓動速度快,但執行性能受運行時編譯影響。 函數
AOT(Ahead Of Time),指的是提早編譯或運行前編譯,在 Release 模式中使用,能夠爲特定的平臺生成穩定的二進制代碼,執行性能好、運行速度快,但每次執行均需提早編譯,開發調試效率低。 性能
能夠看到,Flutter 提供的兩種編譯模式中,AOT 是靜態編譯,即編譯成設備可直接執行的二進制碼;而 JIT 則是動態編譯,即將 Dart 代碼編譯成中間代碼(Script Snapshot),在運行時設備須要 Dart VM 解釋執行。測試
而熱重載之因此只能在 Debug 模式下使用,是由於 Debug 模式下,Flutter 採用的是 JIT 動態編譯(而 Release 模式下采用的是 AOT 靜態編譯)。JIT 編譯器將 Dart 代碼編譯成能夠運行在 Dart VM 上的 Dart Kernel,而 Dart Kernel 是能夠動態更新的,這就實現了代碼的實時更新功能,原理以下圖。ui
整體來講,完成熱重載的能夠分爲掃描工程改動、增量編譯、推送更新、代碼合併、Widget 重建 5 個步驟。
能夠看到,Flutter 提供的熱重載在收到代碼變動後,並不會讓 App 從新啓動執行,而只會觸發 Widget 樹的從新繪製,所以能夠保持改動前的狀態,這就大大節省了調試複雜交互界面的時間。
好比,咱們須要爲一個視圖棧很深的頁面調整 UI 樣式,若採用從新編譯的方式,不只須要漫長的全量編譯時間,而爲了恢復視圖棧,也須要重複以前的屢次點擊交互,才能從新進入到這個頁面查看改動效果。但若是是採用熱重載的方式,不只沒有編譯時間,並且頁面的視圖棧狀態也得以保留,完成熱重載以後立刻就能夠預覽 UI 效果了,至關於進行了局部界面刷新。
Flutter 提供的亞秒級熱重載一直是開發者的調試利器。經過熱重載,咱們能夠快速修改 UI、修復 Bug,無需重啓應用便可看到改動效果,從而大大提高了 UI 調試效率。
不過,Flutter 的熱重載也有必定的侷限性。由於涉及到狀態保存與恢復,因此並非全部的代碼改動均可以經過熱重載來更新。如下是Flutter開發中幾個不支持熱重載的典型場景:
咱們就具體看看這幾種場景的問題,應該如何解決吧!
當代碼更改致使編譯錯誤時,熱重載會提示編譯錯誤信息。好比下面的例子中,代碼中漏寫了一個反括號,在使用熱重載時,編譯器直接報錯,以下所示。
Initializing hot reload...
Syncing files to device iPhone X...
Compiler message:
lib/main.dart:84:23: Error: Can't find ')' to match '('. return MaterialApp( ^ Reloaded 1 of 462 libraries in 301ms. 複製代碼
在這種狀況下,只需更正上述代碼中的錯誤,就能夠繼續使用熱重載。
當代碼更改會影響 Widget 的狀態時,會使得熱重載先後 Widget 所使用的數據不一致,即應用程序保留的狀態與新的更改不兼容。
這時,熱重載也是沒法使用的。好比下面的代碼中,咱們將某個類的定義從 StatelessWidget 改成 StatefulWidget 時,熱重載就會直接報錯,以下所示。
//改動前
class MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return GestureDetector(onTap: () => print('T'));
}
}
//改動後
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> { /*...*/ }
複製代碼
當遇到這種狀況時,咱們須要重啓應用,才能看到更新後的程序的運行效果。
在 Flutter 中,全局變量和靜態屬性都被視爲狀態,在第一次運行應用程序時,會將它們的值設爲初始化語句的執行結果,所以在熱重載期間不會從新初始化。
好比下面的代碼中,咱們修改了一個靜態 Text 數組的初始化元素。雖然熱重載並不會報錯,但因爲靜態變量並不會在熱重載以後初始化,所以這個改變並不會產生效果,代碼以下。
//改動前
final sampleText = [
Text("T1"),
Text("T2"),
Text("T3"),
Text("T4"),
];
//改動後
final sampleText = [
Text("T1"),
Text("T2"),
Text("T3"),
Text("T10"), //改動點
];
複製代碼
若是須要更改全局變量和靜態屬性的初始化語句,須要重啓應用才能查看更改效果。
在 Flutter 中,因爲熱重載以後只會根據原來的根節點從新建立控件樹,所以 main 函數的任何改動並不會在熱重載後從新執行。因此,若是咱們改動了 main 函數體內的代碼,是沒法經過熱重載看到更新效果的。
//更新前
class MyAPP extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Center(child: Text('Hello World', textDirection: TextDirection.ltr));
}
}
void main() => runApp(new MyAPP());
//更新後
void main() => runApp(const Center(child: Text('Hello, 2019', textDirection: TextDirection.ltr)));
複製代碼
因爲 main 函數並不會在熱重載後從新執行,所以以上改動是沒法經過熱重載查看更新的。
在熱重載時,Flutter 會保存 Widget 的狀態,而後重建 Widget。而 initState 方法是 Widget 狀態的初始化方法,這個方法裏的更改會與狀態保存發生衝突,所以熱重載後不會產生效果。
例如,在下面的例子中,咱們將計數器的初始值由 10 改成 100,代碼以下:
//更改前
class _MyHomePageState extends State<MyHomePage> {
int _counter;
@override
void initState() {
_counter = 10;
super.initState();
}
...
}
//更改後
class _MyHomePageState extends State<MyHomePage> {
int _counter;
@override
void initState() {
_counter = 100;
super.initState();
}
...
}
複製代碼
因爲這樣的改動發生在 initState 方法中,所以沒法經過熱重載查看更新,咱們須要重啓應用,才能看到更改效果。
在 Flutter 中,枚舉和泛型也被視爲狀態,所以對它們的修改也不支持熱重載。
好比在下面的代碼中,咱們將一個枚舉類型改成普通類,併爲其增長了一個泛型參數,代碼以下。
//更改前
enum Color {
red,
green,
blue
}
class C<U> {
U u;
}
//更改後
class Color {
Color(this.r, this.g, this.b);
final int r;
final int g;
final int b;
}
class C<U, V> {
U u;
V v;
}
複製代碼
針對上面不能使用 Hot Reload 的狀況,就須要使用 Hot Restart。Hot Restart 能夠徹底重啓您的應用程序,但卻不用結束調試會話。
對於Android Studio來講, 執行 Hot Restart無需 stop操做,再Run 一下,就是 Hot Restart。
對於VS Code 來講,打開命令面板,輸入 **Flutter: Hot Restart ** 或者 直接快捷鍵 Ctrl+F5,就可使用 Hot Restart。
Flutter 的熱重載是基於 JIT 編譯模式的代碼增量同步。因爲 JIT 屬於動態編譯,可以將 Dart 代碼編譯成生成中間代碼,讓 Dart VM 在運行時解釋執行,所以能夠經過動態更新中間代碼實現增量同步。
熱重載的流程能夠分爲 5 步,包括:掃描工程改動、增量編譯、推送更新、代碼合併、Widget 重建。Flutter 在接收到代碼變動後,並不會讓 App 從新啓動執行,而只會觸發 Widget 樹的從新繪製,所以能夠保持改動前的狀態,大大縮短了從代碼修改到看到修改產生的變化之間所須要的時間。
另外一方面,因爲涉及到狀態的保存與恢復,涉及狀態兼容與狀態初始化的場景,熱重載是沒法支持的,如改動先後 Widget 狀態沒法兼容、全局變量與靜態屬性的更改、main 方法裏的更改、initState 方法裏的更改、枚舉和泛型的更改等。
能夠發現,熱重載提升了調試 UI 的效率,很是適合寫界面樣式這樣須要反覆查看修改效果的場景。但因爲其狀態保存的機制所限,熱重載自己也有一些沒法支持的邊界。
若是你在寫業務邏輯的時候,不當心碰到了熱重載沒法支持的場景,也不須要進行漫長的從新編譯加載等待,只要點擊位於工程面板左下角的熱重啓(Hot Restart)按鈕,就能夠以秒級的速度進行代碼從新編譯以及程序重啓了,一樣也很快。