谷歌的 Flutter 為開發(fā)人員提供了一種構(gòu)建 Android 和 iOS 原生用戶界面的方法,為開發(fā)人員減少了很多相關(guān)的負擔(dān),包括幫助他們節(jié)省時間。有些人可能想知道 Flutter 項目和原生 Android 或 iOS 項目有何不同,例如它們的渲染機制或事件傳遞機制,或者在出現(xiàn)問題時開發(fā)人員如何修復(fù)錯誤和實現(xiàn)項目。
為了回答這些問題,這篇文章將以“hello_flutter”為例,首先介紹 Flutter 的設(shè)計原則,然后討論定制和優(yōu)化,并為對 Flutter 感興趣的開發(fā)人員提供了一些可遵循的步驟。
Flutter 基礎(chǔ)
架構(gòu)
Flutter 的架構(gòu)包含三個不同的層:框架、引擎和嵌入器。
Flutter 的框架使用 Dart 實現(xiàn),提供了 Material 風(fēng)格的小部件、Cupertino 風(fēng)格的小部件(用于 iOS)、文本 / 圖像 / 按鈕小部件、渲染、動畫、手勢等。該層的核心代碼包含了 flutter 代碼庫的包和 sky_engine 代碼庫的包(dart:ui 庫提供了 flutter 框架和引擎之間的接口),例如 io、async 和 ui 包。
Flutter 的引擎是用 C++ 實現(xiàn)的,并包含了 Skia、Dart 和 Text。Skia 是一個開源的 2D 圖形庫,為各種硬件和軟件平臺提供通用 API。它是谷歌 Chrome、Chrome OS、Android、Mozilla Firefox、Firefox OS 等產(chǎn)品的圖形引擎。支持的平臺包括 Windows7+、macOS 10.10.5+、iOS8+、Android4.1+、Ubuntu14.04+ 等。
引擎的 Dart 部分主要包括 Dart 運行時和垃圾回收(GC)。如果 Flutter 在調(diào)試模式下運行,則還包括 JIT(Just in Time)支持,而如果是在發(fā)布模式下,則通過 AOT(Ahead of Time)將 Dart 代碼編譯為原生的“arm”代碼,這個時候就沒有 JIT。Text 是指以下的文本渲染庫:libtxt 庫(用于字體選擇和分隔線),派生自 minikin 和 HartBuzz(用于字形和圖形)。Skia 充當(dāng)渲染后端,在 Android 上使用 FreeType 渲染,在 iOS 上使用 Fuchsia 和 CoreGraphics 渲染。
嵌入器可將 Flutter 嵌入到各種平臺中。它的主要任務(wù)是渲染 Surface 設(shè)置、線程設(shè)置和插件。我們可以看到,F(xiàn)lutter 的平臺相關(guān)層是最小的,其中平臺(例如 iOS)只提供畫布,其余與渲染相關(guān)的邏輯發(fā)生在 Flutter 內(nèi)部,從而實現(xiàn)良好的跨平臺一致性。
工程結(jié)構(gòu)
本文中使用的開發(fā)環(huán)境是 Flutter beta v0.3.1,對應(yīng)的引擎提交標(biāo)簽為 09d05a389。
“hello_flutter”示例項目的工程結(jié)構(gòu)如下:
在上面的例子中,“ios”是 iOS 的代碼,使用 CocoaPods 來管理依賴項,“android”是 Android 的代碼,使用 Gradle 來管理依賴項,“l(fā)ib”是 Dart 代碼,使用 pub 來管理依賴項。pub 中與 Cocoapods 的 Podfile 和 Podfile.lock 相對應(yīng)的分別是 pubspec.yaml 和 pubspec.lock。
模式
Flutter 支持常見的模式,包括調(diào)試、發(fā)布和分析,但它們之間存在一些區(qū)別。
Flutter 的調(diào)試模式對應(yīng)于 Dart 的 JIT 模式,也稱為檢查模式或慢模式,并支持 iOS 和 Android 的設(shè)備和模擬器。在這個模式下,可以啟用斷言功能,包括所有調(diào)試信息、服務(wù)擴展和調(diào)試輔助工具,如“observatory”。這個模式針對快速開發(fā)進行了優(yōu)化,但并沒有針對運行速度、程序包大小或部署進行過優(yōu)化。在這種模式下,采用了基于 JIT 的編譯技術(shù),支持流行的亞秒級有狀態(tài)熱重載。
Flutter 的發(fā)布模式對應(yīng)于 Dart 的 AOT 模式,該模式的目標(biāo)是部署到最終用戶的設(shè)備上,支持真實設(shè)備而不是模擬器。在此模式下,所有斷言都被禁用,為了盡可能多地刪除調(diào)試信息,還會禁用所有調(diào)試工具。這個模式針對快速啟動、快速運行和包大小進行了優(yōu)化,同時禁止所有調(diào)試輔助和服務(wù)擴展。
Flutter 的分析模式與發(fā)布模式類似,只是添加了對服務(wù)擴展和跟蹤的支持,并最小化使用跟蹤信息所需的依賴性。例如,“observatory”可以連到進程上。分析模式不支持模擬器,因為模擬器上的診斷不能代表實際性能。
由于 Flutter 的分析模式和發(fā)布模式在編譯方面沒有差異,因此本文僅討論調(diào)試模式和發(fā)布模式。
實際上,使用 Flutter 開發(fā)的 iOS 或 Android 項目仍然是標(biāo)準(zhǔn)的 iOS 或 Android 項目。Flutter 通過在 BuildPhase 中添加 shell 來生成并嵌入 App.framework 和 Flutter.framework(iOS),并通過 Gradle 添加 flutter.jar 和 vm/isolate_snapshot_data/instr(Android)來編譯相關(guān)代碼并將其嵌入到原生應(yīng)用程序中。因此,本文主要討論 Flutter 引入的構(gòu)建和運行原則。盡管編譯目標(biāo)包括 arm、x64、x86 和 arm64,但它們的原理都很類似,所以本文僅討論與 arm 相關(guān)的原則。(如果沒有特殊描述,Android 默認為 armv7。)
iOS 的代碼編譯和執(zhí)行
在發(fā)布模式下編譯
在發(fā)布模式下,iOS 項目的 Dart 代碼構(gòu)建過程如下:
在圖中,gen_snapshot 是 Dart 編譯器,它使用搖樹優(yōu)化技術(shù)(類似于可生成最小包的依賴樹邏輯,因此在 Flutter 中禁用 Dart 支持的反射)生成匯編形式的機器碼,然后通過編譯工具鏈(如 xcrun)生成最終 App.framework。換句話說,對于所有的 Dart 代碼,包括業(yè)務(wù)邏輯代碼和第三方軟件包代碼,它們所依賴的 Flutter 框架(Dart)代碼最終會變成 App.framework。
搖樹優(yōu)化功能位于 gen_snapshot 中。要查看相應(yīng)的邏輯,可以訪問:
engine/src/third_party/dart/runtime/vm/compiler/aot/precompiler.cc
Dart 代碼最終對應(yīng)于 App.framework 的符號如下:
事實上,與 Android 類似,App.framework 也包括了四個部分:kDartVmSnapshotData、kDartVmSnapshotInstructions、kDartIsolateSnapshotData 和 kDartIsolateSnapshotInstructions。為什么 iOS 使用 App.framework 而不是像 Android 那樣的四個文件?由于 iOS 系統(tǒng)的限制,F(xiàn)lutter 引擎無法在運行時將內(nèi)存頁標(biāo)記為可執(zhí)行,而在 Android 下則可以。
Flutter.framework 對應(yīng)于 Flutter 架構(gòu)中的引擎和嵌入器。實際上,F(xiàn)lutter.framework 位于 flutter 代碼庫的 /bin/cache/artifacts/engine/ios * 中,默認是從谷歌的代碼庫中提取的。當(dāng)需要自定義變更時,可以使用 Ninja 系統(tǒng)下載和構(gòu)建與引擎相關(guān)的代碼。
Flutter 相關(guān)代碼的最終產(chǎn)物是 App.framework(由 Dart 代碼生成)和 Flutter.framework(引擎)。在 Xcode 項目中,Generated.xcconfig 描述了與 Flutter 相關(guān)的環(huán)境配置信息,在 Runner 項目設(shè)置中新增的 xcode_backend.sh 實現(xiàn)了一個副本(從 flutter 框架代碼庫到 Runner 項目根目錄下的 Flutter 目錄)和 Flutter.framework 的嵌入以及 App.framework 的編譯和嵌入。
最終生成的 Runner.app 中與 Flutter 相關(guān)的文件如下:
flutter_assets 是 Flutter 資源,App.framework 和 Flutter.framework 是代碼,位于 Frameworks 目錄中。
在發(fā)布模式下運行
與 Flutter 相關(guān)的渲染、事件和通信處理的邏輯如下:
Dart 主函數(shù)的調(diào)用棧如下:
在調(diào)試模式下編譯
在調(diào)試模式下,F(xiàn)lutter 的編譯結(jié)構(gòu)與發(fā)布模式中的編譯結(jié)構(gòu)類似。差異主要表現(xiàn)在兩點:
1.Flutter.framework
在調(diào)試模式下,框架包含 JIT 支持,而在發(fā)布模式下沒有 JIT 支持。
2.App.framework
與 Flutter.framework 不同,App.framework 是原生機器碼,與 AOT 模式中的 Dart 代碼對應(yīng),而在 JIT 模式下,App.framework 只有幾個簡單的 API,Dart 代碼存在于 snapshot_blob.bin 文件中。這部分代碼的快照是帶有簡單標(biāo)記的源代碼的腳本快照。所有的注釋和空格字符都被移除,常量被規(guī)格化,不存在機器代碼、搖樹優(yōu)化或代碼混淆。
App.framework 中的符號表如下:
針對 Runner.app/flutter_assets/snapshot_blob.bin 上運行 strings 命令可以查看以下內(nèi)容:
調(diào)試模式下主入口的調(diào)用棧如下:
Android 的代碼編譯和執(zhí)行
除了一些與平臺相關(guān)的功能之外,Android 其他邏輯(例如對應(yīng)于 AOT 的發(fā)布模式和對應(yīng)于 JIT 的調(diào)試模式)與 iOS 非常相似,只需注意兩個關(guān)鍵差異。
在發(fā)布模式下編譯
在發(fā)布模式下,Android Flutter 項目中的 Dart 代碼結(jié)構(gòu)如下:
vm/isolate_snapshot_data/instr 是 arm 指令,引擎會在運行時加載它們并將其標(biāo)記為可執(zhí)行。vm_snapshot_data/instr 用于初始化 DartVM,調(diào)用入口為 Dart_Initialize(Dart_api.h)。isolate_snapshot_data/instr 對應(yīng)于創(chuàng)建新隔離的 App 代碼,調(diào)用入口為 Dart_CreateIsolate(Dart_api.h)。
Flutter.jar 類似于 iOS 的 Flutter.framework,包括引擎代碼(Flutter.jar 中的 libflutter.so)以及一組將 Flutter 嵌入到 Android 中的類和接口(FlutterMain、FlutterView、FlutterNativeView 等)。事實上,flutter.jar 位于 Flutter 代碼庫的 /bin/cache/artifacts/engine/android* 中,默認從谷歌代碼庫中拉取。當(dāng)需要自定義更改時,可以使用 Ninja 系統(tǒng)下載引擎源代碼來生成 flutter.jar。
以 isolate_snapshot_data/instr 為例,運行 disarm 命令的結(jié)果如下:
其 APK 結(jié)構(gòu)如下:
在全新安裝 APK 之后,使用時間戳(結(jié)合 versionCode 與 packageinfo 的 lastUpdateTime)來決定是否將 Flutter 相關(guān)文件復(fù)制到本地 app 數(shù)據(jù)目錄。復(fù)制的內(nèi)容如下:
isolate/vm_snapshot_data/instr 最終位于 app 本地數(shù)據(jù)目錄中,該目錄是可寫的。因此,可以通過下載和替換這些快照就可以完成 app 的替換和更新。
在發(fā)布模式下執(zhí)行
下圖顯示了發(fā)布模式下的執(zhí)行流程:
在調(diào)試模式下編譯
與 iOS 的情況一樣,Android 中的調(diào)試模式和發(fā)布模式之間的區(qū)別主要在于以下兩個組件:
1.flutter.jar
這里的區(qū)別與之前針對 iOS 所描述的完全相同。
2.app 代碼
app 代碼位于 flutter_assets 下的 snapshot_blob.bin 中,就像 iOS 一樣。
在介紹完 Flutter 有關(guān) iOS 和 Android 的編譯原理后,我們將重點介紹如何配置 Flutter 及其引擎,以進行自定義和優(yōu)化。因為 Flutter 使用了敏捷開發(fā)模式,所以當(dāng)前出現(xiàn)的問題在未來可能就不是問題。因此,以下部分不著重于如何解決問題,而是著重于不同類型的場景,這些場景體現(xiàn)了 Flutter 自定義和優(yōu)化方面的原則。
在 Flutter 中進行定制和優(yōu)化開發(fā)
Flutter 是一個復(fù)雜的系統(tǒng)。除了上面提到的三層架構(gòu),它還包括 Flutter Android Studio(Intellij)插件、pub 代碼庫管理和其他各種組件。不過,定制和優(yōu)化通常與 Flutter 的工具鏈有關(guān),代碼位于 Flutter 代碼庫的 flutter_tools 包中。我們現(xiàn)在將分別介紹如何針對 Android 和 iOS 進行自定義。
自定義 Android
自定義 Android 涉及 flutter.jar、libflutter.so(在 flutter.jar 中)、gen_snapshot、flutter.gradle 和 flutter_tools。在自定義 Flutter 時,需要注意以下事項:
1. 將 Android 中的目標(biāo)設(shè)置為 armeabi
這是構(gòu)建過程的一部分,邏輯是在 flutter.gradle 中定義的。如果要應(yīng)用程序通過 armeabi 支持 armv7/arm64,必須修改 Flutter 的默認邏輯,如下所示:
由于 Gradle 本身的特性,這部分可以在修改后生效。
2. 將 Android 設(shè)置為在啟動時默認使用第一個可啟動的 Activity
這部分與 flutter_tools 有關(guān),修改如下:
這里的要點不是如何修改它,而是如何讓更改生效。原則上,使用“flutter run/build/analyze/test/upgrade”這樣的命令實際上運行的是 Flutter 腳本(flutter_repo_dir/bin/flutter),然后再通過這個腳本運行 flutter_tools.snapshot(由 packages/flutter_tools 生成)。邏輯如下:
if [[ ! -f "SNAPSHOT_PATH" ]] || [[ ! -s "STAMP_PATH" ]] || [[ "(cat "STAMP_PATH")" != "revision" ]] || [[ "FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then
rm -f "$FLUTTER_ROOT/version"
touch "$FLUTTER_ROOT/bin/cache/.dartignore"
"$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh"
echo Building flutter tool...
if [[ "$TRAVIS" == "true" ]] || [[ "$BOT" == "true" ]] || [[ "$CONTINUOUS_INTEGRATION" == "true" ]] || [[ "$CHROME_HEADLESS" == "1" ]] || [[ "$APPVEYOR" == "true" ]] || [[ "$CI" == "true" ]]; then
PUB_ENVIRONMENT="$PUB_ENVIRONMENT:flutter_bot"
fi
export PUB_ENVIRONMENT="$PUB_ENVIRONMENT:flutter_install"
if [[ -d "$FLUTTER_ROOT/.pub-cache" ]]; then
export PUB_CACHE="${PUB_CACHE:-"$FLUTTER_ROOT/.pub-cache"}"
fi
while : ; do
cd "$FLUTTER_TOOLS_DIR"
"$PUB" upgrade --verbosity=error --no-packages-dir && break
echo Error: Unable to 'pub upgrade' flutter tool. Retrying in five seconds...
sleep 5
done
"$DART" --snapshot="$SNAPSHOT_PATH" --packages="$FLUTTER_TOOLS_DIR/.packages" "$SCRIPT_PATH"
echo "$revision" > "$STAMP_PATH"
fi
很明顯,如果要重建 flutter_tools,可以刪除 flutter_repo_dir/bin/cache/flutter_tools.stamp(以便重新生成它),或者注釋掉 if/fi(每次都重新生成)。
3. 在調(diào)試模式下發(fā)布 Flutter
如果你發(fā)現(xiàn) Flutter 在開發(fā)中出現(xiàn)延遲,并且猜測這可能是由邏輯或調(diào)試模式引起的,那么可以在發(fā)布模式下構(gòu)建 APK,或者將 Flutter 強制改為為發(fā)布模式,如下所示:
自定義 iOS
與自定義 iOS 相關(guān)的內(nèi)容包括 Flutter.framework、gen_snapshot、xcode_backend.sh 和 flutter_tools。在自定義 Flutter 時,需要注意以下事項:
1. 在優(yōu)化期間重復(fù)替換 Flutter.framework 導(dǎo)致的重新編譯
這部分的邏輯與構(gòu)建有關(guān),位于 xcode_backend.sh 中。為了確保每次都能獲得正確的 Flutter.framework,F(xiàn)lutter 每次都會根據(jù)配置查找并替換 Flutter.framework(請參閱 Generated.xcconfig 配置)。不過,這會導(dǎo)致重新編譯依賴于這個框架的項目代碼。必要的修改如下:
2. 在調(diào)試模式下發(fā)布 Flutter
要進行這個自定義,請將 Generated.xcconfig 中的 FLUTTER_BUILD_MODE 更改為“Release”,將 FLUTTER_FRAMEWORK_DIR 更改為與“Release”對應(yīng)的路徑。
3. 設(shè)置對 armv7 的支持
有關(guān)此方案的原始文檔,請參閱 https://github.com/flutter/engine/wiki/iOS-Builds-Supporting-ARMv7。
事實上,F(xiàn)lutter 本身在 iOS 中支持 armv7,但目前官方還沒有提供支持,因此必須修改相關(guān)邏輯,如下所示:
a. 生成默認邏輯:
Flutter.framework(arm64)
b. 修改 Flutter,以便每次都可以重建 flutter_tools。修改 build_aot.Dart 和 mac.Dart,將 iOS 的相關(guān) arm64 更改為 armv7,并將 gen_snapshot 更改為 i386。
可以通過以下命令生成 i386 的 gen_snapshot:
./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm
ninja -C out/ios_debug_arm
這里有一種隱含的邏輯:
構(gòu)造 gen_snapshot 的預(yù)定義宏(x86_64/__ i386 等)。目標(biāo) gen_snapshot 的結(jié)構(gòu)和最終的 App.framework 結(jié)構(gòu)必須保持一致。也就是說,使用 x86_64->x86_64->arm64 或 i386->i386->armv7。
c. 在 iPhone4S 上,當(dāng) gen_snapshot 生成不受支持的 SDIV 命令時會發(fā)生 EXC_BAD_INSTRUCTION(EXC_ARM_UNDEFINED)錯誤,這可以通過向 gen_snapshot(位于 build_aot.Dart 中)添加參數(shù)“——no-use-integer-division”來解決。其背后的邏輯如下:
d.“l(fā)ipo -create”在 a 步驟和 b 步驟中生成的 Flutter.framework,以便生成支持 armv7 和 arm64 的 Flutter.framework。
e. 修改 Flutter.framework 中的 Info.plist,并刪除:
UIRequiredDeviceCapabilities
arm64
同樣,你必須在 App.framework 上執(zhí)行相同的操作,以避免受到 AppStore 中應(yīng)用程序細化的影響。
調(diào)試 Flutter 工具
在調(diào)試模式下構(gòu)建 APK 時,如果你想知道 Flutter 的特定執(zhí)行邏輯,可以采用以下方法:
a. 了解 flutter_tools 命令的參數(shù)。
b. 將 packages/flutter_tools 作為 Dart 項目打開,并添加新的“Dart Command Line App”配置。將 Dart 文件設(shè)置為“flutter_tools.Dart”,將工作目錄設(shè)置為 Flutter 項目的路徑,并將 Program 參數(shù)設(shè)置為先前獲得的參數(shù)。
自定義和調(diào)試引擎
請考慮以下情形。假設(shè)我們基于 Flutter beta v0.3.1 定制和開發(fā)服務(wù),為了確保穩(wěn)定性,SDK 在某段時間內(nèi)不會升級。同時,F(xiàn)lutter v0.3.1 的 master 分支上修改了一個 bug,標(biāo)記為 fix_bug_commit。你將如何應(yīng)對這種情況?
1.Flutter beta v0.3.1 將其相應(yīng)的引擎代碼提交指定為 09d05a389。
請參閱:flutter/bin/internal/engine.version。
2. 獲取引擎代碼。
3. 由于 master 代碼是在第二步中獲得的,我們需要的是與特定提交相對應(yīng)的代碼(09d05a389),所以需要從這次提交拉取一個新分支:custom_beta_v0.3.1。
4. 在 custom_beta_v0.3.1(commit:09d05a389)上運行“gclient sync”,獲取與 flutter beta v0.3.1 相對應(yīng)的所有引擎代碼。
5. 使用“git cherry-pick fix_bug_commit”將 master 的變更同步到 custom_beta_v0.3.1。如果變更依賴了最新依賴項,則可能會發(fā)生編譯錯誤。
6. 運行以下命令應(yīng)用與 iOS 相關(guān)的變更:
./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm
ninja -C out/ios_debug_arm
./flutter/tools/gn --runtime-mode=release --ios --ios-cpu=arm
ninja -C out/ios_release_arm
./flutter/tools/gn --runtime-mode=profile --ios --ios-cpu=arm
ninja -C out/ios_profile_arm
./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm64
ninja -C out/ios_debug
./flutter/tools/gn --runtime-mode=release --ios --ios-cpu=arm64
ninja -C out/ios_release
./flutter/tools/gn --runtime-mode=profile --ios --ios-cpu=arm64
ninja -C out/ios_profile
要調(diào)試 Flutter.framework 源代碼,請使用以下命令:
./flutter/tools/gn --runtime-mode=debug --unoptimized --ios --ios-cpu=arm64
ninja -C out/ios_debug_unopt
用生成的文件替換 Flutter 中的 Flutter.framework 和 gen_snapshot,這樣就可以調(diào)試引擎源代碼。
7. 最后,運行以下命令應(yīng)用與 Android 相關(guān)的變更:
./flutter/tools/gn --runtime-mode=debug --android --android-cpu=arm
ninja -C out/android_debug
./flutter/tools/gn --runtime-mode=release --android --android-cpu=arm
ninja -C out/android_release
./flutter/tools/gn --runtime-mode=profile --android --android-cpu=arm
ninja -C out/android_profile
你可以使用生成的文件替換 flutter/bin/cache/artifacts/engine/android* 下的 gen_snapshot 和 flutter.jar,以便生成 Android 的 arm 和 debug/release/profile 文件包。
1、具有1-5工作經(jīng)驗的,面對目前流行的技術(shù)不知從何下手,
需要突破技術(shù)瓶頸的可以加。
2、在公司待久了,過得很安逸,
但跳槽時面試碰壁。
需要在短時間內(nèi)進修、跳槽拿高薪的可以加。
3、如果沒有工作經(jīng)驗,但基礎(chǔ)非常扎實,對java工作機制,
常用設(shè)計思想,常用java開發(fā)框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。
但是所學(xué)的知識點沒有系統(tǒng)化,很難在技術(shù)領(lǐng)域繼續(xù)突破的可以加。
5.?群號:高級架構(gòu)群?Java進階群:180705916備注好信息!
6.阿里Java高級大牛直播講解知識點,分享知識,
多年工作經(jīng)驗的梳理和總結(jié),帶著大家全面、
科學(xué)地建立自己的技術(shù)體系和技術(shù)認知!