怎樣的Flutter Engine定製流程,才能實現真正“開箱即用”?

引言

使用Flutter的過程中,如果遇到Flutter Engine的問題需要對其進行修改定製,那麼我們需要對它的編譯、打包以及發佈流程非常清楚。這次在Flutter升級的過程中,發現之前Flutter Engine編譯發佈的腳本存在不少問題:

  • 沒法做到開箱即用

  • 腳本分散在多個文件中不便於維護

  • Engine源碼準備過程過於複雜,需要對git庫重置和切換分支

  • 另外Flutter Engine從1.5.4升級到1.9.1,Flutter Engine的產物結構發生了變化。

因此,我們對Engine打包發佈的腳本進行了重寫,簡化編譯發佈的流程。

背景知識

想要對Engine進行定製,首先就要熟悉它的編譯和調試,雖然Flutter官方文檔中對Engine的編譯有說明,但內容比較分散,很多地方講解得也不夠詳細。

通過依賴關係確定代碼版本

在我們使用Flutter開發的時候最直接接觸的並不是Flutter Engine 而是 Flutter Framework。所以我們第一步就是要安裝我們需要使用的Flutter Framework的版本,比如我們需要使用Flutter 1.9.1 ,則本地拉取對應tag的Flutter 進行安裝,從Flutter Framework目錄下的bin/internal/engine.version文件中我們可以看到對應的Flutter Engine的版本 ,這個版本是通過Flutter Engine對應commit id(git提交的sha-1哈希值)來表示的。
我們可以先把Flutter Engine的代碼clone下來看下,clone之後 checkout到上面的commit節點,Flutter Engine根目錄下面有一個比較重要的文件DEPS , 這個文件中描述了所有的依賴,如果你需要對其中的某些依賴比如skia,boringssl做定製的話,那麼就需要基於這裏面聲明的版本來進行相應的修改。

工具鏈

在編譯之前我們還需要了解下Flutter Engine編譯所使用的一些工具

  • gclient,https://www.chromium.org/developers/how-tos/depottools/gclient ,這是chromium所使用的一個源碼庫管理的工具,可以很好的管理源碼以及對應的依賴,通過gclinet我們可以獲取所有的編譯需要的源碼和依賴

  • ninja,https://ninja-build.org/  ,編譯工具,負責最終的編譯工作

  • gn,https://gn.googlesource.com/gn ,負責生成 ninja編譯需要的build文件,特別像Flutter這種跨多種操作系統平臺跨多種CPU架構的,就需要通過gn生成很多套不同的ninja build文件。

上面的這些工具的使用場景,簡單點說就是通過gclient獲取Flutter Engine編譯所需要的編譯環境,源碼和依賴庫,然後通過gn生成ninja編譯所需要的build文件,最終通過ninja來進行編譯。

編譯

Flutter的編譯並不需要我們直接取拉Flutter Engine的源碼,都是通過gclient來進行源碼和依賴的管理,我們要做的第一步就是創建一個工作目錄,比如一個名爲engine的目錄,目錄下創建一個gclient的配置文件.gclient, 此配置文件的語法可以參見 https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/HEAD/README.gclient.md

  1. solutions = [

  2. {

  3. "managed": False,

  4. "name": "src/flutter",

  5. "url": "https://github.com/flutter/flutter.git",

  6. "custom_deps": {},

  7. "deps_file": "DEPS",

  8. "safesync_url": "",

  9. },

  10. ]

進入engine目錄執行 gclient sync,這個步驟比較耗時,第一次運行,即使100%之後還是會下載東西,我們可以通過進程管理器來查看gclient相應進程(.cipd_client)的網絡活動情況,不要提前手動kill掉進程。第一次gclient sync 執行完成了,engine/src/flutter爲Flutter Engine源碼的位置,我們需要手動切換到對應的版本分支,然後再次執行gclinet sync對此版本的依賴重新同步下,此次執行會比首次執行快很多。

接下來就是對Engine進行編譯了,這裏我們以iOS爲例,我們編譯了iOS模擬器的Flutter Engine的debug產物

  1. ./flutter/tools/gn --ios --simulator --unoptimized #生成ninja編譯的配置文件

  2. ./flutter/tools/gn --unoptimized

  3. ninja -C out/ios_debug_sim_unopt && ninja -C out/host_debug_unopt

gn在生成build文件的時候有不少參數需要我們關注,可以通過類似--ios --android來指定系統平臺,不指定則爲host平臺,比如在macOS中爲macOS,在windows中爲windows;通過--unoptimized來指定Flutter Engine是否進行debug編譯,如果指定了--unoptimized,則打出來的產物會帶debug的一些東西,比如額外的log,assert,ios則會帶上dSYM信息。所以如果你想要進行Engine源碼的調試則必須指定--unoptimized; 另外我們可以通過runtime-mode來指定flutter的運行模式,包含debug,release,profile不指定則爲debug。

