2019-11-09 構(gòu)建高效的Flutter App打包發(fā)布環(huán)境

構(gòu)建高效的Flutter App打包發(fā)布環(huán)境

軟件項(xiàng)目的交付是一個(gè)復(fù)雜的過(guò)程,任何原因都有可能導(dǎo)致交付過(guò)程失敗。中小型研發(fā)團(tuán)隊(duì) 經(jīng)常遇到的一個(gè)現(xiàn)象是,App 在開(kāi)發(fā)測(cè)試時(shí)沒(méi)有任何異常,但一到最后的打包構(gòu)建交付時(shí)就問(wèn)題頻出。所以,每到新版本發(fā)布時(shí),大家不僅要等候打包結(jié)果,還經(jīng)常需要加班修復(fù)臨時(shí)出現(xiàn)的問(wèn)題。如果沒(méi)有很好地線上應(yīng)急策略,即使打包成功,交付完成后還是非常緊張。

可以看到,產(chǎn)品交付不僅是一個(gè)令工程師頭疼的過(guò)程,還是一個(gè)高風(fēng)險(xiǎn)動(dòng)作。其實(shí),失敗并不可怕,可怕的是每次失敗的原因都不一樣。所以,為了保障可靠交付,我們需要關(guān)注從源代碼到發(fā)布的整個(gè)流程,提供一種可靠的發(fā)布支撐,確保 App 是以一種可重復(fù)的、自動(dòng)化 的方式構(gòu)建出來(lái)的。同時(shí),我們還應(yīng)該將打包過(guò)程提前,將構(gòu)建頻率加快,因?yàn)檫@樣不僅可 以盡早發(fā)現(xiàn)問(wèn)題,修復(fù)成本也會(huì)更低,并且能更好地保證代碼變更能夠順利發(fā)布上線。

其實(shí),這正是持續(xù)交付的思路。
所謂持續(xù)交付,指的是建立一套自動(dòng)監(jiān)測(cè)源代碼變更,并自動(dòng)實(shí)施構(gòu)建、測(cè)試、打包和相關(guān) 操作的流程鏈機(jī)制,以保證軟件可以持續(xù)、穩(wěn)定地保持在隨時(shí)可以發(fā)布的狀態(tài)。 持續(xù)交付 可以讓軟件的構(gòu)建、測(cè)試與發(fā)布變得更快、更頻繁,更早地暴露問(wèn)題和風(fēng)險(xiǎn),降低軟件開(kāi)發(fā) 的成本。
你可能會(huì)覺(jué)得,大型軟件工程里才會(huì)用到持續(xù)交付。其實(shí)不然,通過(guò)運(yùn)用一些免費(fèi)的工具和 平臺(tái),中小型項(xiàng)目也能夠享受到開(kāi)發(fā)任務(wù)自動(dòng)化的便利。而 Travis CI 就是這類工具之中, 市場(chǎng)份額最大的一個(gè)。所以接下來(lái),我就以 Travis CI 為例,與你分享如何為 Flutter 工程 引入持續(xù)交付的能力。

Travis CI

Travis CI 是在線托管的持續(xù)交付服務(wù),用 Travis 來(lái)進(jìn)行持續(xù)交付,不需要自己搭服務(wù)器, 在網(wǎng)頁(yè)上點(diǎn)幾下就好,非常方便。
Travis 和 GitHub 是一對(duì)配合默契的工作伙伴,只要你在 Travis 上綁定了 GitHub 上的項(xiàng) 目,后續(xù)任何代碼的變更都會(huì)被 Travis 自動(dòng)抓取。然后,Travis 會(huì)提供一個(gè)運(yùn)行環(huán)境,執(zhí) 行我們預(yù)先在配置文件中定義好的測(cè)試和構(gòu)建步驟,并最終把這次變更產(chǎn)生的構(gòu)建產(chǎn)物歸檔 到 GitHub Release 上,如下所示:

image.png

可以看到,通過(guò) Travis 提供的持續(xù)構(gòu)建交付能力,我們可以直接看到每次代碼的更新的變 更結(jié)果,而不需要累積到發(fā)布前再做打包構(gòu)建。這樣不僅可以更早地發(fā)現(xiàn)錯(cuò)誤,定位問(wèn)題也 會(huì)更容易。
要想為項(xiàng)目提供持續(xù)交付的能力,我們首先需要在 Travis 上綁定 GitHub。我們打開(kāi)Travis 官網(wǎng),使用自己的 GitHub 賬號(hào)授權(quán)登陸就可以了。完成授權(quán)之后,頁(yè)面會(huì)跳轉(zhuǎn)到 Travis。Travis 主頁(yè)上會(huì)列出 GitHub 上你的所有倉(cāng)庫(kù),以及你所屬于的組織,如下圖所示:


