當 Flutter 遇見 Web,會有怎樣的祕密 ?

騰訊IMWeb團隊發表於2019-09-30

本文作者:IMWeb IMWeb團隊 原文出處:IMWeb社群 未經同意,禁止轉載

前言

騰訊 OED 的客戶端團隊在 2019 年上半年 ,就已經把 Flutter 落地到 企鵝輔導 的業務中了。今年我們又一起去上海參加了 2019 年穀歌開發者大會,遇見了更多的 Flutter 開發者,這次體驗比第一次去的時候感覺熟悉了很多。希望未來有機會把他們邀請來深圳,進行一些 Flutter 的技術分享。此次開發者大會又恰逢 Flutter to Web 也已經正式合入 Master,那麼,前端同學是否可以趁著這股東風一起參與到 Flutter 的協同開發中呢,我想這問題會困擾著很多人?如果您有好的想法,可以在留言區參與評論。

本文不是一篇 Flutter 詳細的學習教程,更像是一個概覽,用盡可能平實的語言和對比的思路去描述它。本著依舊從前端同學的角度出發,去理解一項新的技術,但又不限於前端技術本身。希望您能通過這篇文章相對全面的理解 Flutter 這項技術本身。特別感謝領導的鼓勵和支援,讓我有機會去學習和理解 Flutter 框架,因為相對我而言,OED 的客戶端團隊的同學經驗會遠超於我,他們已經完整經歷了業務從 0 到 1 的過程,這是一種非常有意思的體驗。

Flutter 技術架構

為什麼是 Flutter ?

在談及 Flutter 之前,我們還是要先簡單回顧一下,客戶端的上一次技術革新 —— React Native(此後簡稱 RN)。相信非常多的團隊都有去落地實踐 RN 的機會,很多 APP 的首屏渲染方案都是用 RN 技術棧進行的。我們自己的產品 企鵝輔導騰訊課堂 內的應用也是一樣。

這裡簡單回顧一下,在有客戶端開發的場景下,為什麼又出現了 RN ?

RN 的價值簡單來講就是 —— 可接受的頁面效能 + 高效開發 + 熱更新。

更新:傳統的 APP 上架之後,出現了業務 BUG,使用者只能去更新 APP,進行 BUG 修復。客戶端實現熱更新修復 BUG,有多難,可以問問 IOS 的開發同學。大概率猜測,手 Q 和微信,應該還是有方案可以熱更新的。但是對很多小廠商這確實是非常艱難的事情。因此,得益於強大的動態化能力 RN 的價值也就完美的體現出來了。

高效:一個 APP 釋出上線,Android 和 IOS 同時需要開發兩個應用,而 RN 只需要一套程式碼,就可以執行在雙平臺上,節省很大的人力成本。並且很多業務線有很強的業務運營訴求,可能會存在很短時間內的多次改版和釋出的情況出現,客戶端開發的人力瓶頸和釋出週期的限制,已經很難滿足這樣的業務場景了。尤其在一些有損釋出的情況下,趕著時間點,帶著 BUG 上線的場景,在後續進行增量的修復,再這樣的情況下,傳統客戶端的表現,簡直就是災難性的。

效能:RN 具有優於 H5 的效能體驗。畢竟是通過客戶端進行的頁面渲染,速度上比 WebView 渲染還是要快不少的。這個在 Weex、Hippy 上都有所體現,雖然低於 Native 的效能,但是在可接受範圍。

PS:這裡的表達,不是描述客戶端開發不好。只是單純從業務角度上看待問題,而把合適的技術放在合適的位置是非常重要的,這也是架構師核心價值之一。

回顧了以上三點,我們發現 RN 的出現,有它的必然性。那麼回到主題,RN 已經這麼優秀了,為什麼還要有 Flutter 的存在,有一次向 Ab 哥請教技術成長的時候,Ab 哥提到了很有意思的一個觀點,就是您對一項技術瞭解的深入程度,取決於是否能認清這項技術的侷限。 就像人一樣,他(她)有多少優點,就會存在多少缺點。沒發現,不等於不存在,因為一定存在。因此,順著這個思路,我們簡單的看一下 RN 的問題。

