原生開發如何學習 Flutter | 谷歌社區說

Hello 大家好,我是《Flutter 開發實戰詳解》的作者,Github GSY 系列開源項目的負責人郭樹煜,目前開源的 gsy_github_app_flutter 以 13k+ 的 star 在中文總榜的 dart 排行上暫處第一名。

image

數據來源: https://github.com/GrowingGit/GitHub-Chinese-Top-Charts/blob/master/content/charts/overall/software/Dart.md

開始之前

Flutter 開源至今其實已經將近 7 年的時間,而我是從 2017 年開始接觸的 Flutter ,如今在 2022 年看來,Flutter 已經是不再是以前小眾的跨平臺框架

如圖所示,可以看到如今的 Flutter 已經有高達 135k 的 star , 10k+ Open 和 50k+ Closed 的 issue 也足以說明 Flutter 社區和用戶的活躍度。

image

在去年下半旬的數據調查中,Flutter 也成為了排名第一的被使用和被喜愛的跨平臺框架,這里說這么說并不是說你一定要去學 Flutter ,而是說不管我們喜不喜歡,目前 Flutter 已經證明了它的價值。

image
image.png

數據來源: 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 應用使用的情況。

數據來源: https://juejin.cn/post/7012382656578977806

最后 Flutter 在 Web 和 PC 端也有支持,但是我暫時還未投入生產使用,目前可以簡單總結就是:

Web

  • Flutter Web 目前支持 HtmlCanvasCanvasKit(WASM),默認是移動端使用 HTML 而桌面端使用 WASM;
  • pub.dev 上 60% 左右的包是 Web 兼容;
  • 體積和 SEO 是使用過程中最需要提前考慮的問題;

可參考資料: https://juejin.cn/post/7059619009213726733

Desktop

PC 端目前相對更弱勢一些,如果是和 Electron 比較,可以簡單認為, Flutter PC 版可以使用更低的內存占用和更小的體積,甚至更好的 FFI 繼承 C 的能力,但是同樣的生態目前也更弱,第三方支持相對較少,需要自己獨立解決的問題會相對更多。

Window 可投入生產版本已經正式發布

可參考資料: https://juejin.cn/post/7018450473292136456

Flutter 和原生開發的不同

Flutter 作為跨平臺的 UI 框架,它主要的特點是做到:在性能還不錯的情況下,框架的 UI 于平臺無關,而從平臺的角度上看, Flutter 其實就是一個“單頁面”的應用。

1、單頁面應用

什么是“單頁面”應用?

也就是對于原生 Android 和 iOS 而言,整個跨平臺 UI 默認都是運行在一個 Activity / ViewController 上面,默認情況下只會有一個 Activity / ViewController, 事實上 Flutter、 ReactNative 、Weex 、Ionic 默認情況下都是如此,所以一般情況下框架的路由和原生的路由也是沒有直接關系

舉個例子,如下圖所示,

  • 在當前 Flutter 端路由堆棧里有 FlutterAFlutterB 兩個頁面 Flutter 頁面;
  • 這時候打開新的 Activity / ViewController,啟動了原生頁面X,可以看到原生頁面 X 作為新的原生頁面加入到原生層路由后,把 FlutterActivity / FlutterViewController 給擋住,也就是把 FlutterAFlutterB 都擋住;
  • 這時候在 Flutter 層再打開新的 FlutterC 頁面,可以看到依然會被原生頁面X擋住;
image

所以通過這部分內容可以看出來,跨平臺應用默認情況下作為單頁面應用,他們的路由堆棧是和原生層存在不兼容的隔離

當然這里面重復用了一個詞:“默認”,也就是其實可以支持自定義混合堆棧的,比如官方的 FlutterEngineGroup ,第三方框架 flutter_boostmix_stackflutter_thrio 等等都是為了解決混合開發的場景。

2、渲染邏輯

介紹完“單頁面”部分的不同,接下來講講 Flutter 在渲染層面的不同。

在渲染層面 Flutter 和其他跨平臺框架存在較大差異,如下圖所示是現階段常見的渲染模式對比:

image
  • 對于原生 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 上內部使用可以是 SurfaceViewTextureView 或者 FlutterImageView ;在 iOS 上是 UIView 通過 Layer 實現的渲染。

所以 Flutter 的控件在不同平臺可以得到一致效果,但是和原生控件進行混合也會有較高的成本和難度,在接入原生控件的能力上,Flutter 提供了 PlatformView 的機制來實現接入, PlatformView 本身的實現會比較容易引發內存和鍵盤等問題,所以也帶來了較高的接入成本。

目前最新版本基本強制要求 Hybrid Composition ,所以相對以前的 PlatformView 會好一點點,當然可能遇到的問題還是有的。比如密碼鍵盤切換,切換頁面時 PlatformView 時頁面閃動。

3、項目結構

image