image.png

完成項(xiàng)目綁定后,接下來(lái)就是為項(xiàng)目增加 Travis 配置文件了。配置的方法也很簡(jiǎn)單,只要在項(xiàng)目的根目錄下放一個(gè)名為.travis.yml 的文件就可以了。

.travis.yml 是 Travis 的配置文件,指定了 Travis 應(yīng)該如何應(yīng)對(duì)代碼變更。代碼 commit 上去之后,一旦 Travis 檢測(cè)到新的變更,Travis 就會(huì)去查找這個(gè)文件,根據(jù)項(xiàng)目類型 (language)確定執(zhí)行環(huán)節(jié),然后按照依賴安裝(install)、構(gòu)建命令(script)和發(fā)布 (deploy)這三大步驟,依次執(zhí)行里面的命令。一個(gè) Travis 構(gòu)建任務(wù)流程如下所示:

image.png

可以看到,為了更精細(xì)地控制持續(xù)構(gòu)建過(guò)程,Travis 還為 install、script 和 deploy 提供了 對(duì)應(yīng)的鉤子(before_install、before_script、after_failure、after_success、 before_deploy、after_deploy、after_script),可以前置或后置地執(zhí)行一些特殊操作。

如果你的項(xiàng)目比較簡(jiǎn)單,沒(méi)有其他的第三方依賴,也不需要發(fā)布到 GitHub Release 上,只 是想看看構(gòu)建會(huì)不會(huì)失敗,那么你可以省略配置文件中的 install 和 deploy。

如何為項(xiàng)目引入 Travis?

Travis 并沒(méi)有內(nèi)置 Flutter 運(yùn)行環(huán)境,所以我們還需要在 install 字段中,為自動(dòng)化任務(wù)安裝 Flutter SDK。下面的例子演示了如何為一個(gè) Flutter 工程配置自動(dòng)化測(cè)試能力。在下面的配置文件中,我們將 os 字段設(shè)置為 osx,在 install 字段中 clone 了 Flutter SDK,并將 Flutter 命令設(shè)置為環(huán)境變量。最后,我們?cè)?script 字段中加上 flutter test 命令,就完成了配置工作:

os:
- osx
install:
  - git clone https://github.com/flutter/flutter.git
  - export PATH="$PATH:`pwd`/flutter/bin"
script:
  - flutter doctor && flutter test

其實(shí),為 Flutter 工程的代碼變更引入自動(dòng)化測(cè)試能力相對(duì)比較容易,但考慮到 Flutter 的 跨平臺(tái)特性,要想在不同平臺(tái)上驗(yàn)證工程自動(dòng)化構(gòu)建的能力(即 iOS 平臺(tái)構(gòu)建出 ipa 包、 Android 平臺(tái)構(gòu)建出 apk 包)又該如何處理呢?

我們都知道 Flutter 打包構(gòu)建的命令是 flutter build,所以同樣的,我們只需要把構(gòu)建 iOS 的命令和構(gòu)建 Android 的命令放到 script 字段里就可以了。但考慮到這兩條構(gòu)建命令執(zhí)行 時(shí)間相對(duì)較長(zhǎng),所以我們可以利用 Travis 提供的并發(fā)任務(wù)選項(xiàng) matrix,來(lái)把 iOS 和 Android 的構(gòu)建拆開(kāi),分別部署在獨(dú)立的機(jī)器上執(zhí)行。

下面的例子演示了如何使用 matrix 分拆構(gòu)建任務(wù)。在下面的代碼中,我們定義了兩個(gè)并發(fā) 任務(wù),即運(yùn)行在 Linux 上的 Android 構(gòu)建任務(wù)執(zhí)行 flutter build apk,和運(yùn)行在 OS X 上 的 iOS 構(gòu)建任務(wù) flutter build ios。

考慮到不同平臺(tái)的構(gòu)建任務(wù)需要提前準(zhǔn)備運(yùn)行環(huán)境,比如 Android 構(gòu)建任務(wù)需要設(shè)置 JDK、安裝 Android SDK 和構(gòu)建工具、接受相應(yīng)的開(kāi)發(fā)者協(xié)議,而 iOS 構(gòu)建任務(wù)則需要設(shè) 置 Xcode 版本,因此我們分別在這兩個(gè)并發(fā)任務(wù)中提供對(duì)應(yīng)的配置選項(xiàng)。
最后需要注意的是,由于這兩個(gè)任務(wù)都需要依賴 Flutter 環(huán)境,所以 install 字段并不需要 拆到各自任務(wù)中進(jìn)行重復(fù)設(shè)置,下面附上完整代碼:

#language: dart
#script:
#  - dart main.dart

