2019-11-09 构建高效的Flutter App打包发布环境

构建高效的Flutter App打包发布环境

软件项目的交付是一个复杂的过程,任何原因都有可能导致交付过程失败。中小型研发团队 经常遇到的一个现象是,App 在开发测试时没有任何异常,但一到最后的打包构建交付时就问题频出。所以,每到新版本发布时,大家不仅要等候打包结果,还经常需要加班修复临时出现的问题。如果没有很好地线上应急策略,即使打包成功,交付完成后还是非常紧张。

可以看到,产品交付不仅是一个令工程师头疼的过程,还是一个高风险动作。其实,失败并不可怕,可怕的是每次失败的原因都不一样。所以,为了保障可靠交付,我们需要关注从源代码到发布的整个流程,提供一种可靠的发布支撑,确保 App 是以一种可重复的、自动化 的方式构建出来的。同时,我们还应该将打包过程提前,将构建频率加快,因为这样不仅可 以尽早发现问题,修复成本也会更低,并且能更好地保证代码变更能够顺利发布上线。

其实,这正是持续交付的思路。
所谓持续交付,指的是建立一套自动监测源代码变更,并自动实施构建、测试、打包和相关 操作的流程链机制,以保证软件可以持续、稳定地保持在随时可以发布的状态。 持续交付 可以让软件的构建、测试与发布变得更快、更频繁,更早地暴露问题和风险,降低软件开发 的成本。
你可能会觉得,大型软件工程里才会用到持续交付。其实不然,通过运用一些免费的工具和 平台,中小型项目也能够享受到开发任务自动化的便利。而 Travis CI 就是这类工具之中, 市场份额最大的一个。所以接下来,我就以 Travis CI 为例,与你分享如何为 Flutter 工程 引入持续交付的能力。

Travis CI

Travis CI 是在线托管的持续交付服务,用 Travis 来进行持续交付,不需要自己搭服务器, 在网页上点几下就好,非常方便。
Travis 和 GitHub 是一对配合默契的工作伙伴,只要你在 Travis 上绑定了 GitHub 上的项 目,后续任何代码的变更都会被 Travis 自动抓取。然后,Travis 会提供一个运行环境,执 行我们预先在配置文件中定义好的测试和构建步骤,并最终把这次变更产生的构建产物归档 到 GitHub Release 上,如下所示:

image.png

可以看到,通过 Travis 提供的持续构建交付能力,我们可以直接看到每次代码的更新的变 更结果,而不需要累积到发布前再做打包构建。这样不仅可以更早地发现错误,定位问题也 会更容易。
要想为项目提供持续交付的能力,我们首先需要在 Travis 上绑定 GitHub。我们打开Travis 官网,使用自己的 GitHub 账号授权登陆就可以了。完成授权之后,页面会跳转到 Travis。Travis 主页上会列出 GitHub 上你的所有仓库,以及你所属于的组织,如下图所示:


image.png

完成项目绑定后,接下来就是为项目增加 Travis 配置文件了。配置的方法也很简单,只要在项目的根目录下放一个名为.travis.yml 的文件就可以了。

.travis.yml 是 Travis 的配置文件,指定了 Travis 应该如何应对代码变更。代码 commit 上去之后,一旦 Travis 检测到新的变更,Travis 就会去查找这个文件,根据项目类型 (language)确定执行环节,然后按照依赖安装(install)、构建命令(script)和发布 (deploy)这三大步骤,依次执行里面的命令。一个 Travis 构建任务流程如下所示:

image.png

可以看到,为了更精细地控制持续构建过程,Travis 还为 install、script 和 deploy 提供了 对应的钩子(before_install、before_script、after_failure、after_success、 before_deploy、after_deploy、after_script),可以前置或后置地执行一些特殊操作。

如果你的项目比较简单,没有其他的第三方依赖,也不需要发布到 GitHub Release 上,只 是想看看构建会不会失败,那么你可以省略配置文件中的 install 和 deploy。

如何为项目引入 Travis?

Travis 并没有内置 Flutter 运行环境,所以我们还需要在 install 字段中,为自动化任务安装 Flutter SDK。下面的例子演示了如何为一个 Flutter 工程配置自动化测试能力。在下面的配置文件中,我们将 os 字段设置为 osx,在 install 字段中 clone 了 Flutter SDK,并将 Flutter 命令设置为环境变量。最后,我们在 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

其实,为 Flutter 工程的代码变更引入自动化测试能力相对比较容易,但考虑到 Flutter 的 跨平台特性,要想在不同平台上验证工程自动化构建的能力(即 iOS 平台构建出 ipa 包、 Android 平台构建出 apk 包)又该如何处理呢?

我们都知道 Flutter 打包构建的命令是 flutter build,所以同样的,我们只需要把构建 iOS 的命令和构建 Android 的命令放到 script 字段里就可以了。但考虑到这两条构建命令执行 时间相对较长,所以我们可以利用 Travis 提供的并发任务选项 matrix,来把 iOS 和 Android 的构建拆开,分别部署在独立的机器上执行。

