給 Android 和 iOS 開發(fā)人員不一樣的 Flutter 基礎(chǔ)講解
一、單頁面應(yīng)用
了解 Flutter 之前,首先介紹一個(gè)簡(jiǎn)單基礎(chǔ)知識(shí)點(diǎn),「那就是大部分的移動(dòng)端跨平臺(tái)框架都是“單頁面”應(yīng)用」。
什么是“單頁面”應(yīng)用?也就是對(duì)于原生 Android 和 iOS 而言,「整個(gè)跨平臺(tái) UI 默認(rèn)都是運(yùn)行在一個(gè) Activity / ViewController 上面」,默認(rèn)情況下只會(huì)有一個(gè) Activity / ViewController, Flutter、 ReactNative 、Weex 、Ionic 默認(rèn)情況下都是如此,「所以一般情況下框架的路由和原生的路由是沒有直接關(guān)系」。
舉個(gè)例子,如下圖所示,
在當(dāng)前 Flutter 端路由堆棧里有 FlutterA和FlutterB兩個(gè)頁面 Flutter 頁面;這時(shí)候打開新的 Activity/ViewController,啟動(dòng)了「原生頁面X」,可以看到「原生頁面X」作為新的原生頁面加入到原生層路由后,把FlutterActivity/FlutterViewController給擋住,也就是把FlutterA和FlutterB都擋??;這時(shí)候在 Flutter 層再打開新的 FlutterC頁面,可以看到依然會(huì)被原生頁面X擋住;

所以通過這部分內(nèi)容可以看出來,「跨平臺(tái)應(yīng)用默認(rèn)情況下作為單頁面應(yīng)用,他們的路由堆棧是和原生層存在不兼容的隔離」。
?當(dāng)然這里面重復(fù)用了一個(gè)詞:「“默認(rèn)”」,也就是其實(shí)可以支持自定義混合堆棧的,比如官方的
?FlutterEngineGroup,第三方框架flutter_boost、mix_stack、flutter_thrio等等。
二、渲染邏輯
介紹完“單頁面”部分的不同,接下來講講 Flutter 在渲染層面的不同。
在渲染層面 Flutter 和其他跨平臺(tái)框架存在較大差異,如下圖所示是現(xiàn)階段常見的渲染模式對(duì)比:

對(duì)于原生 Android 而言,是「原生代碼經(jīng)過 skia 最后到 GPU 完成渲染繪制」,Android 原生系統(tǒng)本身自帶了 skia; 對(duì)于 Flutter 而言,「Dart 代碼里的控件經(jīng)過 skia 最后到 GPU 完成渲染繪制」,這里在 Andriod 上使用的系統(tǒng)的 skia ,而在 iOS 上使用的是打包到項(xiàng)目里的 skia ; 對(duì)于 ReactNative/Weex 等類似的項(xiàng)目,它們是「運(yùn)行在各自的 JS 引擎里面,最后通過映射為原生的控件,利用原生的渲染能力進(jìn)行渲染」; 對(duì)于 ionic 等這類 Hybird 的跨平臺(tái)框架,使用的主要就是 「WebView 的渲染能力」;
?skia 在 Android 上根據(jù)不同情況就可能會(huì)是
?OpenGL或者Vulkan,在 iOS 上如果有支持Metal也會(huì)使用Metal加速渲染。
通過前面的介紹,可以看出了:
ReactNative/Weex 這類跨平臺(tái)和原生平臺(tái)存在較大關(guān)聯(lián):
好處就是:如果需要使用原生平臺(tái)的控件能力,接入成本會(huì)比較低; 壞處自然就是:渲染嚴(yán)重依賴平臺(tái)控件的能力,耦合較多,不同系統(tǒng)之間原生控件的差異,同個(gè)系統(tǒng)的不同版本在控件上的屬性和效果差異,組合起來在后期開發(fā)過程中就是很大的維護(hù)成本。、
?例如:在 iOS 上調(diào)試好的樣式,在 Android 上出現(xiàn)了異常;在 Android 上生效的樣式,在 iOS 上沒有支持;在 iOS 平臺(tái)的控件效果,在 Android 上出現(xiàn)了不一樣的展示,比如下拉刷新,Appbar等;
?
Flutter 與之不同的地方就是渲染直接利用 skia 和 GPU 交互,在 Android 和 iOS 平臺(tái)上實(shí)現(xiàn)了平臺(tái)無關(guān)的控件,簡(jiǎn)單說就是 Flutter 里的 Widget 大部分都是和 Android 和 iOS 沒有關(guān)系。
「本質(zhì)上原生平臺(tái)是提供一個(gè)類似 Surface 的畫板,之后剩下的只需要由 Flutter 來渲染出對(duì)應(yīng)的控件」
?一般是使用
?FlutterView作為渲染承載,它在 Android 上內(nèi)部使用可以是SurfaceView、TextureView或者FlutterImageView;在 iOS 上是UIView通過Layer實(shí)現(xiàn)的渲染。
「所以 Flutter 的控件在不同平臺(tái)可以得到一致效果,但是和原生控件進(jìn)行混合也會(huì)有較高的成本和難度」,在接入原生控件的能力上,F(xiàn)lutter 提供了 PlatformView 的機(jī)制來實(shí)現(xiàn)接入, PlatformView 本身的實(shí)現(xiàn)會(huì)比較容易引發(fā)內(nèi)存和鍵盤等問題,所以也帶來了較高的接入成本。
三、項(xiàng)目結(jié)構(gòu)