首先,看維護成本,雖然 RN 是一套程式碼多端執行。但還是需要 IOS 和 Android 開發幫助我們去一個一個的繪製元件,尤其遇到特殊訴求的時候,還要 case by case 的處理,並且隨著 IOS 和 Android 系統本身的迭代和升級,以及框架自身發展的歷史包袱,我們可能還需要處理很多與原生系統之間的平臺差異,修復各種奇奇怪怪的 BUG,這對業務來說是很大的負擔。

其次,對效能訴求,無論是產品還是開發同學,對於使用者體驗的追求,永遠都不會停止。RN 存在諸多效能的短板,因此,才會有 Weex 這樣的產品出現,去定製化的解決業務場景下的問題。JS 和 Native 的通訊,頁面的事件監聽,複雜動畫的渲染和交換成本,都是很大的效能挑戰。

因此,在存在更強的業務訴求的時候,人們就不得不去尋找更好的方式去實現。非常存感激的看待谷歌這家公司,都是定位於商業公司,但實際上對世界的影響力上面,公司與公司之間差距還是非常大的。這個課題範圍太大,以後有機會可以深度討論一下。

您看到了上面的描述,為了解決上面這些問題 —— 自繪引擎時代出現了,以 Flutter 為代表的技術方案會應運而生,相信一定不會只有 Flutter 一項技術出現的,畢竟,歷史是驚人的相似。其實想到自繪引擎,我最先想到的是那些遊戲引擎。那現在又為什麼給出 自繪引擎 這樣的一個概念呢?H5 是依賴於瀏覽器渲染,RN 依賴於客戶端渲染,而 Flutter 基於 Skia 自己繪製的圖形介面。因此,Flutter 才能真正實現跨端!相信在不久的未來,在傳統客戶端上也能看到 Flutter 的身影,這樣才能真正達到多端統一。想起了一句話 ~ 思路決定出路

最後,我們再簡單總結一下有哪些問題:

1、Web 效能差,跟原生 App 存在肉眼可見的差距;

2、React Native 跟 Web 相比,支援的能力非常有限,特定長場景問題,需要三端團隊一個一個處理;

3、Web 瀏覽器的安卓碎片化嚴重(感謝 X5,騰訊的同學過得相對輕鬆一些)。

為了解決上面的問題,Flutter 出現了:

一套程式碼可以執行在兩端

自繪 UI,脫離平臺,也可以簡單的把它理解為一個瀏覽器的子集。

鋪墊了這麼多,就是為了幫助您回憶起技術發展的脈絡和技術趨勢,可以更好的理解下面即將要表達的文稿,下面我們正式開始介紹 Flutter。

Flutter 實現原理

Flutter 能介紹的技術點其實非常多,這裡找了一些具有代表性的技術項,結合自己的理解跟大家分享一下。包括設計思路、渲染方式、UI 的生命週期。因為這幾個點,跟 React 技術棧風格非常相似,以這種思考結構去對比介紹,可以幫助大家更好的理解這項技術本身。

Flutter 整體架構設計

當 Flutter 遇見 Web,會有怎樣的祕密 ?

Google 了一下關鍵詞,搜素得到了這張圖片,從下向上進行一些描述:

Embedder:是作業系統適配層,實現了渲染 Surface 設定,執行緒設定,以及平臺外掛等平臺相關特性的適配。從這裡我們可以看到,Flutter 平臺相關特性並不多,這就使得從框架層面保持跨端一致性的成本相對較低。

Flutter Engine:這是一個純 C++實現的 SDK,其中囊括了 Skia 引擎、Dart 執行時、文字排版引擎等。不過說白了,它就是 Dart 的一個執行時,它可以以 JIT、JIT Snapshot 或者 AOT 的模式執行 Dart 程式碼。在程式碼呼叫 dart:ui 庫時,提供 dart:ui 庫中 Native Binding 實現。 不過別忘了,這個執行時還控制著 VSync 訊號的傳遞、GPU 資料的填充等,並且還負責把客戶端的事件傳遞到執行時中的程式碼。具體的繪製方式,我們放在後面描述。

