<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在上一篇文章中,我與你分享了 Flutter 的 Debug 與 Release 編譯模式,以及如何通過斷言與編譯常數來精準識別當前代碼所運行的編譯模式,從而寫出只在 Debug 或 Release 模式下生效的代碼。 另外,對于在開發期與發布期分別使用不同的配置環境,Flutter 也提供了支持。我們可以將應用中可配置的部分進行封裝抽象,使用配置多入口的方式,通過 InheritedWidget 來為應用的啟動注入環境配置。 如果你有過原生應用的開發經歷,那你一定知道在原生應用開發時,如果我們想要在硬件設備上看到調整后的運行效果,在完成了代碼修改后,必須要經過漫長的重新編譯,才能同步到設備上。 而 Flutter 則不然,由于 Debug 模式支持 JIT,并且為開發期的運行和調試提供了大量優化,因此代碼修改后,我們可以通過亞秒級的熱重載(Hot Reload)進行增量代碼的快速刷新,而無需經過全量的代碼編譯,從而大大縮短了從代碼修改到看到修改產生的變化之間所需要的時間。 比如,在開發頁面的過程中,當我們點擊按鈕出現一個彈窗的時候,發現彈窗標題沒有對齊,這時候只要修改標題的對齊樣式,然后保存,在代碼并沒有重新編譯的情況下,標題樣式就發生了改變,感覺就像是在 UI 編輯面板中直接修改元素樣式一樣,非常方便。 那么,Flutter 的熱重載到底是如何實現的呢? ## 熱重載 熱重載是指,在不中斷 App 正常運行的情況下,動態注入修改后的代碼片段。而這一切的背后,離不開 Flutter 所提供的運行時編譯能力。為了更好地理解 Flutter 的熱重載實現原理,我們先簡單回顧一下 Flutter 編譯模式背后的技術吧。 * JIT(Just In Time),指的是即時編譯或運行時編譯,在 Debug 模式中使用,可以動態下發和執行代碼,啟動速度快,但執行性能受運行時編譯影響; :-: ![](https://img.kancloud.cn/ab/69/ab692d1e072df378bc78fef6245205a3_1502x542.png) 圖 1 JIT 編譯模式示意圖 * AOT(Ahead Of Time),指的是提前編譯或運行前編譯,在 Release 模式中使用,可以為特定的平臺生成穩定的二進制代碼,執行性能好、運行速度快,但每次執行均需提前編譯,開發調試效率低。 :-: ![](https://img.kancloud.cn/fe/87/fe8712b8a36a032b0646ed85fec9b2a5_1308x392.png) 圖 2 AOT 編譯模式示意圖 可以看到,Flutter 提供的兩種編譯模式中,AOT 是靜態編譯,即編譯成設備可直接執行的二進制碼;而 JIT 則是動態編譯,即將 Dart 代碼編譯成中間代碼(Script Snapshot),在運行時設備需要 Dart VM 解釋執行。 而熱重載之所以只能在 Debug 模式下使用,是因為 Debug 模式下,Flutter 采用的是 JIT 動態編譯(而 Release 模式下采用的是 AOT 靜態編譯)。JIT 編譯器將 Dart 代碼編譯成可以運行在 Dart VM 上的 Dart Kernel,而 Dart Kernel 是可以動態更新的,這就實現了代碼的實時更新功能。 :-: ![](https://img.kancloud.cn/2d/fb/2dfbedae7b95dd152a587070db4bb9fa_900x528.png) 圖 3 熱重載流程 總體來說,**熱重載的流程可以分為掃描工程改動、增量編譯、推送更新、代碼合并、Widget 重建 5 個步驟:** 1. 工程改動。熱重載模塊會逐一掃描工程中的文件,檢查是否有新增、刪除或者改動,直到找到在上次編譯之后,發生變化的 Dart 代碼。 2. 增量編譯。熱重載模塊會將發生變化的 Dart 代碼,通過編譯轉化為增量的 Dart Kernel 文件。 3. 推送更新。熱重載模塊將增量的 Dart Kernel 文件通過 HTTP 端口,發送給正在移動設備上運行的 Dart VM。 4. 代碼合并。Dart VM 會將收到的增量 Dart Kernel 文件,與原有的 Dart Kernel 文件進行合并,然后重新加載新的 Dart Kernel 文件。 5. Widget 重建。在確認 Dart VM 資源加載成功后,Flutter 會將其 UI 線程重置,通知 Flutter Framework 重建 Widget。 可以看到,Flutter 提供的熱重載在收到代碼變更后,并不會讓 App 重新啟動執行,而只會觸發 Widget 樹的重新繪制,因此可以保持改動前的狀態,這就大大節省了調試復雜交互界面的時間。 比如,我們需要為一個視圖棧很深的頁面調整 UI 樣式,若采用重新編譯的方式,不僅需要漫長的全量編譯時間,而為了恢復視圖棧,也需要重復之前的多次點擊交互,才能重新進入到這個頁面查看改動效果。但如果是采用熱重載的方式,不僅沒有編譯時間,而且頁面的視圖棧狀態也得以保留,完成熱重載之后馬上就可以預覽 UI 效果了,相當于局部界面刷新。 ## 不支持熱重載的場景 Flutter 提供的亞秒級熱重載一直是開發者的調試利器。通過熱重載,我們可以快速修改 UI、修復 Bug,無需重啟應用即可看到改動效果,從而大大提升了 UI 調試效率。 不過,Flutter 的熱重載也有一定的局限性。因為涉及到狀態保存與恢復,所以并不是所有的代碼改動都可以通過熱重載來更新。 接下來,我就與你介紹幾個不支持熱重載的典型場景: * 代碼出現編譯錯誤; * Widget 狀態無法兼容; * 全局變量和靜態屬性的更改; * main 方法里的更改; * initState 方法里的更改; * 枚舉和泛類型更改。 現在,我們就具體看看這幾種場景的問題,應該如何解決吧。 ## 代碼出現編譯錯誤 當代碼更改導致編譯錯誤時,熱重載會提示編譯錯誤信息。比如下面的例子中,代碼中漏寫了一個反括號,在使用熱重載時,編譯器直接報錯: ~~~ 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 的狀態時,會使得熱重載前后 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"), // 改動點 ]; ~~~ 如果我們需要更改全局變量和靜態屬性的初始化語句,重啟應用才能查看更改效果。 ## main 方法里的更改 在 Flutter 中,由于熱重載之后只會根據原來的根節點重新創建控件樹,因此 main 函數的任何改動并不會在熱重載后重新執行。所以,如果我們改動了 main 函數體內的代碼,是無法通過熱重載看到更新效果的。 在第 1 篇文章“[預習篇 · 從零開始搭建 Flutter 開發環境](https://time.geekbang.org/column/article/104051)”中,我與你介紹了這種情況。在更新前,我們通過 MyApp 封裝了一個展示“Hello World”的文本,在更新后,直接在 main 函數封裝了一個展示“Hello 2019”的文本: ~~~ // 更新前 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 函數并不會在熱重載后重新執行,因此以上改動是無法通過熱重載查看更新的。 ## initState 方法里的更改 在熱重載時,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; } ~~~ 這兩類更改都會導致熱重載失敗,并生成對應的提示消息。同樣的,我們需要重啟應用,才能查看到更改效果。 ## 總結 好了,今天的分享就到這里,我們總結一下今天的主要內容吧。 Flutter 的熱重載是基于 JIT 編譯模式的代碼增量同步。由于 JIT 屬于動態編譯,能夠將 Dart 代碼編譯成生成中間代碼,讓 Dart VM 在運行時解釋執行,因此可以通過動態更新中間代碼實現增量同步。 熱重載的流程可以分為 5 步,包括:掃描工程改動、增量編譯、推送更新、代碼合并、Widget 重建。Flutter 在接收到代碼變更后,并不會讓 App 重新啟動執行,而只會觸發 Widget 樹的重新繪制,因此可以保持改動前的狀態,大大縮短了從代碼修改到看到修改產生的變化之間所需要的時間。 而另一方面,由于涉及到狀態保存與恢復,因此涉及狀態兼容與狀態初始化的場景,熱重載是無法支持的,比如改動前后 Widget 狀態無法兼容、全局變量與靜態屬性的更改、main 方法里的更改、initState 方法里的更改、枚舉和泛型的更改等。 可以發現,熱重載提高了調試 UI 的效率,非常適合寫界面樣式這樣需要反復查看修改效果的場景。但由于其狀態保存的機制所限,熱重載本身也有一些無法支持的邊界。 如果你在寫業務邏輯的時候,不小心碰到了熱重載無法支持的場景,也不需要進行漫長的重新編譯加載等待,只要點擊位于工程面板左下角的熱重啟(Hot Restart)按鈕,就可以以秒級的速度進行代碼重新編譯以及程序重啟了,同樣也很快。 ## 思考題 最后,我給你留下一道思考題吧。 你是否了解其他框架(比如 React Native、Webpack)的熱重載機制?它們的熱重載機制與 Flutter 有何區別?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看