如上圖所示,默認(rèn)情況下 Flutter 工程結(jié)構(gòu)是這樣的:
android原生的工程目錄,可以配置原生的appName,logo,啟動(dòng)圖,AndroidManifest等等;ios工程目錄,配置啟動(dòng)圖,logo,應(yīng)用名稱,plist文件等等;build目錄,這個(gè)目錄是編譯后出現(xiàn),一般是 git 的 ignore 目錄,打包過程和輸入結(jié)果都在這個(gè)目錄下,Android 原生的打包過程輸出也被重定向輸出到這里;lib目錄,用來寫 dart 代碼的,入口文件一般是main.dart;pubspec.yaml文件,F(xiàn)lutter 工程里最重要的文件之一,不管是靜態(tài)資源引用(圖片,字體)、第三方庫依賴還是 Dart 版本聲明都寫在這里。
如下圖是使用是關(guān)于 pubspec.yaml 文件的結(jié)構(gòu)介紹

?需要注意,當(dāng)這個(gè)文件發(fā)生改變時(shí),需要重新執(zhí)行
?flutter pub get,并且stop應(yīng)用之后重新運(yùn)行項(xiàng)目,而不是使用hotload。
如下所示是 Flutter 的插件工程,F(xiàn)lutter 中分為 Package 和 Plugin ,如果是
Package項(xiàng)目屬于 Flutter 包工程,不會(huì)包含原生代碼;Plugin項(xiàng)目屬于 Flutter 插件工程,包含了 Android 和 iOS 代碼;

四、打包調(diào)試
Flutter 運(yùn)行之前都需要先執(zhí)行 flutter pub get 來先同步下載第三方代碼,下載的第三方代碼一般存在于(Mac) /Users/你的用戶名/.pub-cache 目錄下 。
下載依賴成功后,可以直接通過 flutter run 或者 IDE 工具點(diǎn)擊運(yùn)行來啟動(dòng) Flutter 項(xiàng)目,這個(gè)過程會(huì)需要原生工程的一些網(wǎng)絡(luò)同步工作,比如:
Android 上的 Gradle 和 aar 依賴包同步; iOS 上需要 pod install 同步一些依賴包;
如果需要在項(xiàng)目同步過程中查看進(jìn)度:
Android 可以到 android/目錄下執(zhí)行./gradlew assembleDebug查看同步進(jìn)度;iOS 可以到 ios/目錄下執(zhí)行pod install,查看下載進(jìn)度;
同步的插件中,如果是 Plugin 帶有原生平臺(tái)的代碼邏輯,那么可以在項(xiàng)目根目錄下看到一個(gè)叫做 .flutter_plugins 和 .flutter-plugins-dependencies 的文件,它們是 git ignore 的文件,Android 和 iOS 中會(huì)根據(jù)這個(gè)文件對(duì)本地路徑的插件進(jìn)行引用,后面 Flutter 運(yùn)行時(shí)會(huì)根據(jù)這個(gè)路徑動(dòng)態(tài)添加依賴。

