iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 24
1
Mobile Development

Why Flutter why? 從表層到底層,從如何到為何。系列 第 24

days[23] = "Flutter Web是怎麼運作的?(上)"

就和之前提的狀態管理一樣,Web的支援在Flutter社群也是一個熱門到有點好笑的話題,幾乎每一兩週就會有人問「現在stable了嗎?」「可以上production了嗎?」
https://ithelp.ithome.com.tw/upload/images/20200924/20129053m63qcq525s.png
和狀態管理不同的是,狀態管理只要是在幾個比較熱門/成熟的套件裡面任意選一個,其實對APP開發的幫助或影響程度都不會差太多,到頭來比較多只是開發者個人偏好的問題。但Flutter Web的成熟就真的會對APP開發的結果產生重大影響,不但客群直接多了一大半,也直接關係到團隊裡還需不需要專職的web工程師。我們也在第一篇就提過,跨平台是APP開發的聖杯,而這個能夠讓我們直接跨足到全世界最大平台的Flutter Web,正可以說是聖杯中的聖杯。

因此,從它還被稱為HummingBird的時代開始,我就經常跟寫web的朋友同事吹捧這個神奇的專案,鼓吹他們一起來學Flutter。而他們的第一個問題幾乎必定是:這到底是怎麼運作的?我們寫的這些Dart程式碼,最後到底是怎麼變成Html+CSS?還是說是用Canvas?SVG?WebGL?瀏覽器上這麼多的技術,Flutter Web到底用了什麼?今天就讓我們來一探究竟。

Flutter架構


這張圖只要是稍微有接觸Flutter的人應該都已經看過無數次了,不過在開始web的部份之前,還是讓我們簡單複習一下,對後續討論會很有幫助。

首先綠色的部份是全部由Dart寫成的Flutter Framework。雖然我們平常會接觸到的絕大多數是在Widgets以上的部份,但在這系列中我們也介紹過一些Rendering的部份。再往下的Animation, Painting, Gesture就跟今天的討論有點關係了,因為這些是會直接呼叫到dart:ui的package。最底層的foundation則是提供像debug, diagnostics, binding, isolates, platform這些比較偏向utils的功能,雖然重要但和畫面的渲染沒什麼關係。

dart:ui是什麼呢?雖然這張圖上沒有出現,但它是在Flutter Framework和Engine之間重要的溝通橋樑,基本上就是Engine API的wrapper,讓我們能透過Dart Code去呼叫Engine的C++ Code。

再來藍色的部份就是主要由C++寫成的Flutter Engine,除了提供Dart Runtime來執行Dart Code之外,最重要的就是透過Skia繪圖引擎來進行最終的渲染。

最後黃色的Embedder是在Android/iOS平台上各自實作的,讓我們能夠把整個Flutter App嵌入Android/iOS App中。

好,現在讓我們來思考一下,如果我們想支援web的話,該怎麼修改這個架構?

我個人的第一直覺是,也許只要多加一個web的Embedder層就好了?不過仔細想想,我們也沒辦法在瀏覽器上跑Flutter Engine啊,又不是WebAssembly。就算可以,每次開網站就要載入整個Flutter Engine,光想就不是很可行對吧?那如果沒有Flutter Engine的話,也就沒有Skia來做渲染了,那我們要用什麼?

Flutter Web架構


答案揭曉!就是Canvas和DOM!其中Canvas可能比較好理解,就像Android, iOS有各自的Canvas API一樣,web上也有瀏覽器提供的Canvas API,讓我們的Flutter Framework可以想畫什麼就畫什麼。相對來說,Flutter Framework該怎麼把我們從Widget Tree開始到最後產生的Layer Tree,轉換成DOM,也就是HTML+CSS,就相當神奇了。

不過再解釋這部份之前,讓我們再把web上的整個Flutter架構從頭說起。

一樣從最上面的Framework層開始。這裡全部都是用Dart寫成的,而Dart一開始就是由Google開發,希望能夠取代Javascript,直接在瀏覽器上執行的語言。雖然最後Google的夢想沒有實現,但當初為了兼容性開發的dart2js編譯器還是好好保存下來了。

也就是說,整個Framework層,加上我們App本身,都可以直接被編譯成Javascript。Flutter Web App啟動時,瀏覽器就會執行這些Javascript,根據我們的App邏輯和Widget設定,產生出Widget, Element, RenderObject等等我們熟悉的物件。這整個流程雖然複雜,但到頭來也只是產生一堆object,管理他們的state而已,到這裡其實都還沒有呼叫到任何平台的API,因此一切都能順利運作。

還記得我們的RenderObject接下來要做什麼嗎?沒錯,就是之前也聊過的Layout。這裡就會開始有點小問題了...那就是Text的Layout,也就是RenderParagraph。除此之外的所有RenderObject都可以正常的根據Layout演算法來計算出位置和大小,但RenderParagraph最終卻必須透過Paragraph.layout(),呼叫原生API來進行layout,才能得到text的大小和位置。

class Paragraph extends NativeFieldWrapperClass2 {
  /// Computes the size and position of each glyph in the paragraph.
  ///
  /// The [ParagraphConstraints] control how wide the text is allowed to be.
  void layout(ParagraphConstraints constraints) => _layout(constraints.width);
  void _layout(double width) native 'Paragraph_layout';
}

然而,瀏覽器上卻沒有提供這樣的API。因此,我們必須直接把text丟進DOM,讓它實際layout出來之後,再取得text的各種測量,如圖所示:

當然,我們沒辦法在真正的UI Layout中進行這件事。因此我們之後會看到,我們其實是把它放進DOM裡面一個專門用來測量Text各種尺寸的隱藏區域,測量完之後再把這些資訊傳回進行中的實際Layout程序。


時間的關係今天就先聊到這,下回我們再接續剩下最重要的,也就是Flutter如何使用Canvas和Dom來進行渲染的部份。


上一篇
days[22] = "如何做一個Pacman遊戲?"
下一篇
days[24] = "Flutter Web是怎麼運作的?(下)"
系列文
Why Flutter why? 從表層到底層,從如何到為何。30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言