Flutter Framework:這是一個純 Dart 實現的 SDK,類似於 React 在 JavaScript 中的作用。它實現了一套基礎庫, 用於處理動畫、繪圖和手勢。並且基於繪圖封裝了一套 UI 元件庫,然後根據 Material 和 Cupertino 兩種視覺風格區分開來。這個純 Dart 實現的 SDK 被封裝為了一個叫作 dart:ui 的 Dart 庫。我們在使用 Flutter 寫 App 的時候,直接匯入這個庫即可使用元件等功能。

  • Framework 這一層是與開發者相關性最強的一層,逐一介紹一下,其中的模組:
  • Foundation: 在最底層,主要定義底層工具類和方法,以提供給其他層使用。
  • Animation:是動畫相關的類,可以基於此建立補間動畫(Tween Animation)和物理原理動畫(Physics-based Animation),類似 Android 的 ValueAnimator 和 iOS 的 Core Animation。
  • Painting:封裝了 Flutter Engine 提供的繪製介面,例如繪製縮放影像、插值生成陰影、繪製盒模型邊框等。
  • Gesture:提供處理手勢識別和互動的功能
  • Rendering:是框架中的渲染庫。控制元件的渲染主要包括三個階段:佈局(Layout)、繪製(Paint)、合成(Composite)。

PS:雖然很早知道 Flutter,但實際寫 Flutter 時間也比較短暫。引擎原始碼層面,目前也沒有深入的涉獵。瞭解的方式可以通過自己閱讀原始碼,或者找谷歌、阿里、美團、以及我司的開發者幫忙。從技術角度來了解這些,在需要的階段,不會成為大家的瓶頸。畢竟商業世界充滿了壁壘,而應用層面的技術本身是開放的。

Flutter 應用層語言
  • 簡單描述一下 JIT 與 AOT

JIT 在執行時即時編譯,在開發週期中使用,可以動態下發和執行程式碼,開發測試效率高,但執行速度和執行效能則會因為執行時即時編譯受到影響。

AOT 即提前編譯,可以生成被直接執行的二進位制程式碼,執行速度快、執行效能表現好,但每次執行前都需要提前編譯,開發測試效率低。

  • Dart 是什麼

它的目標在於成為下一代結構化 Web 開發語言。Dart 釋出於 2011 年 10 月 Google 的 "GOTO 國際軟體開發大會"。是一種基於類程式語言(class-based programming language),在所有瀏覽器都能夠有高效能的執行效率。Chrome 瀏覽器內建了 Dart VM,可以直接高效的執行 dart 程式碼(2015 年被移出)。支援 Dart 程式碼轉成 Javascript,直接在 Javascript 引擎上執行。dart2js

  • Dart 的特點

開發時 JIT,提升開發效率;釋出時 AOT,提升效能。不會面對 JS 與 Native 之間互動的問題了。 Dart 的記憶體策略,採用多生代演算法(與 Node 有一些類似)。執行緒模型依舊是單執行緒 Event Loop 模型,通過 isolate 進行隔離,可以降低開發難度(與 Node 也非常類似)。 Dart 的生態,這個跟 Node.js 差距十分明顯,npm 還是行業中最活躍的。 而靜態語法與排版方式,純前端入門還是有一定成本。

備註:(1)TS 可以一定程度上幫助 JS 新增一些靜態檢測,但本質上依舊是無法達成這樣的效果;(2) 關於入門成本這個問題,如果您想深入,我相信這都不會成為問題。關鍵看是否能為業務和團隊帶來價值。

  • Flutter 選擇 Dart 的原因

健全的型別系統,同時支援靜態型別檢查和執行時型別檢查。 程式碼體積優化(Tree Shaking),編譯時只保留執行時需要呼叫的程式碼(不允許反射這樣的隱式引用),所以龐大的 Widgets 庫不會造成釋出體積過大。 豐富的底層庫,Dart 自身提供了非常多的庫。多生代無鎖垃圾回收器,專門為 UI 框架中常見的大量 Widgets 物件建立和銷燬優化。跨平臺,iOS 和 Android 共用一套程式碼。JIT & AOT 執行模式,支援開發時的快速迭代和正式釋出後最大程度發揮硬體效能。Native Binding。在 Android 上,v8 的 Native Binding 可以很好地實現,但是 iOS 上的 JavaScriptCore 不可以,所以如果使用 JavaScript,Flutter 基礎框架的程式碼模式就很難統一了。而 Dart 的 Native Binding 可以很好地通過 Dart Lib 實現。