編譯完成後 可以在out對應的目錄中看到對應的產物 有兩個比較關心的就是 Flutter.framework和clangx64目錄下的gensnapshot,其中Flutter.framework是Flutter Engine的編譯的結果,gen_snapshot則是擔當着dart的編譯器。

調試

首先我們可以通過IDE或者flutter命令創建一個demo工程,然後通過命令使用local engine來運行,
flutter run --local-engine-src-path=/Users/Luke/Projects/engine/src  --local-engine=iosdebugsimunopt
在flutter demo工程下通過local engine的方式運行,這裏我們使用的是ios模擬器來進行調試的,運行之後確認模擬器可以正常run起來。這個時候我們通過Xcode打開ios目錄下的iOS的工程,會發現Generated.xcconfig中多了一些FLUTTER
ENGINE,LOCAL_ENGINE的內容。

這個時候我們可以在main函數中設置斷點(swift的工程沒有main的情況下,斷點設置在@UIApplicationMain下面)。debug走到斷點的時候我們可以在console中通過br set -f FlutterViewController.mm -l 123來設置斷點。
當然還有個更簡單的方法,就是將local engine對應的生成的iOS的project拖入demo工程,就可以直接在Engine的源碼中設置斷點。這兩種方法都可以進行斷點調試。

Flutter Engine發佈流程定製

上面介紹瞭如何對官方的engine代碼進行編譯和調試,但是在真實的開發流程中我們並不能直接使用local engine。

自己的代碼庫

如果你定製的代碼庫也是放在github上那麼直接fork官方的repo進行修改便可以了,如果代碼庫需要在自己的服務器上,那麼步驟稍微多一些,首先在你自己的git服務中創建自己的repo,比如在自己搭建的gitlab中創建一個MyFlutterEngine的repo,後繼就可以進行代碼庫的準備了。

  1. git clone [email protected]:xxxx/MyFlutterEngine.git

  2. git remote add upstream https://github.com/flutter/engine.git

  3. git fetch upstream

  4. git checkout upstream/v1.9.1-hotfixes

  5. git branch v1.9.1

  6. git checkout v1.9.1

  7. git push origin v1.9.1

到這裏我們就準備好我們自己的Flutter Engine的代碼庫了,你可以在裏面進行代碼的修改。

Flutter Engine產物發佈的格式和方式

但是當我們真正用於線上產品打包發佈的時候,我們並不會使用local engine的方式來工作。Flutter Framework的目錄下有一個bin/cache的目錄(此目錄默認是gitignore的),所有的不同架構不同平臺的engine的產物都會緩存在下面,通過檢查會發現,這下面的engine產物和我們直接編譯得出的產物並不完全一致,所以第一步就需要弄清楚bin/cache下engine產物的結構。
這裏我們只關心iOS和安卓,iOS的比較簡單就三個目錄,ios,ios-profile,ios-release,分別對應debug,profile,release的flutter運行模式,每一個其實都是不同CPU架構進行了合併(通過lipo工具進行合併)主要包含armv7,arm64,這裏gen_snapshot有兩個版本,分別用於arm64和amrv7的架構進行dart的aot編譯,

由於安卓平臺中,沒法對不同CPU架構進行合併所以安卓產物的目錄比較多,

想知道詳細的邏輯可以參見flutter tool中關於cache部分的源碼 https://github.com/flutter/flutter/blob/v1.9.1-hotfixes/packages/fluttertools/lib/src/cache.dart, 這些Flutter Engine的構建產物在需要的時候從稱之爲flutter infra的鏡像中下載,在國內可以通過國內的鏡像(https://storage.flutter-io.cn/flutterinfra)進行下載,具體可以查看 https://flutter.dev/community/china 中的說明。

發佈流程

經過以上的瞭解,我們可以開始着手準備Flutter Engine定製化的發佈了。
我們可以通過一個git庫來管理我們的發佈腳本和一些配置文件,這樣可以保證別人只要clone下此庫就可以直接使用了。如果需要支持多Flutter Engine版本的打包發佈,可以一個版本對應一個打包發佈腳本,將公用的方法比如log,打包狀態這些抽離到公用的腳本中。

以下爲單個Flutter Engine版本的發佈的流程:

首先我們需要準備一個.gclient文件,實際使用時候可以將此文件做成一個模版文件,每一個Flutter Engine版本對應一個.gclient模版文件,在gclient sync之前將相應版本的模版拷貝成.gclient。
在.gclient的配置中我們可以直接指定好Flutter Engine代碼及其對應的revision,如果部分依賴的庫也需要修改,則可以在custom_deps中加入需要修改的依賴庫的git地址及其revision,指定好revision可以避免首次gclient sync之後需要額外切換Flutter Engine的代碼分支後再gclient sync的情況,也不需要手動去修改定製過的依賴的代碼庫和分支,可以減少不少工作量。