matrix:
  include:
    - os: linux
      language: android
      dist: trusty
      licenses:
        - 'android-sdk-preview-license-.+'
        - 'android-sdk-license-.+'
        - 'google-gdk-license-.+'
      # 聲明需要安裝的 Android 組件
      android:
        components:
          - tools
          - platform-tools
          - build-tools-28.0.3
          - android-28
          - sys-img-armeabi-v7a-google_apis-28
          - extra-android-m2repository
          - extra-google-m2repository
          - extra-google-android-support
      jdk: oraclejdk8
      sudo: false
      addons:
        apt:
          sources:
            - ubuntu-toolchain-r-test
          packages:
          - libstdc++6
          - fonts-droid
      # 確保 sdkmanager 是最新的
      before_script:
        - yes | sdkmanager --update
      script:
        - yes | flutter doctor --android-licenses
        - flutter doctor && flutter -v build apk
    # 聲明 iOS 的運(yùn)行環(huán)境
    - os: osx
      language: objective-c
      osx_image: xcode10.2
      before_script:
        - pod repo update
      script:
        - flutter doctor && flutter -v build ios --no-codesign
      before_deploy:
        - mkdir app && mkdir app/Payload
        - cp -r build/ios/iphoneos/Runner.app app/Payload
        - pushd app && zip -r -m app.ipa Payload  && popd
install:
  - git clone -b 'v1.9.1+hotfix.4' --depth 1 https://github.com/flutter/flutter.git
  - export PATH="$PATH:`pwd`/flutter/bin"
cache:
  directories:
    - $HOME/.pub-cache

如何將打包好的二進(jìn)制文件自動(dòng)發(fā)布出來(lái)?

我們只需要為這兩個(gè)構(gòu)建任務(wù)增加 deploy 字段,設(shè)置 skip_cleanup 字段 告訴 Travis 在構(gòu)建完成后不要清除編譯產(chǎn)物,然后通過(guò) file 字段把要發(fā)布的文件指定出來(lái),最后就可以通過(guò) GitHub 提供的 API token 上傳到項(xiàng)目主頁(yè)了。
下面的示例演示了 deploy 字段的具體用法,在下面的代碼中,我們獲取到了 script 字段 構(gòu)建出的 app-release.apk,并通過(guò) file 字段將其指定為待發(fā)布的文件??紤]到并不是每次構(gòu)建都需要自動(dòng)發(fā)布,所以我們?cè)谙旅娴呐渲弥校黾恿?on 選項(xiàng),告訴 Travis 僅在對(duì)應(yīng) 的代碼更新有關(guān)聯(lián) tag 時(shí),才自動(dòng)發(fā)布一個(gè) release 版本:

# 聲明構(gòu)建需要執(zhí)行的命令 
script:
  - yes | flutter doctor --android-licenses
  - flutter doctor && flutter -v build apk 
  # 聲明部署的策略,即上傳 github 
deploy:
  provider: releases
  api_key: xxxxx
  file:
    - build/app/outputs/apk/release/app-release.apk
  skip_cleanup: true
  on:
    tags: true

需要注意的是,由于我們的項(xiàng)目是開(kāi)源庫(kù),因此 GitHub 的 API token 不能明文放到配置 文件中,需要在 Travis 上配置一個(gè) API token 的環(huán)境變量,然后把這個(gè)環(huán)境變量設(shè)置到配置文件中。

我們先打開(kāi) GitHub,點(diǎn)擊頁(yè)面右上角的個(gè)人頭像進(jìn)入 Settings,隨后點(diǎn)擊 Developer Settings 進(jìn)入開(kāi)發(fā)者設(shè)置。在開(kāi)發(fā)者設(shè)置頁(yè)面中,我們點(diǎn)擊左下角的 Personal access tokens 選項(xiàng),生成訪問(wèn) token。token 設(shè)置頁(yè)面提供了比較豐富的訪問(wèn)權(quán)限控制,比如倉(cāng)庫(kù)限制、用戶限制、讀寫(xiě) 限制等,這里我們選擇只訪問(wèn)公共的倉(cāng)庫(kù),填好 token 名稱 xxx,點(diǎn)擊確認(rèn)之后, GitHub 會(huì)將 token 的內(nèi)容展示在頁(yè)面上。


image.png

需要注意的是,這個(gè) token 你只會(huì)在 GitHub 上看到一次,頁(yè)面關(guān)了就再也找不到了,所 以我們先把這個(gè) token 復(fù)制下來(lái)。接下來(lái),我們打開(kāi) Travis 主頁(yè),找到我們希望配置自動(dòng)發(fā)布的項(xiàng)目,然后點(diǎn)擊右上角的 More options 選擇 Settings 打開(kāi)項(xiàng)目配置頁(yè)面。


image.png

在 Environment Variable 里,把剛剛復(fù)制的 token 改名為 GITHUB_TOKEN,加到環(huán)境 變量即可。