Flutter 實現思路

看到了上面的介紹,這裡總結一下 Flutter 的實現思路。它開闢了新的設計理念,實現了真正的跨平臺的方案,自研 UI 框架,它的渲染引擎是 Skia 圖形庫來實現的,而開發語言選擇了同時支援 JIT 和 AOT 的 Dart。不僅保證了開發效率,同時也提升了執行效率。由於 Flutter 自繪 UI 的實現方式,因此也儘可能的減少了不同平臺之間的差異。也保持和原生應用一樣的高效能。因此,Flutter 也是跨平臺開發方案中最靈活和徹底的那個,它重寫了底層渲染邏輯和上層開發語言的一整套完整解決方案

  • 徹底跨端:

    • Flutter 構建了一整套包括底層渲染、頂層設計的全套開發套件。
    • 這樣不僅可以保證檢視渲染在 Android 和 IOS 上面的高度一致,也可以保證渲染和互動效能(媲美原生應用)。
  • 與現有方案核心區別:

    • 類 RN 方案,JS 開發,Native 渲染。資料通訊 bridge;
    • Hybird 瀏覽器渲染 + 原生元件繪製;
    • Flutter 設計自閉環,完成渲染和資料通訊;

UI 渲染 方案

談到 UI 渲染方案,作為前端開發,我們是繞不過現在如火如荼的三大框架的。為什麼要談 類 React 方案呢?因為 Flutter 的設計方案,與 React 設計具有一樣的思路。在渲染這裡我們會談及 控制元件、渲染原理、以及生命週期。

Flutter 是如何進行頁面渲染的呢?傳統 Web 是通過瀏覽器,而 Flutter 是自繪。所謂自繪就是使用者介面上 Flutter 自己繪製到介面,無需依賴 Ios 和 Android 原生能力,是通過一個叫做 Skia 引擎進行頁面繪圖。

介紹一下 Skia

Skia 是一個 2D 的繪圖引擎庫,其前身是一個向量繪圖軟體,Chrome 和 Android 均採用 Skia 作為繪圖引擎。Skia 提供了非常友好的 API,並且在圖形轉換、文字渲染、點陣圖渲染方面都提供了友好、高效的表現。Skia 是跨平臺的,所以可以被嵌入到 Flutter 的 iOS SDK 中,而不用去研究 iOS 閉源的 Core Graphics / Core Animation。

Skia 是用 C++ 開發的、效能彪悍的 2D 影像繪製引擎,其前身是一個向量繪圖軟體。 Skia 在圖形轉換、文字渲染、點陣圖渲染方面都表現卓越,並提供了開發者友好的 API。Android 自帶了 Skia,所以 Flutter Android SDK 要比 iOS SDK 小很多。正是得益於 Skia 的存在:

  • Flutter 底層的渲染能力得到了統一,不在需要使用做雙端適配;
  • 通過 OpenGL、GPU,不需要依賴原生的元件渲染框架。
  • Flutter 可以最大限度的抹平平臺差異,提升渲染效率和效能。
Flutter 的渲染流程

使用者可以看到一張影像展示,至少需要三類介質:CPU、GPU 和 顯示器。CPU 負責影像的資料計算,GPU 負責影像資料的渲染,而顯示器是最終圖片展示的載體。CPU 拿到需要上屏的資料做處理和加工,處理完成之後交給 GPU,GPU 在渲染之後將資料放入幀緩衝區,隨後隨著控制同步訊號 (VSync) 以週期性的頻率,從緩衝區內讀出資料,在顯示器上進行影像呈現。而且作業系統就是一個無限迴圈的機制,不停的重複上面的操作,進行顯示器的更新.

當 Flutter 遇見 Web,會有怎樣的祕密 ?

Flutter 的渲染整體流程也是這樣的, Dart 進行檢視資料的合成,然後交給 Skia 引擎進行處理,處理之後再交給 GPU 進行資料合成,然後準備上屏。當一幀影像繪製完畢後準備繪製下一幀時,顯示器會發出一個垂直同步訊號(VSync),所以 60Hz 的螢幕就會一秒內發出 60 次這樣的訊號。

Flutter 繪製流程

