Hello 大家好,我是《Flutter 開發實戰詳解》的作者,Github GSY 系列開源項目的負責人郭樹煜,目前開源的 gsy_github_app_flutter 以 13k+ 的 star 在中文總榜的 dart 排行上暫處第一名。
開始之前
Flutter 開源至今其實已經將近 7 年的時間,而我是從 2017 年開始接觸的 Flutter ,如今在 2022 年看來,Flutter 已經是不再是以前小眾的跨平臺框架。
如圖所示,可以看到如今的 Flutter 已經有高達 135k
的 star , 10k+
Open 和 50k+
Closed 的 issue 也足以說明 Flutter 社區和用戶的活躍度。
在去年下半旬的數據調查中,Flutter 也成為了排名第一的被使用和被喜愛的跨平臺框架,這里說這么說并不是說你一定要去學 Flutter ,而是說不管我們喜不喜歡,目前 Flutter 已經證明了它的價值。
數據來源: https://rvtechnologies.com/10-reasons-why-flutter-is-growing-as-a-cross-platform-framework/
其實在去年和前年,我也做過一些簡單的統計:
- 2020 年
52
個樣本中有19
個 App 里出現了 Flutter; - 2021 年
46
個樣本中有24
個 App 里出現了 Flutter;
這份數據樣本比較小,主要是從我個人常用的 App 進行統計,所以不準確也不具備代表性,但是可以一定程度反映了國內現在 Flutter 應用使用的情況。
最后 Flutter 在 Web 和 PC 端也有支持,但是我暫時還未投入生產使用,目前可以簡單總結就是:
Web
- Flutter Web 目前支持
HtmlCanvas
和CanvasKit
(WASM),默認是移動端使用 HTML 而桌面端使用 WASM; - pub.dev 上
60%
左右的包是 Web 兼容; - 體積和 SEO 是使用過程中最需要提前考慮的問題;
Desktop
PC 端目前相對更弱勢一些,如果是和 Electron
比較,可以簡單認為, Flutter PC 版可以使用更低的內存占用和更小的體積,甚至更好的 FFI 繼承 C 的能力,但是同樣的生態目前也更弱,第三方支持相對較少,需要自己獨立解決的問題會相對更多。
Window 可投入生產版本已經正式發布
Flutter 和原生開發的不同
Flutter 作為跨平臺的 UI 框架,它主要的特點是做到:在性能還不錯的情況下,框架的 UI 于平臺無關,而從平臺的角度上看, Flutter 其實就是一個“單頁面”的應用。
1、單頁面應用
什么是“單頁面”應用?
也就是對于原生 Android 和 iOS 而言,整個跨平臺 UI 默認都是運行在一個 Activity
/ ViewController
上面,默認情況下只會有一個 Activity
/ ViewController
, 事實上 Flutter、 ReactNative 、Weex 、Ionic 默認情況下都是如此,所以一般情況下框架的路由和原生的路由也是沒有直接關系。
舉個例子,如下圖所示,
- 在當前 Flutter 端路由堆棧里有
FlutterA
和FlutterB
兩個頁面 Flutter 頁面; - 這時候打開新的
Activity
/ViewController
,啟動了原生頁面X,可以看到原生頁面 X 作為新的原生頁面加入到原生層路由后,把FlutterActivity
/FlutterViewController
給擋住,也就是把FlutterA
和FlutterB
都擋住; - 這時候在 Flutter 層再打開新的
FlutterC
頁面,可以看到依然會被原生頁面X擋住;
所以通過這部分內容可以看出來,跨平臺應用默認情況下作為單頁面應用,他們的路由堆棧是和原生層存在不兼容的隔離。
當然這里面重復用了一個詞:“默認”,也就是其實可以支持自定義混合堆棧的,比如官方的
FlutterEngineGroup
,第三方框架flutter_boost
、mix_stack
、flutter_thrio
等等都是為了解決混合開發的場景。
2、渲染邏輯
介紹完“單頁面”部分的不同,接下來講講 Flutter 在渲染層面的不同。
在渲染層面 Flutter 和其他跨平臺框架存在較大差異,如下圖所示是現階段常見的渲染模式對比:
對于原生 Android 而言,是原生代碼經過 skia 最后到 GPU 完成渲染繪制,Android 原生系統本身自帶了 skia;
對于 Flutter 而言,Dart 代碼里的控件經過 skia 最后到 GPU 完成渲染繪制,這里在 Andriod 上使用的系統的 skia ,而在 iOS 上使用的是打包到項目里的 skia ;
對于 ReactNative/Weex 等類似的項目,它們是運行在各自的 JS 引擎里面,最后通過映射為原生的控件,利用原生的渲染能力進行渲染;( PS,今年官方終于要發布重構的版本了:2022 年 React Native 的全新架構更新 )
對于 ionic 等這類 Hybird 的跨平臺框架,使用的主要就是 WebView 的渲染能力;
skia 在 Android 上根據不同情況就可能會是
OpenGL
或者Vulkan
,在 iOS 上如果有支持Metal
也會使用Metal
加速渲染。
通過前面的介紹,可以看出了:
ReactNative/Weex
這類跨平臺和原生平臺存在較大關聯:
好處就是:如果需要使用原生平臺的控件能力,接入成本會比較低;
壞處自然就是: 渲染嚴重依賴平臺控件的能力,耦合較多,不同系統之間原生控件的差異,同個系統的不同版本在控件上的屬性和效果差異,組合起來在后期開發過程中就是很大的維護成本。
例如:在 iOS 上調試好的樣式,在 Android 上出現了異常;在 Android 上生效的樣式,在 iOS 上沒有支持;在 iOS 平臺的控件效果,在 Android 上出現了不一樣的展示,比如下拉刷新,Appbar等; 如果這些問題再加上每個系統版本 Framework 的細微差別,就會變得細思極恐。
另外再說個例子,Android 和 iOS 的陰影效果差異。
Flutter
與之不同的地方就是渲染直接利用 skia 和 GPU 交互,在 Android 和 iOS 平臺上實現了平臺無關的控件,簡單說就是 Flutter
里的 Widget
大部分都是和 Android 和 iOS 沒有關系。
本質上原生平臺是提供一個類似 Surface
的畫板,之后剩下的只需要由 Flutter 來渲染出對應的控件
一般是使用
FlutterView
作為渲染承載,它在 Android 上內部使用可以是SurfaceView
、TextureView
或者FlutterImageView
;在 iOS 上是UIView
通過Layer
實現的渲染。
所以 Flutter 的控件在不同平臺可以得到一致效果,但是和原生控件進行混合也會有較高的成本和難度,在接入原生控件的能力上,Flutter 提供了 PlatformView
的機制來實現接入, PlatformView
本身的實現會比較容易引發內存和鍵盤等問題,所以也帶來了較高的接入成本。
目前最新版本基本強制要求 Hybrid Composition ,所以相對以前的
PlatformView
會好一點點,當然可能遇到的問題還是有的。比如密碼鍵盤切換,切換頁面時PlatformView
時頁面閃動。
3、項目結構
如上圖所示,默認情況下 Flutter 工程結構是這樣的:
-
android
原生的工程目錄,可以配置原生的appName
,logo
,啟動圖,AndroidManifest
等等; -
ios
工程目錄,配置啟動圖,logo
,應用名稱,plist
文件等等; -
build
目錄,這個目錄是編譯后出現,一般是 git 的 ignore 目錄,打包過程和輸入結果都在這個目錄下,Android 原生的打包過程輸出也被重定向輸出到這里; -
lib
目錄,用來寫 dart 代碼的,入口文件一般是main.dart
; -
pubspec.yaml
文件,Flutter 工程里最重要的文件之一,不管是靜態資源引用(圖片,字體)、第三方庫依賴還是 Dart 版本聲明都寫在這里。
如下圖是使用是關于 pubspec.yaml
文件的結構介紹
需要注意,當這個文件發生改變時,需要重新執行
flutter pub get
,并且stop
應用之后重新運行項目,而不是使用hotload
。
如下所示是 Flutter 的插件工程,Flutter 中分為 Package
和 Plugin
,如果是
-
Package
項目屬于 Flutter 包工程,不會包含原生代碼; -
Plugin
項目屬于 Flutter 插件工程,包含了 Android 和 iOS 代碼;
4、打包調試
Flutter 運行之前都需要先執行 flutter pub get
來先同步下載第三方代碼,下載的第三方代碼一般存在于(Mac) /Users/你的用戶名/.pub-cache
目錄下 。
下載依賴成功后,可以直接通過 flutter run
或者 IDE 工具點擊運行來啟動 Flutter 項目,這個過程會需要原生工程的一些網絡同步工作,比如:
- Android 上的 Gradle 和 aar 依賴包同步;
- iOS 上需要 pod install 同步一些依賴包;
如果需要在項目同步過程中查看進度:
- Android 可以到
android/
目錄下執行./gradlew assembleDebug
查看同步進度; - iOS 可以到
ios/
目錄下執行pod install
,查看下載進度;
同步的插件中,如果是 Plugin
帶有原生平臺的代碼邏輯,那么可以在項目根目錄下看到一個叫做 .flutter_plugins
和 .flutter-plugins-dependencies
的文件,它們是 git ignore 的文件,Android 和 iOS 中會根據這個文件對本地路徑的插件進行引用,后面 Flutter 運行時會根據這個路徑動態添加依賴。
默認情況下 Flutter 在 debug 下是 JIT 的運行模式所以運行效率會比較低,速度相對較慢,但是可以 hotload。
在 release 下是 AOT 模式,運行速度會快很多,同時 Flutter 在模擬器上一般默認會使用 CPU 運行,在真機上會使用 GPU 運行,所以性能表現也不同。
另外 iOS 14 真機上 debug 運行,斷后鏈接后再次啟動是無法運行的。
如果項目存在緩存問題,可以直接執行 flutter clean
來清理緩存。
最后說下 Flutter 的為什么不支持熱更新?
前面講過 ReactNative 和 Weex 是通過將 JS 代碼里的控件轉化為原生控件進行渲染,所以本質上 JS 代碼部分都只是文本而已,利用 code-push
推送文本內容本質上并不會違法平臺要求。
而 Flutter 打包后的文件是二進制文件,推送二進制文件明顯是不符合平臺要求的。
release 打包后的 Android 會生成
app.so
和flutter.so
兩個動態庫;iOS 會生成App.framework
和Flutter.framework
兩個文件。
所以 Flutter 的第三方熱更新市面上常見的有:MxFlutter
、Fair
、Kraken
、liteApp
、NEJFlutter
、Flap
(MTFlutter)、flutter_code_push
(chimera) 等等,而這些框架都不會是直接下發可執行的二進制文件,大致市面上根據 DSL 的不同,動態化方案可以分為兩大類:面向前端的和面向終端。
如下圖所示,例如 WXG 的 LiteApp
、騰訊的 MxFlutter
和阿里的 Kraken
(北海) 就是面向前端 ,使用 JS/TS 。
如下圖所示:例如 Flap
、flutter_code_push
就是面向終端,主要是對 Dart 的 DSL 或者編碼下功夫。
參考資料: https://tech.meituan.com/2020/06/23/meituan-flutter-flap.html
最后,關于 Flutter 熱更新動態化的支持,可以參考這個表格:
參考資料:Flutter實現動態化更新-技術預研 https://juejin.cn/post/7033708048321347615
5、Flutter 簡單介紹
這里介紹下 Flutter Dart 部分相關的內容,對于原生開發來說,Flutter 主要優先了解響應式和Widget
。
響應式
響應式編程也叫做聲明式編程,這是現在前端開發的主流,當然對于客戶端開發的一種趨勢,比如 Jetpack Compose
、SwiftUI
。
Jetpack Compose 和 Flutter 的在某些表層上看真的很相似。
響應式簡單來說其實就是你不需要手動更新界面,只需要把界面通過代碼“聲明”好,然后把數據和界面的關系接好,數據更新了界面自然就更新了。
從代碼層面看,對于原生開發而言,沒有 xml
的布局,沒有 storyboard
,布局完全由代碼完成,所見即所得,同時也不會需要操作界面“對象”去進行賦值和更新,你所需要做的就是配置數據和界面的關系。
響應式開發比數據綁定或者 MVVM 不同的地方是,它每次都是重新構建和調整整個渲染樹,而不是簡單的對 UI 進行
visibility
操作。
如下圖所示,是 Flutter 下針對響應式 UI 的典型第三方示例: responsive_framework
Widget
Widget
是 Flutter 里的基礎概念,也是我們寫代碼最直接接觸的對象,Flutter 內一切皆 Widget ,Widget 是不可變的(immutable),每個 Widget 狀態都代表了一幀。
所以 Widget
作為一個 immutable
對象,它不可能是真正工作的 UI 對象,在 Flutter 里真正的 View
級別對象是 Element
和 RenderObject
, 其中 Element
的抽象對象就是我們經常用到的 BuildContext
。
舉個例子,如下代碼所示,其中 testUseAll
這個 Text
在同一個頁面下在三處地方被使用,并且代碼可以正常運行渲染,如果是一個真正的 View
,是不能在一個頁面下這樣被多個地方加載使用的。
所以 Flutter 中 Widget
更多只是配置文件的地位,用于描述界面的配置代碼,具體它們的實現邏輯、關系還有分類,可以看我寫的書 《Flutter開發實戰詳解》中 的第三章和第四章部分。
有趣的問題
最后說一個比較有意思的問題,之前有人說 Flutter 里是傳遞值還是引用?這個問題看過網上有不少文章解釋得很奇怪,存在一些誤導性的解釋,其實這個問題很簡單:
Flutter 里一切皆是對象, 就連 int
、 double
、bool
也是對象,你覺得對象傳遞的是什么?
但是對于對象的操作是有區別的,比如對于 int
、 double
等 class
的 +
、-
、*
、 \
等操作,其實是執行了這個 class
的 operator
操作符的操作, 然后返回了一個 num
對象。
而對于這個操作,只需要要去 dart vm
看看 Double
對象在進行加減乘除時做了什么,如下圖所示,看完相信就知道方法里傳遞 int
、double
對象后進行操作會是什么樣的結果。
Flutter 和 Compose
最后聊一聊 Flutter 和 Compose。
其實自從 Jetpack Compose 面世以來,關于 Flutter 與 Compose 之間的選擇問題就開始在 Android 開發中出現,就如同之前有 iOSer 糾結在 Flutter 和 SwiftUI 之間選誰一樣,對于 Android 開發來說似乎“更頭痛”的是 Flutter 與 Compose “同出一爹”。
這里我只是提供一些我個人的理解,并不代表官方的觀點:
Flutter 和 Compose 的未來目標會比較一致,但是至少它們出現的初衷是不一樣。
首先 Compose 是 Jetpack 系列的全新 UI 庫,理解下這點!Compose 是 Jetpack 系列的成員之一,所以可以被應用到 Android 界面開發中,所以你也可以選擇不用,用不用都能開發 Android 的 UI 。
然后再說 Compose 出生的目的:就是為了重新定義 Android 上 UI 的編寫方式,為了提高 Android 原生的 UI 開發效率,讓 Android 的 UI 開發方式能跟上時代的步伐。
不管你喜不喜歡,聲明式的界面開發就是如今的潮流,不管是 React 、SwiftUI 、Flutter 等都在表明這一點。
而對于 Flutter 而言就是跨平臺,因為 Flutter 沒有自己的平臺 ,有人說 Fuchsia
會是 Flutter 的家,但那已經屬于后話,畢竟 Fuchsia
要先能養活自己。
因為 Flutter 出生就是為了跨平臺存在的全新 UI 框架,從底層到上層都是“創新”和“大膽”的設計,就選擇 Dart 本身就是一項很“大膽”的決定,甚至在 Web 平臺都敢支持選用 Canvaskit
的 WASM
模式。
所以 Flutter 的“任性”從一出來就不被看好,當然至今也有不看好它的人,因為它某種程度很“偏激”和不友好。
另外從起源和維護上:
- Flutter 起源是 Chrome 項目組,選用了 Dart ,所以 Flutter 并不是歸屬于 Android 的項目;
- Compose 起源于 Android 團隊,它使用的是 Kotlin ;
所以他們起源和維護都屬于不同 Group ,所以從我們外界看可能會覺得有資源沖突,但是本質上他們是不同的大組在維護的。
好了,扯了那么多,總結下就是:
Compose 是 Android UI 的未來,現階段你可以不會,但是如果未來你會繼續在 Android 平臺的話,你就必須會。 ,而 Compose 的跨平臺支持也在推進,不過不是谷歌維護,而是由 Jetpack 提供的 Compose for Compose Multiplatform 。
Flutter 的未來在于多平臺,更穩定可靠的多平臺 UI 框架。如果你的路線方向不是大前端或者多端開發者,那你可以不會也沒關系。
說帶了這些框架主要還是做 UI 的,學哪個看你喜歡哪個就行~當然,可能更重要是看你領導要求你用哪個,而回歸到沖突的問題上, Flutter 和 Compose 沖突嗎?
從立項的意義上看 Flutter 和 Compose 好像是沖突的,但是從使用者的角度看,它們并不沖突。
因為對于開發者而言,不管你是先學會 Compose 還是先學會 Flutter,對于你掌握另外一項技能都有幫助,相當于學會一種就等于學會另一種的 70%
從未來的角度看:
如果你是原生開發,還沒接觸過 Flutter , 那先去學 Compose ,這對你的 Android 生涯更有幫助,然后再學 Flutter 也不難。
如果你已經在使用或者學習 Flutter ,那么請繼續深造,不必因為擔心 Compose 而停滯不前,當你掌握了 Flutter 后其實離 Compose 也不遠了。
它們二者的未來都會是多平臺,而我認為的沖突主要是在于動手學起來,而不是在二者之間徘徊糾結。
從現實角度出發:目前 Flutter 2.0 下的 Android 和 iOS 已經趨向穩定,Web 已經進入 Stable 分支,而 Macos/Linux/Win 也進入了 Beta 階段,并且可以在 Stable 分支通過 snapshot 預覽。所以從這個階段考慮,如果你需要跨平臺開發,甚至 PC 平臺,那么優先考慮 Flutter 吧。
你選擇 React Native 也沒問題,說起來最近 React Native 的版本號已經到了 0.67 了,還是突破不到 1.0 ····
當然大家可能會關心框架是否有坑的問題,本質上所有框架都有坑,甚至網絡因素都可能會成為你的痛點,問題在于你是否接受這些坑,平臺的背后本身就是“臟活”和“累活”, Flutter 的全平臺之路很艱難,能做好 Android 和 iOS 的支持和兼容就很不容易了。
最后還是要例行補充這一點:
跨平臺之所以是跨平臺,首先就是要有對應原生平臺的存在, 很多原生平臺的問題都需要回歸到平臺去解決,那些喜歡吹 xxx 制霸原生要涼的節奏,僅僅是因為“你的焦慮會成為它們的利潤”。