如上圖所示,默認情況下 Flutter 工程結構是這樣的:

  • android 原生的工程目錄,可以配置原生的 appNamelogo ,啟動圖, AndroidManifest 等等;
  • ios 工程目錄,配置啟動圖,logo,應用名稱,plist 文件等等;
  • build 目錄,這個目錄是編譯后出現,一般是 git 的 ignore 目錄,打包過程和輸入結果都在這個目錄下,Android 原生的打包過程輸出也被重定向輸出到這里;
  • lib 目錄,用來寫 dart 代碼的,入口文件一般是 main.dart
  • pubspec.yaml 文件,Flutter 工程里最重要的文件之一,不管是靜態資源引用(圖片,字體)、第三方庫依賴還是 Dart 版本聲明都寫在這里。

如下圖是使用是關于 pubspec.yaml 文件的結構介紹

image

需要注意,當這個文件發生改變時,需要重新執行 flutter pub get,并且 stop 應用之后重新運行項目,而不是使用 hotload

如下所示是 Flutter 的插件工程,Flutter 中分為 PackagePlugin ,如果是

  • Package 項目屬于 Flutter 包工程,不會包含原生代碼;
  • Plugin 項目屬于 Flutter 插件工程,包含了 Android 和 iOS 代碼;
image

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 運行時會根據這個路徑動態添加依賴。

image

默認情況下 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.soflutter.so 兩個動態庫;iOS 會生成 App.frameworkFlutter.framework 兩個文件。

所以 Flutter 的第三方熱更新市面上常見的有:MxFlutterFairKrakenliteAppNEJFlutterFlap(MTFlutter)、flutter_code_push (chimera) 等等,而這些框架都不會是直接下發可執行的二進制文件,大致市面上根據 DSL 的不同,動態化方案可以分為兩大類:面向前端的和面向終端。

如下圖所示,例如 WXG 的 LiteApp、騰訊的 MxFlutter 和阿里的 Kraken (北海) 就是面向前端 ,使用 JS/TS 。

image

參考資料: https://mp.weixin.qq.com/s/OpgqjTIiB6z9YiN1FTDeYQ

如下圖所示:例如 Flapflutter_code_push 就是面向終端,主要是對 Dart 的 DSL 或者編碼下功夫。

image

參考資料: https://tech.meituan.com/2020/06/23/meituan-flutter-flap.html

最后,關于 Flutter 熱更新動態化的支持,可以參考這個表格:

image

參考資料:Flutter實現動態化更新-技術預研 https://juejin.cn/post/7033708048321347615

5、Flutter 簡單介紹

這里介紹下 Flutter Dart 部分相關的內容,對于原生開發來說,Flutter 主要優先了解響應式和Widget

響應式

響應式編程也叫做聲明式編程,這是現在前端開發的主流,當然對于客戶端開發的一種趨勢,比如 Jetpack ComposeSwiftUI

Jetpack Compose 和 Flutter 的在某些表層上看真的很相似。

響應式簡單來說其實就是你不需要手動更新界面,只需要把界面通過代碼“聲明”好,然后把數據和界面的關系接好,數據更新了界面自然就更新了。

從代碼層面看,對于原生開發而言,沒有 xml 的布局,沒有 storyboard,布局完全由代碼完成,所見即所得,同時也不會需要操作界面“對象”去進行賦值和更新,你所需要做的就是配置數據和界面的關系

響應式開發比數據綁定或者 MVVM 不同的地方是,它每次都是重新構建和調整整個渲染樹,而不是簡單的對 UI 進行 visibility 操作。

如下圖所示,是 Flutter 下針對響應式 UI 的典型第三方示例: responsive_framework

image
image
Widget

Widget 是 Flutter 里的基礎概念,也是我們寫代碼最直接接觸的對象,Flutter 內一切皆 Widget ,Widget 是不可變的(immutable),每個 Widget 狀態都代表了一幀。

所以 Widget 作為一個 immutable 對象,它不可能是真正工作的 UI 對象,在 Flutter 里真正的 View 級別對象是 ElementRenderObject , 其中 Element 的抽象對象就是我們經常用到的 BuildContext

舉個例子,如下代碼所示,其中 testUseAll 這個 Text 在同一個頁面下在三處地方被使用,并且代碼可以正常運行渲染,如果是一個真正的 View ,是不能在一個頁面下這樣被多個地方加載使用的。

image

所以 Flutter 中 Widget 更多只是配置文件的地位,用于描述界面的配置代碼,具體它們的實現邏輯、關系還有分類,可以看我寫的書 《Flutter開發實戰詳解》中 的第三章和第四章部分。

有趣的問題

最后說一個比較有意思的問題,之前有人說 Flutter 里是傳遞值還是引用?這個問題看過網上有不少文章解釋得很奇怪,存在一些誤導性的解釋,其實這個問題很簡單:

Flutter 里一切皆是對象, 就連 intdoublebool 也是對象,你覺得對象傳遞的是什么?

但是對于對象的操作是有區別的,比如對于 intdoubleclass+-*\ 等操作,其實是執行了這個 classoperator 操作符的操作, 然后返回了一個 num 對象。

image

而對于這個操作,只需要要去 dart vm 看看 Double 對象在進行加減乘除時做了什么,如下圖所示,看完相信就知道方法里傳遞 intdouble 對象后進行操作會是什么樣的結果。

image

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 平臺都敢支持選用 CanvaskitWASM 模式。

所以 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 制霸原生要涼的節奏,僅僅是因為“你的焦慮會成為它們的利潤”。

image
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容