當 Flutter 遇見 Web,會有怎樣的祕密 ?

如上圖所示,Flutter 渲染流程分為 7 個步驟:

首先是獲取到使用者的操作,然後你的應用會因此顯示一些動畫

接著 Flutter 開始構建 Widget 物件。

Widget 物件構建完成後進入渲染階段,這個階段主要包括三步:

  • 佈局元素:決定頁面元素在螢幕上的位置和大小;
  • 繪製階段:將頁面元素繪製成它們應有的樣式;
  • 合成階段:按照繪製規則將之前兩個步驟的產物組合在一起

最後的光柵化由 Engine 層來完成。

佈局

佈局時 Flutter 深度優先遍歷渲染物件樹。資料流的傳遞方式是從上到下傳遞約束,從下到上傳遞大小。也就是說,父節點會將自己的約束傳遞給子節點,子節點根據接收到的約束來計算自己的大小,然後將自己的尺寸返回給父節點。整個過程中,位置資訊由父節點來控制,子節點並不關心自己所在的位置,而父節點也不關心子節點具體長什麼樣子。

當 Flutter 遇見 Web,會有怎樣的祕密 ?

為了防止因子節點發生變化而導致的整個控制元件樹重繪,Flutter 加入了一個機制——Relayout Boundary,在一些特定的情形下 Relayout Boundary 會被自動建立,不需要開發者手動新增。

邊界:Flutter 使用邊界標記需要重新佈局和重新繪製的節點部分,這樣就可以避免其他節點被汙染或者觸發重建。就是控制元件大小不會影響其他控制元件時,就沒必要重新佈局整個控制元件樹。有了這個機制後,無論子樹發生什麼樣的變化,處理範圍都只在子樹上。

當 Flutter 遇見 Web,會有怎樣的祕密 ?

快取:要提升效能表現,快取也是少不了的。在 Flutter 中,幾乎所有的 Element 都會具有一個 key,這個 key 是唯一的。當子樹重建後,只會重新整理 key 不同的部分。而節點資料的複用就是依靠 key 來從快取中取得。

在確定每個空間的位置和大小之後,就進入繪製階段。繪製節點的時候也是深度遍歷繪製節點樹,然後把不同的 RenderObject 繪製到不同的圖層上。

繪製

在佈局完成之後,渲染物件樹中的每個節點都有了明確的尺寸和位置。Flutter 會把所有的 Element 繪製到不同的圖層上。與佈局過程類似,繪製的過程也是深度優先遍歷,先繪製父節點,然後繪製子節點。以下圖為例:節點 1、節點 2、節點 3、4、5,最後繪製節點 6。

當 Flutter 遇見 Web,會有怎樣的祕密 ?

如上圖可以看到一種場景,就是比如檢視可能會合並,導致 節點 2 的子節點 5 與 它的 兄弟節點 6 處於同一個圖層,這樣會導致當 節點 2 需要重繪的時候,與其無關的節點 6 也會被重繪,帶來效能問題。

為了解決上面的問題,Flutter 提出了佈局邊界的機制 —— 重繪邊界(Repaint-Boundary)。在重繪邊界內,Flutter 會強制切換新的圖層,這樣可以避免邊界內外的互相影響,避免無關內容雖然處於同一個層級導致的不必要的重繪。

當 Flutter 遇見 Web,會有怎樣的祕密 ?

重繪邊界的一個典型場景就是 ScrollView。ScorllView 滾動的時候會重新整理檢視,從而觸發內容重繪,而當滾動內容重繪時,一般情況下其它內容是不需要被重繪的。這個時候重繪邊界就非常有價值了。

這裡思路就是更精細化的對元件的更新進行最小範圍的控制。

合成和渲染

最上面已經展示了 Flutter 的 7 層渲染流水線(Rendering pipline)的圖裡。這裡主要描述一下對合成和渲染的理解。渲染流水線是由垂直同步訊號(Vsync)驅動的。這個概念很類似我們平時說的 FPS 的概念,每秒 60 幀,過低的頻率會顯得頁面很卡。

當每一次 Vsync 訊號到來以後,Flutter 框架會按照圖裡的順序執行一系列動作:

動畫(Animate)、構建(Build)、佈局(Layout)和繪製(Paint)