v1.9.1版本的.gclient模版文件:

  1. solutions = [

  2. {

  3. "managed": False,

  4. "name": "src/flutter",

  5. "url": "[email protected]:xxxx/[email protected]",

  6. "custom_deps": {

  7. "src/third_party/skia":"[email protected]:xxxx/[email protected]",

  8. "src/third_party/boringssl/src":"[email protected]:xxxx/[email protected]",

  9. },

  10. "deps_file": "DEPS",

  11. "safesync_url": "",

  12. },

  13. ]

gclient sync將Flutter Engine代碼以及對應的依賴都準備好了之後就是編譯的工作了,同步完成後目錄下面出現的src目錄其本身也是一個git庫,具體可查看 https://github.com/flutter/buildroot ,內容主要是Flutter Engine的編譯環境,src下面的flutter則爲Flutter Engine的代碼,下面是具體的編譯腳本,這裏以iOS爲例

  1. ./flutter/tools/gn --runtime-mode=debug --ios --simulator

  2. ninja -C out/ios_debug_sim

  3. ./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm

  4. ninja -C out/ios_debug_arm

  5. ./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm64

  6. ninja -C out/ios_debug

  7. ./flutter/tools/gn --runtime-mode=release --ios --ios-cpu=arm

  8. ninja -C out/ios_release_arm

  9. ./flutter/tools/gn --runtime-mode=release --ios --ios-cpu=arm64

  10. ninja -C out/ios_release

  11. ./flutter/tools/gn --runtime-mode=profile --ios --ios-cpu=arm

  12. ninja -C out/ios_profile_arm

  13. ./flutter/tools/gn --runtime-mode=profile --ios --ios-cpu=arm64

  14. ninja -C out/ios_profile

執行之後所有的初步的產物都會在out對應的子目錄中,現在我們再次進入到Flutter Engine 編譯的根目錄中,進行產物的組裝和發佈,在這裏我們目前採用了一種比較簡單的發佈方案,我們將自己的Flutter Framework的bin/cache目錄從gitignore中移除,發佈的時候就將產物覆蓋然後提交到我們自己的Flutter Framework的庫中,缺點就是Flutter Framework的git庫體積會比較大,而且後繼萬一官方做一些緩存策略的改變也會被影響到。

下面以iOS debug的產物爲例,release,profile都是類似的過程:

  1. # ios debug

  2. cp -rf src/out/ios_debug/Flutter.framework tmp/

  3. lipo -create -output tmp/Flutter.framework/Flutter \

  4. src/out/ios_debug/Flutter.framework/Flutter \

  5. src/out/ios_debug_arm/Flutter.framework/Flutter \

  6. src/out/ios_debug_sim/Flutter.framework/Flutter

  7. cd tmp

  8. zip -r Flutter.framework.zip Flutter.framework

  9. cd ..

  10. mkdir -p "${flutter_path}"/bin/cache/artifacts/engine/ios

  11. cp -rf tmp/Flutter.framework "${flutter_path}"/bin/cache/artifacts/engine/ios/

  12. cp -f tmp/Flutter.framework.zip "${flutter_path}"/bin/cache/artifacts/engine/ios/

  13. cp -f src/out/ios_debug/clang_x64/gen_snapshot "${flutter_path}"/bin/cache/artifacts/engine/ios/gen_snapshot_arm64

  14. cp -f src/out/ios_debug_arm/clang_x64/gen_snapshot "${flutter_path}"/bin/cache/artifacts/engine/ios/gen_snapshot_armv7

  15. rm -rf tmp/*

收益

  • 通過將此腳本,只要我們下載好發佈工具庫,直接執行腳本就可以自動開始編譯發佈了,真正做到開箱即用,免去別的配置和準備。

  • .gclient 使用模版文件,不同版本的engine對應不同的模版,打包時拷貝執行

  • .gclient 中指定好版本分支和自定義的依賴信息,源代碼和依賴sync後一步到位,避免二次切換分支

  • 打包流程腳本和公用方法分離,不同版本的打包腳本獨立分開,通用方法共享,方便維護

後續計劃

前面提到將產物放到Flutter Framework的bin/cache目錄下並不是最優的方案,通過官方的文檔可以知道通過設置FLUTTERSTORAGEBASEURL的環境變量可以更改flutter infra的鏡像的地址,所以後繼的方案就是搭建自己的flutter infra鏡像,產物編譯完成後提交到自己的鏡像網站中。

閒魚團隊是Flutter+Dart FaaS前後端一體化新技術的行業領軍者,就是現在!客戶端/服務端java/架構/前端/質量工程師面向社會招聘,base杭州阿里巴巴西溪園區,一起做有創想空間的社區產品、做深度頂級的開源項目,一起拓展技術邊界成就極致!

*投餵簡歷給小閒魚→[email protected]

開源項目、峯會直擊、關鍵洞察、深度解讀

請認準閒魚技術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章