默認(rèn)情況下 Flutter 在 「debug 下是 JIT 的運(yùn)行模式」所以運(yùn)行效率會(huì)比較低,速度相對(duì)較慢,但是可以 hotload。
在 「release 下是 AOT 模式」,運(yùn)行速度會(huì)快很多,同時(shí) Flutter 在「模擬器上一般默認(rèn)會(huì)使用 CPU 運(yùn)行,在真機(jī)上會(huì)使用 GPU 運(yùn)行」,所以性能表現(xiàn)也不同。
?另外 iOS 14 真機(jī)上 debug 運(yùn)行,斷后鏈接后再次啟動(dòng)是無法運(yùn)行的。
?
如果項(xiàng)目存在緩存問題,可以「直接執(zhí)行 flutter clean 來清理緩存」。
最后說下 Flutter 的為什么不支持熱更新?
前面講過 ReactNative 和 Weex 是通過將 JS 代碼里的控件轉(zhuǎn)化為原生控件進(jìn)行渲染,所以本質(zhì)上 JS 代碼部分都只是文本而已,利用 code-push 推送文本內(nèi)容本質(zhì)上并不會(huì)違法平臺(tái)要求。
而 Flutter 打包后的文件是二進(jìn)制文件,推送二進(jìn)制文件明顯是不符合平臺(tái)要求的。
?release 打包后的 Android 會(huì)生成
?app.so和flutter.so兩個(gè)動(dòng)態(tài)庫;iOS 會(huì)生成App.framework和Flutter.framework兩個(gè)文件。
五、Flutter 簡(jiǎn)單介紹
最后簡(jiǎn)單介紹下 Flutter Dart 部分相關(guān)的內(nèi)容,對(duì)于原生開發(fā)來說,F(xiàn)lutter 主要優(yōu)先了解這三點(diǎn):「響應(yīng)式、Widget 和狀態(tài)管理」 。
響應(yīng)式
響應(yīng)式編程也叫做聲明式編程,這是現(xiàn)在前端開發(fā)的主流,當(dāng)然對(duì)于客戶端開發(fā)的一種趨勢(shì),比如 Jetpack Compose 、SwiftUI 。
?Jetpack Compose 和 Flutter 的在某些表層上看真的很相似。
?
「響應(yīng)式簡(jiǎn)單來說其實(shí)就是你不需要手動(dòng)更新界面,只需要把界面通過代碼“聲明”好,然后把數(shù)據(jù)和界面的關(guān)系接好,數(shù)據(jù)更新了界面自然就更新了?!?/strong>
從代碼層面看,對(duì)于原生開發(fā)而言,「沒有 xml 的布局,沒有 storyboard」,布局完全由代碼完成,所見即所得,同時(shí)也「不會(huì)需要操作界面“對(duì)象”去進(jìn)行賦值和更新,你所需要做的就是配置數(shù)據(jù)和界面的關(guān)系」。
?響應(yīng)式開發(fā)比數(shù)據(jù)綁定或者 MVVM 不同的地方是,它每次都是重新構(gòu)建和調(diào)整整個(gè)渲染樹,而不是簡(jiǎn)單的對(duì) UI 進(jìn)行
?visibility操作。
Widget
Widget 是 Flutter 里的基礎(chǔ)概念,也是我們寫代碼最直接接觸的對(duì)象,「Flutter 內(nèi)一切皆 Widget ,Widget 是不可變的(immutable),每個(gè) Widget 狀態(tài)都代表了一幀?!?/strong>
所以 Widget 作為一個(gè) immutable 對(duì)象,它不可能是真正工作的 UI 對(duì)象,「在 Flutter 里真正的 View 級(jí)別對(duì)象是 Element 和 RenderObject , 其中 Element 的抽象對(duì)象就是我們經(jīng)常用到的 BuildContext」。
舉個(gè)例子,如下代碼所示,其中 testUseAll 這個(gè) Text 在同一個(gè)頁面下在三處地方被使用,并且代碼可以正常運(yùn)行渲染,如果是一個(gè)真正的 View ,是不能在一個(gè)頁面下這樣被多個(gè)地方加載使用的。

所以 Flutter 中 「Widget 更多只是配置文件的地位」,用于描述界面的配置代碼,具體它們的實(shí)現(xiàn)邏輯、關(guān)系還有分類,可以看我寫的書 「《Flutter開發(fā)實(shí)戰(zhàn)詳解》中」 的第三章和第四章部分。
狀態(tài)管理
Flutter 作為響應(yīng)式開發(fā)框架,本質(zhì)上它其實(shí)不再追求什么 MVC 、MVP、MVVVM 的設(shè)計(jì)模式,它更多是對(duì)界面狀態(tài)的管理。
?就是要拋棄以前在原生平臺(tái)上,需要拿到
?View的對(duì)象,然后做對(duì)其進(jìn)行 UI 設(shè)置這種思路。
Flutter 上更多需要管理數(shù)據(jù)的流向,比如:
數(shù)據(jù)是從哪里發(fā)出,然后再到哪里消費(fèi); 數(shù)據(jù)是單向還是雙向; 數(shù)據(jù)需要進(jìn)過哪些中間轉(zhuǎn)化; 數(shù)據(jù)是從哪一層開始往下傳遞; 數(shù)據(jù)綁定了哪些地方; 如何實(shí)現(xiàn)多個(gè)地方的局部刷新;
因?yàn)閷?duì)于界面來說,它只需要根據(jù)數(shù)據(jù)進(jìn)行變化即可,我們不需要獲取它去單獨(dú)設(shè)置,所以 Flutter 中有各種數(shù)據(jù)管理和共享的框架,比較流行的有 provider 、 getx 、 flutter_redex、flutter_mobx 等等。
有趣的問題
最后說一個(gè)比較有意思的問題,之前有人說 「Flutter 里是傳遞值還是引用」?這個(gè)問題看過網(wǎng)上有不少文章解釋得很奇怪,存在一些誤導(dǎo)性的解釋,其實(shí)這個(gè)問題很簡(jiǎn)單:
「Flutter 里一切皆是對(duì)象, 就連 int 、 double 、bool 也是對(duì)象,你覺得對(duì)象傳遞的是什么?」
但是對(duì)于對(duì)象的操作是有區(qū)別的,比如對(duì)于 int 、 double 等 class 的 + 、- 、* 、 \ 等操作,其實(shí)是執(zhí)行了這個(gè) class 的 operator 操作符的操作, 然后返回了一個(gè) num 對(duì)象。

而對(duì)于這個(gè)操作,只需要要去 dart vm 看看 Double 對(duì)象在進(jìn)行加減乘除時(shí)做了什么,如下圖所示,看完相信就知道方法里傳遞 int 、double 對(duì)象后進(jìn)行操作會(huì)是什么樣的結(jié)果。

-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取