最終生成一個場景(Scene)之後送往底層,由 GPU 繪製到螢幕上。

當 Flutter 遇見 Web,會有怎樣的祕密 ?

Flutter App 只有在狀態發生變化的時候需要觸發渲染流水線。當你的 App 無任何狀態改變的時候,Flutter 是不需要重新渲染頁面的。所以,Vsync 訊號需要 Flutter App 去排程。比如,我們在 Widget 內使用了 setState 方法改變了控制元件的狀態。

整個渲染流水線是執行在 UI 執行緒裡的,以 Vsync 訊號為驅動,在框架渲染完成之後會輸出 Layer Tree。Layer Tree 被送入 Engine,Engine 會把 Layer Tree 排程到 GPU 執行緒,在 GPU 執行緒內合成(compsite)Layer Tree,然後由 Skia 2D 渲染引擎渲染後送入 GPU 顯示。這裡提到 Layer Tree 是因為我們即將要分析的渲染流水線繪製階段最終輸出就是這樣的 Layer Tree。所以繪製階段並不是簡單的呼叫 Paint 函式這麼簡單了,而是很多地方都涉及到 Layer Tree 的管理。

Flutter 只關心向 GPU 提供檢視資料,GPU 的 VSync 訊號同步到 UI 執行緒,UI 執行緒使用 Dart 來構建抽象的檢視結構,這份資料結構在 GPU 執行緒進行圖層合成,檢視資料提供給 Skia 引擎渲染為 GPU 資料,這些資料通過 OpenGL 或者 Vulkan 提供給 GPU。

這裡描述一下合成的概念,所謂合成就是因為我們繪製的頁面結構複雜,如果直接交付給繪圖引擎去進行圖層渲染,可能會出現大量的渲染內容重繪,因此,需要先進性一次圖層合成,就是說先把所有的圖層根據大小、層級等規則計算出最終的顯示效果,將相同的圖層合併,簡化渲染樹,提升渲染效率。

Flutter 會將合成之後的資料,交給 Skia 進行頁面二維圖層的渲染。

看到了 Widget,會想起什麼?

Flutter 繪製介面的基礎是 Widget,也就是描述頁面的最小模組。

  • Flutter 的核心設計思想就是 "一切皆 Widget"

    • 前端同學可以把 Widget 理解為 Web Component 的 元件 即可。

    • 一種結構化資料的抽象,包含了元件的佈局、渲染屬性、事件響應資訊等。

在這一個部分我們對比著 React 的設計方式對比著看一下 Flutter 的實現,在 React 中您可以看到三種很重要的名稱。JSX、Virtual Dom、真實 Dom,而在 Flutter 中我們依然可以看到對應的三類抽象的資料結構分別是 Widget、Element 和 RenderObject,他們的功能與 React 內三個資料抽象有異曲同工之處。

Widget 類似 React VM 的 F(x) = Y 中的 x 存在

Flutter 中的 Widget 是完全不可變的!只要當檢視發生變化,Flutter 就會重新建立一個新的 Widget 進行更新。即是 React 也是有一定的資料 Diff 的策略,而這裡變更即建立的方式,會帶來大量的銷燬和重建的過程,是否非常消耗效能?

Widget 對標的是 標識 React 的虛擬 DOM 節點的 資料描述 JSX,不是真實渲染的頁面 DOM。只是資料的抽象,不涉及檢視渲染。並且 Widget 具有不可變性,也提升了 Widget 本身的複用性。因此並沒大量的效能消耗,而 Dart 的作為靜態語言的執行速度,也會有著超越 JS 的效能。

Element 是 Widget 的一個例項化物件

Element 承載了檢視構建的上下文資料,是連線結構化的配置資訊到完成最終渲染的橋樑; Element 是一個可變的資料結構, 可以大致理解為 Virtual DOM。可以進行 diff 更新; 可以將真正需要修改的資料同步到 RenderObject 中。最大程度的降低渲染檢視的修改,提升渲染效率。

RenderObject 負責檢視渲染的物件

Flutter 的渲染分為 4 個部分。佈局、繪製、合成、渲染,其中 佈局和繪製是在 RenderObject 中完成的。 Flutter 採用深度優的方式渲染物件樹,確定樹中的各個物件的位置和尺寸,並把它繪製到不同圖層, 繪製完成之後交給 Skia 在 VSync 訊號同步時從渲染樹合成點陣圖,然後交給 CPU 進而完成上屏。