下面的例子演示了如何使用 matrix 分拆构建任务。在下面的代码中,我们定义了两个并发 任务,即运行在 Linux 上的 Android 构建任务执行 flutter build apk,和运行在 OS X 上 的 iOS 构建任务 flutter build ios。

考虑到不同平台的构建任务需要提前准备运行环境,比如 Android 构建任务需要设置 JDK、安装 Android SDK 和构建工具、接受相应的开发者协议,而 iOS 构建任务则需要设 置 Xcode 版本,因此我们分别在这两个并发任务中提供对应的配置选项。
最后需要注意的是,由于这两个任务都需要依赖 Flutter 环境,所以 install 字段并不需要 拆到各自任务中进行重复设置,下面附上完整代码:

#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 的运行环境
    - 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

如何将打包好的二进制文件自动发布出来?

我们只需要为这两个构建任务增加 deploy 字段,设置 skip_cleanup 字段 告诉 Travis 在构建完成后不要清除编译产物,然后通过 file 字段把要发布的文件指定出来,最后就可以通过 GitHub 提供的 API token 上传到项目主页了。
下面的示例演示了 deploy 字段的具体用法,在下面的代码中,我们获取到了 script 字段 构建出的 app-release.apk,并通过 file 字段将其指定为待发布的文件。考虑到并不是每次构建都需要自动发布,所以我们在下面的配置中,增加了 on 选项,告诉 Travis 仅在对应 的代码更新有关联 tag 时,才自动发布一个 release 版本:

# 声明构建需要执行的命令 
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

需要注意的是,由于我们的项目是开源库,因此 GitHub 的 API token 不能明文放到配置 文件中,需要在 Travis 上配置一个 API token 的环境变量,然后把这个环境变量设置到配置文件中。

我们先打开 GitHub,点击页面右上角的个人头像进入 Settings,随后点击 Developer Settings 进入开发者设置。在开发者设置页面中,我们点击左下角的 Personal access tokens 选项,生成访问 token。token 设置页面提供了比较丰富的访问权限控制,比如仓库限制、用户限制、读写 限制等,这里我们选择只访问公共的仓库,填好 token 名称 xxx,点击确认之后, GitHub 会将 token 的内容展示在页面上。


image.png

需要注意的是,这个 token 你只会在 GitHub 上看到一次,页面关了就再也找不到了,所 以我们先把这个 token 复制下来。接下来,我们打开 Travis 主页,找到我们希望配置自动发布的项目,然后点击右上角的 More options 选择 Settings 打开项目配置页面。


image.png

在 Environment Variable 里,把刚刚复制的 token 改名为 GITHUB_TOKEN,加到环境 变量即可。


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

这个案例介绍的是 Android 的构建产物 apk 发布。而对于 iOS 而言,我们还需要对其构 建产物 app 稍作加工,让其变成更通用的 ipa 格式之后才能发布。这里我们就需要用到 deploy 的钩子 before_deploy 字段了,这个字段能够在正式发布前,执行一些特定的产 物加工工作。

下面的例子演示了如何通过 before_deploy 字段加工构建产物。由于 ipa 格式是在 app 格式之上做的一层包装,所以我们把 app 文件拷贝到 Payload 后再做压缩,就完成了发布 前的准备工作,接下来就可以在 deploy 阶段指定要发布的文件,正式进入发布环节了:

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,随后打一个 tag。等待 Travis 构建完毕后可以看到, 我们的工程已经具备自动发布构建产物的能力了。


image.png

如何为 Flutter Module 工程引入自动发布能力?

这个例子介绍的是传统的 Flutter App 工程(即纯 Flutter 工程),如果我们想为 Flutter Module 工程(即混合开发的 Flutter 工程)引入自动发布能力又该如何设置呢?
其实也并不复杂。Module 工程的 Android 构建产物是 aar,iOS 构建产物是 Framework。Android 产物的自动发布比较简单,我们直接复用 apk 的发布,把 file 文件指定为 aar 文件即可;iOS 的产物自动发布稍繁琐一些,需要将 Framework 做一些简单的 加工,将它们转换成 Pod 格式。
下面的例子演示了 Flutter Module 的 iOS 产物是如何实现自动发布的。由于 Pod 格式本 身只是在 App.Framework 和 Flutter.Framework 这两个文件的基础上做的封装,所以我 们只需要把它们拷贝到统一的目录 FlutterEngine 下,并将声明了组件定义的 FlutterEngine.podspec 文件放置在最外层,最后统一压缩成 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

将这段代码提交后可以看到,Flutter Module 工程也可以自动的发布原生组件了。

通过这些例子我们可以看到,任务配置的关键在于提炼出项目自动化运行需要的命令集合, 并确认它们的执行顺序。只要把这些命令集合按照 install、script 和 deploy 三个阶段安置 好,接下来的事情就交给 Travis 去完成,我们安心享受持续交付带来的便利就可以了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345