image.png

最后,我們只要把配置文件中的 api_key 替換成 ${GITHUB_TOKEN}就可以了.

deploy:
  provider: releases
  api_key: ${GITHUB_TOKEN}
  file:
    - build/app/outputs/apk/release/app-release.apk
  skip_cleanup: true
  on:
    tags: true

這個(gè)案例介紹的是 Android 的構(gòu)建產(chǎn)物 apk 發(fā)布。而對(duì)于 iOS 而言,我們還需要對(duì)其構(gòu) 建產(chǎn)物 app 稍作加工,讓其變成更通用的 ipa 格式之后才能發(fā)布。這里我們就需要用到 deploy 的鉤子 before_deploy 字段了,這個(gè)字段能夠在正式發(fā)布前,執(zhí)行一些特定的產(chǎn) 物加工工作。

下面的例子演示了如何通過(guò) before_deploy 字段加工構(gòu)建產(chǎn)物。由于 ipa 格式是在 app 格式之上做的一層包裝,所以我們把 app 文件拷貝到 Payload 后再做壓縮,就完成了發(fā)布 前的準(zhǔn)備工作,接下來(lái)就可以在 deploy 階段指定要發(fā)布的文件,正式進(jìn)入發(fā)布環(huán)節(jié)了:

before_deploy:
  - mkdir app && mkdir app/Payload
  - cp -r build/ios/iphoneos/Runner.app app/Payload
  - pushd app && zip -r -m app.ipa Payload  && popd
# 聲明部署的策略,即上傳 apk 至 github release
deploy:
  provider: releases
  api_key: ${GITHUB_TOKEN}
  file:
    - app/app.ipa
  skip_cleanup: true
  on:
    tags: true

將更新后的配置文件提交至 GitHub,隨后打一個(gè) tag。等待 Travis 構(gòu)建完畢后可以看到, 我們的工程已經(jīng)具備自動(dòng)發(fā)布構(gòu)建產(chǎn)物的能力了。


image.png

如何為 Flutter Module 工程引入自動(dòng)發(fā)布能力?

這個(gè)例子介紹的是傳統(tǒng)的 Flutter App 工程(即純 Flutter 工程),如果我們想為 Flutter Module 工程(即混合開(kāi)發(fā)的 Flutter 工程)引入自動(dòng)發(fā)布能力又該如何設(shè)置呢?
其實(shí)也并不復(fù)雜。Module 工程的 Android 構(gòu)建產(chǎn)物是 aar,iOS 構(gòu)建產(chǎn)物是 Framework。Android 產(chǎn)物的自動(dòng)發(fā)布比較簡(jiǎn)單,我們直接復(fù)用 apk 的發(fā)布,把 file 文件指定為 aar 文件即可;iOS 的產(chǎn)物自動(dòng)發(fā)布稍繁瑣一些,需要將 Framework 做一些簡(jiǎn)單的 加工,將它們轉(zhuǎn)換成 Pod 格式。
下面的例子演示了 Flutter Module 的 iOS 產(chǎn)物是如何實(shí)現(xiàn)自動(dòng)發(fā)布的。由于 Pod 格式本 身只是在 App.Framework 和 Flutter.Framework 這兩個(gè)文件的基礎(chǔ)上做的封裝,所以我 們只需要把它們拷貝到統(tǒng)一的目錄 FlutterEngine 下,并將聲明了組件定義的 FlutterEngine.podspec 文件放置在最外層,最后統(tǒng)一壓縮成 zip 格式即可。

before_deploy:
  - mkdir .ios/Outputs && mkdir .ios/Outputs/FlutterEngine
  - cp FlutterEngine.podspec .ios/Outputs/
  - cp -r .ios/Flutter/App.framework/ .ios/Outputs/FlutterEngine/App.framework/
  - cp -r .ios/Flutter/engine/Flutter.framework/ .ios/Outputs/FlutterEngine/Flutter.framework/
  - pushd .ios/Outputs && zip -r FlutterEngine.zip  ./ && popd
deploy:
  provider: releases
  api_key: ${GITHUB_TOKEN}
  file:
    - .ios/Outputs/FlutterEngine.zip
  skip_cleanup: true
  on:
    tags: true

將這段代碼提交后可以看到,F(xiàn)lutter Module 工程也可以自動(dòng)的發(fā)布原生組件了。

通過(guò)這些例子我們可以看到,任務(wù)配置的關(guān)鍵在于提煉出項(xiàng)目自動(dòng)化運(yùn)行需要的命令集合, 并確認(rèn)它們的執(zhí)行順序。只要把這些命令集合按照 install、script 和 deploy 三個(gè)階段安置 好,接下來(lái)的事情就交給 Travis 去完成,我們安心享受持續(xù)交付帶來(lái)的便利就可以了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容