Widget 同樣分為有狀態 和 無狀態元件

無狀態控制元件 StatelessWidget 類似 React 的 PFC。 有狀態控制元件 StatefulWidget 就是 React 的 元件。 如同 react 元件一樣,使用有狀態元件是有成本的。正確的評估你的需求,避免使用無意義的有狀態元件。

這裡比較大的區別,是 Flutter 直接把 Widget 設計成為了一個不可變的! 這也導致了技術方案的實現上存在了差異。

既然看到了 Widget,那一定會有生命週期

每當您看到元件、狀態、檢視這些名詞的時候,伴隨著它們的存在,一定會存在一個叫生命週期的概念。是的 ,Flutter 也存在它的生命週期。

元件 生命週期分為三個階段:

建立

建構函式 --> initState --> didChangeDependencies --> build

State:構造方法是生命週期的起點,Flutter 會通過 StatefulWidget.createState 來建立一個 State。我們可以通過初始化方法,接收父 Widget 傳遞過來的初始化 UI 配置引數,這些配置引數決定了 Widget 的最初配置效果

initState:會在 State 物件被插入檢視樹的時候呼叫,這個函式在 State 的生命週期中只會被呼叫一次,所以我們可以在這裡做一些初始化工作,比如為狀態變數設定預設值。

didChangeDependencies:則用來專門處理 State 物件依賴關係變化,會在 initSate()呼叫結束後被 Flutter 呼叫。

build:作用是構建檢視。通過以上步驟,Framework 認為 Sate 已經準備好了,於是呼叫 build。我們需要在這個函式中,根據父 Widget 傳遞過來的初始化配置資料,以及 State 的當前狀態,建立一個 Widget,然後返回。

更新

Widget 的狀態更新,主要由三個方法觸發:setState、didChangeDependencies 和 didUpdateWidget。

setState:是 我們最熟悉的方式,控制元件內更新,然後從新 build。

didChangeDependencies:State 物件的依賴關係發生變化時,Flutter 會回撥這個方法,隨後觸發元件構建。哪些情況下 State 物件的依賴關係會發生變化呢?典型場景是,系統語言 Locale 或者應用主題改變時,系統會通知 Sate 執行 didChangeDependencies 回撥方法。

didUpdateWidget:當 Widget 的配置發生變化時,比如,父 Widget 觸發重建(即父 Widget 的狀態發生變化)時,熱過載時,系統會呼叫這個函式。

一旦這三個函式被呼叫,Flutter 隨後就會銷燬老 Widget,並呼叫 build 方法重建 Widget。

銷燬

元件的銷燬相對比較簡單。比如元件被移除,或是頁面銷燬的時候,系統會呼叫 diactivate 和 dispose 這兩個方法,來移除或銷燬元件。

當元件的可見狀態發生變化時,deactivate 函式會被呼叫,這時 Sate 會被暫時從檢視樹中移除。值得注意的是,頁面切換時,由於 State 物件在檢視樹中的位置發生了變化,需要暫時移除後再重新新增,重新觸發元件構建,因此這個函式也會被呼叫。

當 State 物件被永久地從檢視樹中移除時,Flutter 會呼叫 dispose 函式。而一旦到這個階段,元件就要被銷燬了,所以我們可以在這裡進行最終的資源釋放、移除監聽、清理環境。

APP 生命週期

從後臺切入前臺,控制檯列印的 App 生命週期變化如下:

AppLifecycleState.paused->AppLifecycleState.inactive->AppLifecycleState.resumed;

從前臺退到後臺,控制檯列印的 App 生命週期變化如下:

AppLifecycleState.resumed->AppLifecycleState.inactive->AppLifecycleState.paused;

更詳細的生命週期詳解,您可以 Google 搜尋 Flutter 生命週期 ,Github 上美團的工程師也有一個例子

當 Flutter 遇見 Web,會有怎樣的祕密 ?

