在今天的Flutter Live上,咱們宣佈正嘗試在Web上運行Flutter。在這篇文章中,描述了咱們如何應對嘗試過程當中遇到的挑戰以及當前該技術的狀態。在本文的最後,您將找到有關互操做和嵌入的問題及答案;
讓咱們快速回顧一下Flutter的架構。 Flutter是一個多層系統,更高的層級更方便開發者使用及操做,並容許開發者用更少的代碼完成更多的功能,而較低的層級雖然爲開發者提供更多的控制能力,然而這種控制也是有代價的:開發者必須處理一些更復雜的事項,當較高層不能知足開發人員的需求時,他們能夠深刻到較低層。開發人員能夠訪問Flutter Engine上方的全部層代碼;
Flutter 移動端架構
Flutter Engine做爲Flutter暴露出來的最底層庫,好比dart:ui。它對widgets,物理模擬(如重力等),動畫或佈局(文本佈局除外)一無所知。它所知道的是如何將圖片組合到屏幕上並將它們變成像素呈現。在dart:ui上直接編寫應用程序是很困難的,這就是咱們爲開發者建立更高層的緣由;
dart:ui之上的層級就是咱們所謂的「框架」層。它下面的層級咱們稱之爲「引擎」。該框架徹底使用Dart編程語言編寫。大多數引擎都是用C ++編寫的,特定於Android的部分用Java編寫,而iOS特定的部分用Objective-C編寫。 dart:ui中的一些基本類和函數是用Dart編寫的,主要用做Dart和C ++之間的橋樑。
Flutter還提供系統插件。插件是用一種語言編寫的代碼,它能夠直接訪問移動生態系統的OEM庫和第三方庫。要爲Android建立插件,您可使用Java或Kotlin編寫代碼。 iOS插件是使用Objective-C或Swift代碼編寫的。
Hello, The Web
Web平臺已經發展了數十年,包括許多技術和規範。有一些術語用於描述相關功能:HTML,CSS,SVG,JavaScript,WebGL。爲了在Web上運行Flutter,咱們須要:
-
編譯Dart代碼:Flutter是用Dart編寫的,咱們須要在Web上運行Dart
-
選擇要在Web上運行的Flutter子集:在Web上運行全部Flutter代碼是不切實際也是毫無幫助的。其中一些是特定於具體平臺的,例如Android和iOS。
-
選擇足夠的Web功能子集:隨着時間的推移,Web平臺會累積功能重疊的功能。例如,您可使用HTML + CSS,SVG,Canvas和WebGL繪製圖形。
從Dart語言誕生起,Dart語言就能夠被編譯成JavaScript語言。許多重要的應用程序從Dart編譯爲JavaScript,並已經被投放到生產環境中運行。 Flutter的編譯策略依賴於一樣的基礎設施。
當咱們開始探索時,咱們面臨着UI渲染的幾種選擇。咱們很快意識到咱們想要支持的特定Flutter層決定了咱們將採用何種Web技術實現。咱們製做了三個原型:
-
只是Widgets:這個原型實現了Flutter的Widget框架,並提供了一組核心佈局Widget做爲構建自定義Widget的基礎。對於佈局和定位,它依賴於Web的內置功能,例如flexbox,網格佈局,瀏覽器滾動等。
-
Widgets+自定義佈局:此原型包括Flutter的佈局系統(由RenderObject提供),但將渲染對象直接映射到HTML元素。
-
Flutter Web Engine:這個原型保留了dart:ui之上的全部層,並提供了一個在瀏覽器中運行的dart:ui實現;
Flutter最有價值的功能之一是它能夠跨平臺移植。您能夠(咱們鼓勵您)編寫自定義平臺特定代碼,該代碼能夠共享到各跨平臺而不須要有任何差異。這容許使用單個代碼庫編寫面向多個平臺的應用程序;
在嘗試將幾個示例應用程序移植到Web以後,咱們意識到原型#1和#2不能提供Flutter開發人員喜歡(可接受)的可移植性級別。所以,咱們決定使用原型#3:Flutter Web Engine,由於這將容許最高的框架級代碼在不一樣平臺之間重用:
Flutter for the Web Architecture (Hummingbird)
既然知道了咱們想要實現整個dart:ui API,咱們須要選擇一組Web技術來構建。 Flutter一次呈現一幀UI。在每一幀內,Flutter構建Widget,執行佈局,最後在屏幕上繪製它們。
建立Widgets
Widget的構建機制不依賴於應用程序運行的環境。該過程只須要實例化內存中的對象並跟蹤它們的狀態,當狀態改變時,計算佈局和繪畫所需的最小更新。將此部分移植到Web上很是簡單,當Dart團隊在dart2js中實現了super-mixin支持以後,編譯器將全部widget和widget框架編譯爲JavaScript時幾乎沒有任何問題;
佈局
佈局系統有點棘手。最大的挑戰是文本佈局。其餘全部Widget - Center,Row, Column, Stack, Scrollable, Padding, Wrap等 - 這些widget都由框架佈局,所以無需修改便可編譯到Web上運行。
在Flutter中,您能夠經過建立Paragraph對象並調用其layout()方法來放置一段文本。不幸的是,Web缺乏直接的文本佈局API。咱們用來測量文本佈局屬性的技巧是讓瀏覽器將其佈局,而後從DOM元素中讀回相關屬性。
放置文本段落時,Flutter測量段落的高度,寬度,最大內在寬度,最小內在寬度以及字母和表意基線(下圖黃色虛線)。這些屬性以下所示。
Paragraph layout attributes
您能夠在Flutter的
段落文檔中找到更多詳細信息。
要測量這些屬性,咱們首先在HTML DOM元素中放置一個段落,而後咱們讀取元素的尺寸。這會致使瀏覽器將其佈局。例如,要獲取元素的寬度和高度,咱們調用
offsetWidth及
offsetHeight。爲了測量基線,咱們將段落放置在一個元素中,該元素配置爲使用flex row進行自我佈局。在段落旁邊,咱們放置另外一個名爲「探針」的元素。由於探針與文本的基線對齊,因此調用
getBoundingClientRect就能夠獲得基線。咱們使用相似的技巧來測量最小和最大固有寬度。
Painting(繪製)
最後但一樣重要的是,咱們須要繪製上述這些Widgets。咱們在這方面的探索經歷了不少誤區,它仍然是咱們研究中最活躍的領域之一。在幀渲染結束時,咱們全部的widgets都須要在屏幕上變成像素。在瀏覽器中,這意味着它們必須歸結爲HTML / CSS,Canvas,SVG和WebGL的某種組合。
咱們尚未看過WebGL,主要是由於它是較底層級別的,而且要求咱們從新實現瀏覽器已經能夠作的事情,例如文本佈局和光柵化2D圖形,也由於咱們尚未弄清楚如何使用非Flutter組件的可訪問性,文本選擇和合成能夠與WebGL一塊兒使用。
咱們的早期原型之一就是爲每一個RenderObject生成了一個HTML元素。咱們確實得到了有但願的結果,但結果卻證實了API的變化太大了。咱們必須用Flutter維持一個巨大的代碼增量,因此咱們擱置了這個想法。
咱們目前正在同時探索的兩種方法:
-
HTML+CSS+Canvas的組合
-
CSS Paint API
HTML+CSS+Canvas
經過這種方法,咱們將框架生成的圖片分類爲使用HTML + CSS表達的圖片,以及使用Canvas 2D表達的圖片。而後,咱們輸出結合了HTML,CSS和2D畫布的HTML DOM。
咱們更喜歡HTML + CSS,由於它有瀏覽器的
顯示列表支持。這意味着咱們能夠優化圖片的光柵化在瀏覽器上的渲染引擎。這也意味着咱們能夠應用任意變換,尤爲是旋轉和縮放,而沒必要擔憂像素化。咱們將此畫布實現稱爲「DomCanvas」。
若是咱們沒法使用HTML + CSS表達圖片,咱們會回到畫布。 Canvas 2D容許咱們繪製幾乎全部的Flutter繪圖命令。若是將Flutter的
Canvas與Web的
CanvasRenderingContext2D進行比較,您會發現許多類似之處,在畫布上繪畫是高效的,由於它不會建立須要隨時間維護的可變樹節點,如HTML DOM或SVG。
2D畫布的一個挑戰是瀏覽器將其表示爲位圖,即存儲寬度x高度像素的內存緩衝區。所以,縮放畫布會致使像素化。若是縮放致使圖片大小的變形,咱們須要調整畫布大小。咱們發現分配畫布至關昂貴,所以調整它們的大小。最重要的是,當將多個畫布合成到同一頁面上時,瀏覽器必須執行柵格合成,合成柵格與顯示列表的工做方式不一樣。您能夠將多個顯示列表繪製到同一個內存緩衝區中。咱們調用Canvas 2D支持的canvas實現BitmapCanvas。咱們正在研究使位圖畫布更有效的方法。
爲了表達Flutter的 opacity, transform, offset, clip rect和其餘
圖層,咱們使用純HTML元素。例如,不透明度層變爲<flt-opacity>元素,其上具備不透明度CSS屬性,變換層變爲帶有變換CSS屬性的<flt-transform>元素,而clip rect變爲帶有overflow:hidden的<flt-clip-rect>。
完成全部操做後,幀將被渲染在做爲HTML元素樹的頁面上呈現,其中DomCanvas和BitmapCanvas做爲葉節點。例如:
Sample HTML DOM structure of a frame
Flutter Engine中的等效Flutter層級樹(稱爲
流層)以下所示:
Sample Flutter Engine layer structure
在結構上它們很是類似。最大的區別是,在Web上,咱們必須根據內容選擇不一樣的圖片實現。
HTML + CSS + Canvas適用於全部現代瀏覽器。可是,咱們已經在展望將來:
CSS Paint API
CSS Paint是一個新的Web API,是
Houdini的一部分。 Houdini是指許多瀏覽器供應商之間的合做,旨在向開發人員展現CSS引擎的某些部分。特別是,CSS Paint API容許開發人員在這些元素請求繪製時將自定義圖形繪製成HTML元素。例如,您能夠將元素背景的繪製任務分配給自定義CSS 畫筆(painter)完成。它與canvas很是類似,但有如下重要區別:
-
這個繪畫不是由主JavaScript虛擬機實例完成的(
此處感謝評論一樓 dilbert2指正,經查
「An isolate is only an instance of a JavaScript VM and it only has its own JavaScript heap.」),而是由一個叫作paint worklet的東西完成的。它有本身的內存空間,在提交DOM更改以後,在瀏覽器的繪製階段執行繪製工做。
-
CSS繪製由顯示列表支持,而不是位圖。這給了咱們一箭雙鵰的好處 - 兼顧2D畫布般的繪畫效率和沒有像素化。
-
目前CSS繪畫不支持繪畫文本。
咱們在Web上運行Flutter中的CSS Paint API獲得了實驗性支持,它已經顯示出良好的結果,特別是在性能方面。咱們的實現只是將paint命令序列化爲自定義CSS屬性。paint worklet讀取這些命令並執行它們。咱們使用普通的<p>和<span> HTML元素渲染文本。
咱們當前的序列化機制不是特別有效 - 它是一個將嵌套列表轉換爲JSON的樹 - 但一部分Houdini項目的做法是添加對
類型化數組的支持。要讓它變成可用的話,咱們須要將繪製命令編碼爲類型化數組而不是JSON字符串。類型化數組是
可轉移的,這意味着它們能夠經過引用從主隔離傳遞到paint工做,這個操做不涉及複製內存。
Interop and embedding(互操做及嵌入)
從Flutter調用Dart庫
Flutter Web應用程序能夠徹底訪問當今在Web上運行的全部現有Dart庫。
從Flutter調用JavaScript庫
Flutter Web應用程序徹底支持Dart的JS-interop包:package:js和dart:js。
在Flutter Web應用程序中使用CSS
目前,Flutter假設徹底控制網頁的正確性和性能。例如,咱們只使用遵循某些性能指南的一小部分CSS,例如
csstriggers.com/。在頁面上放置任意CSS可能會致使Flutter表現不可預測。
另外一個在Flutter Web應用程序中避免使用CSS的緣由是,在設計時,Flutter須要在渲染幀時知道全部佈局屬性。 CSS充當黑盒子的角色。例如,若是要顯示可滾動的widget列表,則必須實例化併爲全部widget生成HTML並應用必要的CSS屬性(例如,flex-direction row和overflow:scroll)。而後瀏覽器將全部內容都放置排列出來並將其呈如今屏幕上。應用程序代碼不參與佈局過程。
最後,本着保持Flutter代碼可跨平臺移植的精神,咱們避免使用CSS,所以咱們能夠在Android和iOS上運行相同的代碼。
將Flutter嵌入現有的Web應用程序中
咱們尚未爲此添加適當的支持,但咱們打算在未來進行探索。咱們正在考慮的幾種方法是使用<iframe>和shadow DOM。
在Flutter中嵌入非Flutter組件
咱們尚未添加對在Flutter Web應用程序中嵌入非Flutter組件 - 自定義元素,React組件,Angular組件 - 的支持,但咱們打算在未來探討這一點。一種可能的途徑是使用
平臺視圖將外來內容放入Flutter Web應用程序中。須要考慮的一個重要方面是這些外來內容可能對應用程序的性能和正確性產生何種影響。由於非Flutter組件可能包含任意CSS,如上所述,它可能會有問題。須要更多的研究。
可移植性
咱們的目標是儘量多地將flutter框架移植到Web上。可是,這並不意味着任何Flutter應用程序能夠不用更改代碼就能夠在Web上運行。Flutter Web用程序仍然是一個Web應用程序;它在瀏覽器中被沙箱化,只能執行Web瀏覽器容許的操做。例如,若是您的Flutter應用程序使用到了沒有Web實現的Native 插件,例如ARCore,您將沒法在Web上運行該應用程序。一樣,它也沒有直接訪問文件系統或底級網絡的權限。
當前狀態
咱們構建了足夠的Web引擎來渲染大部分Flutter Gallery示例中的內容。咱們尚未移植Cupertino Widget,但全部Material Widgets,Material Theming,以及Shrine和Contact Profile演示應用程序都已經能夠在Web上運行。(
演示視頻地址)
源代碼放置在何處?
咱們計劃很快開源這個項目,並很高興與開源社區分享。該項目最初是做爲Google內部源代碼樹的一項探索而開始的。咱們的代碼穩定後,咱們打算將開發轉移到GitHub,咱們有機會將其與內部基礎架構分開,與此同時,若是您在
github.com/flutter看到與Web相關的拉取請求,請不要感到驚訝!
結論
我但願這篇文章能讓您瞭解到咱們正在解決的問題,以使Flutter在Web上良好運行。咱們歡迎您的想法和主意。 請繼續關注Google I / O 2019!