重磅|庖丁解牛之——Flutter for Web
Flutter for Web
在2018年冬的Flutter 1.0倫敦釋出會上,Flutter產品經理Tim Sneath透過一個滑動拼圖的例子介紹瞭如何讓Flutter執行在Web之上。這一當時代號HummingBird的專案後來被重新命名為flutter_web,並最終合入了master分支。
Flutter Web想在單程式碼庫的情況下,使Flutter應用擁有Web支援。這樣開發者使用Dart編寫的Flutter應用可以被部署到任意的Web伺服器上,或嵌入到瀏覽器中。開發者可以使用Flutter的所有特性,也不需要特殊的瀏覽器外掛支援。
就最新的Flutter1.9.x而言,Flutter Web還處於技術預覽版階段,離真正應用到生產環境中還是有一些距離的。
設計
那麼Flutter Web是怎麼做到這一切的呢?這就要從Flutter的原理說起。Flutter框架的設計如下所示:
其中,Flutter Framework是使用純Dart開發的。我們將其分為兩部分,渲染和邏輯。就渲染而言,其最終會表示為dart:ui中提供的TextBox,Picture,Image等例項物件,再透過native方法(實現dart呼叫C++)呼叫Skia,Text等C++庫,最終渲染在螢幕上,邏輯部分則被Dart Runtime執行。不難看出,要實現在Web上執行Flutter,要解決兩個問題。Dart如何執行在Web上以及dart:ui中的native方法如何透過標準Web的方式來實現。就前者而言,dart2js是一個已有的成熟框架,所以問題的重點就在於如何透過標準Web的方式去實現一個dart:ui庫。這也就是目前Flutter Web的設計原理:
在Flutter Web的設計之初,主要考慮了兩個方案用於Web支援:
HTML+CSS+Canvas
CSS Paint API
方案1具有最好的相容性,它優先考慮HTML+CSS表達,當HTML+CSS無法表達圖片的時候,會使用Canvas來繪製。但2D Canvas在瀏覽器中是點陣圖表示,會造成畫素化下的效能問題。
方案2是新的Web API, 屬於Houdini的組成部分。Houdini提供了一組可以直接訪問CSS物件模型的API,使得開發者可以去書寫程式碼並被瀏覽器作為CSS加以解析,這樣在無需等待瀏覽器原生的支援下,創造了新的CSS特性。它的繪製並非由核心Javascript完成,而是類似Web Worker的機制。其繪製由顯示列表支援,而不是點陣圖。但目前CSS Paint API不支援文字,此外各家廠商對齊支援也並不統一。
鑑於此,目前Flutter Web使用的是基於方案1的實現。
環境準備
Flutter環境
flutter doctor -v[✓] Flutter (Channel master, v1.10.6-pre.61, on Mac OS X 10.15 19A558d, locale en-CN) • Flutter version 1.10.6-pre.61 at /Users/kylewong/Codes/Flutter/alibaba-flutter/flutter • Framework revision 7bf9aea254 (4 hours ago), 2019-09-25 00:37:12 -0700 • Engine revision 63949eb0fd • Dart version 2.6.0 (build 2.6.0-dev.0.0 69b5681546)...[✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome[✓] Android Studio (version 3.5) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin version 39.0.3 • Dart plugin version 191.8423 • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
Web環境
在flutter的master分支上,開發者可以透過下方命令檢查當前是否開啟了Web支援:
kylewong@KyleWongdeMacBook-Pro web_dig % flutter devices3 connected devices:MHA AL00 • GWY7N16A31002764 • android-arm64 • Android 9 (API 28)Chrome • chrome • web-javascript • Google Chrome 77.0.3865.90Server • web • web-javascript • Flutter Tools
如果不能看到Chrome/Server這兩個裝置,可以透過以下命令開啟支援:
flutter config --enable-web
這個命令會將配置項儲存到使用者Home目錄下的.flutter_settings中,一個典型的內容如下所示:
{ "enable-web": false, "ios-signing-cert": "iPhone Developer: Kang Wang (xxx)"}
dart2js配置修改
以flutter自帶的gallery為例,預設的flutter web實現下,生成的js如下所示:
可以看到此js程式碼可讀性很差(變數名,格式等),大小為2.2MB。這是因為flutter構建過程中開啟了dart2js命令的O4最佳化項所致。為了方便我們分析和除錯,我們對此其進行如下修改:
O0將禁止很多最佳化,修改後的效果如下所示:
可以看到,大小增加了不少,但可讀性上好很多,除特殊說明外,本文將在O0最佳化項下展開。
原理剖析
Gallery上的表現對比
我們首先基於Flutter提供的Gallery專案,比較下其在Mobile和Web上的表現(此處使用Flutter Web預設最佳化級別):
Flutter Native vs Flutter Web:
可以看出,Flutter Web在完備性上還是比較不錯的,但依然有一些問題,比如本地圖片在Android裝置上顯示正常,在iOS上卻無法正常顯示,網路圖片則是正常的。
在Mobile/Web開發中,常見的元素包括圖片,文字,形狀,手勢等,接下來,我們逐一進行剖析。
圖片的實現
以如下程式碼為例:
import 'package:flutter/material.dart';void main() => runApp(Image.asset('assets/1.png'));
其執行效果如下(左側為Native,右側為Web):
其Native與Web簡要原理對比如下所示:
在flutterwebsdk中最終呼叫html庫(dart-sdk自帶)繪製的程式碼如下:
flutter_web_sdk/lib/_engine/engine/bitmap_canvas.dart@overridevoid drawImageRect( ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint) { // TODO(het): Check if the src rect is the entire image, and if so just // append the imgElement and set it's height and width. print('KWLM04'); final HtmlImage htmlImage = image; ctx.drawImageScaledFromSource( htmlImage.imgElement, src.left, ... dst.height, );}
相對應地,透過 flutter build web--release--verbose
生成的main.dart.js中部分程式碼如下:
此部分對應上述bitmap_canvas.dart中的drawImageRectdrawImageRect$4: function(image, src, dst, paint) { ... P.print("KWLM04"); H.interceptedTypeCheck(image, "$isHtmlImage"); J.drawImageScaledFromSource$9$x(this.get$ctx(), image.imgElement, src.left, src.top, src.get$width(src), src.get$height(src), dst.left, dst.top, dst.get$width(dst), dst.get$height(dst));},?drawImageScaledFromSource$9$x: function(receiver, a0, a1, a2, a3, a4, a5, a6, a7, a8) { return J.getInterceptor$x(receiver).drawImageScaledFromSource$9(receiver, a0, a1, a2, a3, a4, a5, a6, a7, a8);},?
最終呼叫到了CanvasRenderingContext2D.drawImage這一標準W3C的API。
文字的實現
以如下程式碼為例:
import 'package:flutter/material.dart';void main() => runApp(Text('Hello Flutter!'));
其執行效果如下(左側為Native,右側為Web):
其Native與Web簡要原理對比如下所示:
在flutterwebsdk中最終呼叫html庫(dart-sdk自帶)構建和新增Element的程式碼如下:
flutter_web_sdk/lib/_engine/engine/dom_canvas.dart
@override
void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) {
print('KWLM18');
final html.Element paragraphElement =
_drawParagraphElement(paragraph, offset, transform: currentTransform);
currentElement.append(paragraphElement);
}
flutter_web_sdk/lib/_engine/engine/engine_canvas.dart
html.Element _drawParagraphElement(
EngineParagraph paragraph,
ui.Offset offset, {
Matrix4 transform,
}) {
print('KWLM25');
assert(paragraph._isLaidOut);
final html.Element paragraphElement = paragraph._paragraphElement.clone(true);
final html.CssStyleDeclaration paragraphStyle = paragraphElement.style;
...
return paragraphElement;
}
相對應地main.dart.js中部分程式碼如下:
此部分對應上述dom_canvas.dart中的drawParagraphdrawParagraph$2: function(paragraph, offset) { var paragraphElement; H.interceptedTypeCheck(paragraph, "$isParagraph"); H.interceptedTypeCheck(offset, "$isOffset"); P.print("KWLM18"); paragraphElement = H._drawParagraphElement(paragraph, offset, this.get$currentTransform(this)); J.append$1$x(this.get$currentElement(), paragraphElement);},?_drawParagraphElement: function(paragraph, offset, transform) { var paragraphElement, paragraphStyle, style, t1; P.print("KWLM25"); paragraphElement = H.interceptedTypeCheck(J.clone$1$x(paragraph._paragraphElement, true), "$isElement0"); paragraphStyle = paragraphElement.style; (paragraphStyle && C.CssStyleDeclaration_methods).set$position(paragraphStyle, "absolute"); ... return paragraphElement;},?
從文字這個例子不難看出,對於可以透過HTML+CSS形式表達的元素,flutter web將其最終翻譯成Element+CSS Style形式動態生成類似靜態HTML+CSS描述的內容,最終完成內容的渲染。
形狀的實現
以如下程式碼為例:
import 'package:flutter/material.dart';void main() => runApp(Container(decoration: BoxDecoration(color: Colors.red)));
其執行效果如下(左側為Native,右側為Web):
其Native與Web簡要原理對比如下所示:
在flutterwebsdk中最終呼叫html庫(dart-sdk自帶)構建Element(新增部分同文字)的程式碼如下:
flutter_web_sdk/lib/_engine/engine/dom_canvas.dart@overridevoid drawRect(ui.Rect rect, ui.PaintData paint) { print('KWLM47'); assert(paint.shader == null); final html.Element rectangle = html.Element.tag('draw-rect'); ... currentElement.append(rectangle);}
相對應地main.dart.js中部分程式碼如下:
此部分對應上述dom_canvas.dart中的drawRectdrawRect$2: function(rect, paint) { var rectangle, isStroke, t1, t2, t3, left, right, $top, bottom, effectiveTransform, translated, style, cssColor, _this = this; H.interceptedTypeCheck(rect, "$isRect"); H.interceptedTypeCheck(paint, "$isPaintData"); P.print("KWLM47"); rectangle = W.Element_Element$tag("draw-rect"); ... J.append$1$x(_this.get$currentElement(), rectangle);}?
對於本例中的形狀,也是透過HTML+CSS的方式實現的。
觸控事件的實現
以如下程式碼為例:
import 'package:flutter/material.dart';void main() => runApp(GestureDetector(child: Text('Click me!',style: TextStyle(fontSize: 50),), onTap: (){ print('KWLM called!');}));
其執行效果如下(左側為Native,右側為Web):
其Native與Web簡要原理對比如下所示:
此例中的PointerBinding由dartsdk.js提供,其提供了從Window獲取事件回撥的機制,並最終呼叫到了WidgetsFlutterBinding(也是GestureBinding)的handlePointerDataPacket$1方法,後續的路由機制同Native情景下的Flutter部分。
優缺點
優點
從目前Flutter Web選取的技術路線來說,HTML+CSS+Canvas這種方式具有最好的相容性,這樣開發者開發的Flutter程式碼(不包括Plugin部分對於Native的擴充套件)將零成本地轉成標準Web展示,這一低成本擴充套件到Web平臺帶來的優勢還是很明顯的。
不足
儘管其優勢很明顯,也面臨一些不足的問題 a. 包大小過大的問題
目前dart2js本身並沒有針對小型程式做出最佳化,即使是本文中的手勢這麼簡單的程式碼,Flutter Web最終生成的大小也有560KB, 無法滿足要求。
但從理論上來說,透過對dart2js本身做出合理的最佳化(鑑於dart/flutter整個的開源設計),我們可以將Flutter Web依賴的基礎SDK全集嵌入應用中(或者按需下載的方式),將真正的業務程式碼與SDK分離,也是有可能將其大小降低的。
b. 功能不完備的問題 比如在flutter_gallery的例子中Safari上圖示展示為方框的問題。
c. 效能的問題 當需要用到BitmapCanvas比較多的時候,Element物件直接的光柵化,會導致在一些諸如縮放等的場景下,面臨效能的問題。當然縮放的問題在移動裝置的場景下也是有可能避免的。
小結
總體而言,Flutter Web具有優秀的設計。它基於dart:js和dart:html這些成熟的框架,透過將與Native相關的dart:ui庫重寫的方式,很好地解決了Flutter擴充套件到Web平臺上的問題。對於上層開發者而言,完全不用去做任何修改,即可產生一套符合Web標準的程式碼,顯示和行為也同原始設計保持一致。雖然目前Flutter Web還不夠成熟,存在一些諸如包大小效能等問題,但基於Flutter和Flutter Web的良好分層設計,我們有理由相信隨著時間的推移和社群成熟,這些問題終將得到改善或解決。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900359/viewspace-2658858/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Flutter小知識--InheritedWidget之庖丁解牛Flutter
- Flutter Web 之 Hello WorldFlutterWeb
- 【老孟Flutter】Flutter 2.0 重磅更新Flutter
- 庖丁解牛之瀏覽器事件環瀏覽器事件
- USDD重磅升級,成Web3.0價值之錨Web
- Flutter Web網站之ScrollView+GridView優化FlutterWeb網站View優化
- 鴻蒙:庖丁解牛,鴻鵠之志(附下載)鴻蒙
- Flutter 圖片庫重磅開源!Flutter
- flutter_web 實戰之文章列表與詳情FlutterWeb
- 用 Arthas “庖丁解牛”
- 重磅! flutter檢視區域性更新Flutter
- 初識Flutter webFlutterWeb
- 【譯】Bringing Flutter to the WebFlutterWeb
- Web 開發者如何理解 Flutter 佈局之 —— 2. ContainerWebFlutterAI
- Web 開發者如何理解 Flutter 佈局之 —— 1. TextWebFlutter
- Web 開發者如何理解 Flutter 佈局之 —— 3. ImageWebFlutter
- Flutter for web 最新填坑FlutterWeb
- Flutter Web 開發部署FlutterWeb
- 將 flutter_web 遷移到 flutter1.9+FlutterWeb
- 庖丁解牛 Activity 啟動流程
- Flutter For Web入門實戰FlutterWeb
- flutter打包釋出web端FlutterWeb
- Flutter Web 近期的重要更新FlutterWeb
- 庖丁解牛React-Redux(二): connectReactRedux
- Web 開發者如何理解 Flutter 佈局之 —— 5. ListView 和 ScrollBarWebFlutterView
- Flutter2.0重磅釋出!帶你一文打盡Flutter EngageFlutter
- Flutter隨筆(二)——使用Flutter Web + Docker + Nginx打造一個簡單的Web專案FlutterWebDockerNginx
- [譯] Hummingbird: Web端執行FlutterWebFlutter
- Flutter web 持續整合實踐FlutterWeb
- [- Flutter 跨界篇-]昨晚簡記+Flutter桌面、Web開發FlutterWeb
- Flutter(八)之Flutter的基礎WidgetFlutter
- Flutter(十)之Flutter的滾動WidgetFlutter
- Flutter主題切換之flutter reduxFlutterRedux
- Flutter Widgets 之 RichTextFlutter
- Flutter Widgets 之 SnackBarFlutter
- Flutter Widgets 之 ListWheelScrollViewFlutterView
- Flutter Widgets 之 ShaderMaskFlutter
- Flutter新版本 Web App 嚐鮮FlutterWebAPP