粗略了整理了一下體驗學習過程中關於 Flutter 基本的部分,這上面大部分分支已經通過程式碼體驗和實踐過了。這裡就不在一一的介紹了。這裡找了三個例項,跟您分享,您可以 clone 下來,跟細節的體驗一下:

佈局案例:github.com/yang7229693…

程式碼例項: github.com/nisrulz/flu…

FlutterDemo: github.com/OpenFlutter…

除了我上面列出的這些,還有很多要做,比如 運維、除錯、自動化測試、相容性、客戶端 SDK 封裝、國際化等等。當然對於開發者來說,工程化、除錯體驗、元件化都是非常重要的。

Flutter to Web 測試

這裡我轉換的程式碼是我們的 APP 裡面的業務程式碼。詳細的操作流程 Flutter 官方文件具有很好的說明。如果單純轉一個完全不依賴 APP 的工程,估計您安裝完環境就可以使用了。轉換商業產品的程式碼,還是要處理一些奇奇怪怪的問題,相信這對您都不會是問題。您可以在這裡 安裝和環境配置 進行環境配置。Flutter 官網提供了一個 案例 您可以嘗試一下。官方給了一個開箱即學的 開發文件

當 Flutter 遇見 Web,會有怎樣的祕密 ?

上面的視訊裡面展示我們 企鵝輔導 的第三個 TAB 內的 Flutter 業務,以及轉換後的 Web 頁面。可以明顯看到,有一區域性確實有些失真,但是完全可用。這裡頁面渲染有一部分 Canvas 渲染DOM 填充 進行的頁面展示。Dart 當年天然支援在 Chrome 中使用,並且長期以來一直支援轉換為 JavaScript。因此,可以遇見的未來,隨著 Flutter 的發展,Dart To Js 業務實踐的進化速度,可能會超過 WASM 的使用

這裡需要解決一些問題,整理了一下官方建議和實踐的體驗:

首先還不建議,在產品化中使用,但既然已經合入 Master,相信這一天也不會遠了。

當 Flutter 遇見 Web,會有怎樣的祕密 ?

業務使用的時候,需要把系統依賴解決掉,比如:本地儲存、網路請求這些。如客戶端使用的是 WNS,而前端需要使用的是 HTTPS。對 3D 動畫依賴比較嚴重的業務,短期就不要選擇 Flutter 作為業務選選了。Flutter to Web 未來作為業務容災的策略還是可以的。

引文

Flutter 官網

Flutter 中文網

Flutter 實戰

閒魚技術博文

總結

這裡再次感謝前人的沉澱,確實要學習的東西太多了,實際想要寫的文章,遠比這裡面描述要多很多,但是由於時間成本過高,不得不砍掉了非常多的內容。後面隨著對 Flutter 更深入的瞭解,有機會再跟您更詳細的分享 Flutter 的內部設計原理。我只是知識的搬運工,在應用層領域作為開發,最大的價值就是服務好產品,最大限度的用技術滿足產品訴求。至於核心的底層開發建設,人生能做到什麼程度,要看緣分;但是最大限度的服務好業務,只要有責任心就可以了。

在此,也特別感謝領導的鼓勵,去嘗試體驗 Flutter 這項技術。瞭解和認知只是一個開始,後面如果有機會,也可以做一些業務嘗試。在行業內部阿里的閒魚做的還是非常深入,美團的小夥伴也有深度的嘗試,感謝他們對行業的貢獻。

當然,我更希望您是前端,並且也對 Flutter 實踐有著興趣,但是又缺少落地的專案,您也可以聯絡我們,團隊可以給您提供一個靶場,進行業務實踐的落地。光說不練都是假把式,核心還是要用業務去砸!

關注我們

IMWeb 團隊隸屬騰訊公司,是國內最專業的前端團隊之一。

我們專注前端領域多年,負責過 QQ 資料、QQ 註冊、QQ 群等億級業務。目前聚焦於線上教育領域,精心打磨 騰訊課堂、企鵝輔導 及 ABCMouse 三大產品。

社群官網

imweb.io/

加入我們

careers.tencent.com/jobdesc.htm…

當 Flutter 遇見 Web,會有怎樣的祕密 ?

掃碼關注 IMWeb前端社群公眾號,獲取最新前端好文

微博、掘金、Github、知乎可搜尋 IMWebIMWeb團隊關注我們。

相關文章