解放雙手——Android的自動化構建及發布
在一個App從開發到測試的過程中,我有很長一段時間都是這樣做的:打包,上傳到tower,在tower上編寫本次更新說明,通知測試。一般情況下,打包及上傳的過程大概也就2分鐘。除此之外,由于項目代碼有作混淆,并且使用了bugly,因此在發出每個版本之后還需要將混淆的mapping.txt傳到bugly上。當日復一日,并且有時還遇到網絡較差的情況時,這種人工手動的工作方式就很影響工作效率及心情了。因此,自動化構建及發布就成了必須掌握的技能了。
本篇分享的是我在Android自動化構建的一些經驗,涉及到的工具及網站如下:
- Gradle
- fir.im
- Gitlab
- gitlab-ci-multi-runner
所述內容包含:
- 使用Gradle自動構建并發布到fir
- 使用Gitlab-CI,在提交時自動化構建并發布到fir
- 在服務器配置docker版的gitlab-ci-multi-runner
- 多flavor時,在fir上同時發布的解決方案
Gradle及fir帶來的解放生產力
構建并上傳apk到fir
我接觸fir.im的時間比較早,那時官方就已經提供了一個命令行打包并上傳的工具 fir-cli 。但是有兩個問題是我難以忍受的:
- 它需要安裝,并且由于使用ruby編寫,所以還需要ruby環境
- 它會構建所有flavor的版本,雖然最后只上傳一個(該問題后來已經解決)
于是,在發現它有提供API之后,我查閱了下Gradle的文檔,自己寫了一個簡單的fir發布插件—— fir-publish 。
這個插件很小很輕,沒有使用額外依賴庫,網絡請求使用的也是Gradle本身就有的http-client的API。使用方式如下:
首先在根項目的build.gradle中加入以下依賴:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.githang:fir:0.1.6'
}
}
然后在app里的build.gradle文件末尾加入以下配置:
apply plugin: 'fir'
fir {
apiToken //fir.im上的API token
bundleId android.defaultConfig.applicationId
flavor "Test" (如果沒有productFlavor,可不配置此項),僅在上傳apk時需要
appName 你的應用名稱,僅在上傳apk時需要
icon 應用圖標路徑,僅在上傳圖標時需要
changeLog "更新日志" // 或者使用 file("日志文件路徑")
}
其中的API token在你登錄fir之后,點擊自己的賬號就可以獲取。
該插件向你的project添加以下4個任務:
- firCert 獲取上傳憑證
- firIcon 上傳圖標,依賴于firCert
- firApk 上傳APK,依賴于firCert及assembleRelease(或assembleFlavorRelease)
- firAll 上傳圖標及APK,依賴于firIcon及firApk
在上面的配置中,更新日志可以直接寫在上面,也可以單獨創建一個更新日志的文件(推薦),每次要發布時只需要修改這個文件上的更新日志,然后執行以下命令即可自動構建并發布到fir:
./gradlew firAll
注:windows用戶在前面不需要加 ./
這樣,我們只需要讓測試人員關注fir上的更新動態即可,而不必自己去等待構建完成再上傳然后等待上傳完成再去寫更新日志。
構建時上傳mapping.txt到bugly
關于自動上傳mapping.txt到bugly的問題,其實bugly本身已有提供相關的Gradle插件 bugly 。但是它會在每次構建Release版本中都執行上傳mapping.txt,而通常我們只是在最終打包版本給測試的時候才需要,所以我修改了一下配置:
def isPublish = hasProperty("publish")
bugly {
appId = '你的appId'
appKey = '你的appKey'
execute = isPublish
upload = isPublish
}
在這里新增加了一個變量 ,僅在有 publish 屬性時才執行上傳的命令,這個屬性在執行的時候帶入,因此我們打包并發布的命令將進一步演變為如下:
./gradlew firApk -Ppublish
Gitlab-CI帶來的進一步解放
在上面的過程中,其實我們解決的最大問題是把構建——發布——編寫版本更新日志這三個步驟合成一步,少去了中間過程的等待,但是結果還是我們要在每次需要時去手動執行這一步。
CI類的服務能夠讓我們把代碼推送到服務器上時即可開始構建,使得我們的整個構建過程達到真正的自動化,而不用人工參與。
由于公司使用的是Gitlab,所以這里只談Gitlab-CI相關的內容。
需要注意一點的是,在安裝之后進行注冊時,如果你是想注冊為共享runner(所有項目都可使用),那么第一個問題的地址應該是你們公司gitlab的地址,第二個問題的token在管理員界面的runner配置中可以看到。如果是想注冊為私有的runner,則其url與token在項目的設置中可以看到。
接下來,只需要在我們的項目的根目錄中添加一個 .gitlab-ci.yml 文件,并在其中進行CI配置,然后提交并推送到我們的gitlab上即可。
還是以我這里的公司項目為例。項目采用git-flow流程進行開發,在要發布時會創建release分支,因此需要發布到fir上給測試人員的是release分支及tag上的代碼,其他分支的代碼我們只需要進行構建測試就可以了。在我們公司的項目中,有開發環境 、測試環境及生產環境,分別對應三個productFlavor:Develop, Test,Official,它們之間只有API的地址不同。因此,構建測試使用其中一個環境的就可以了。
所以腳本如下:
before_script:
- chmod +x ./gradlew
compileTest:
script: "./gradlew clean aDevelopDebug"
except:
- /^release.*$/
- tags
publishToFir:
script: "./gradlew clean firApk -Ppublish"
type: deploy
only:
- /^release.*$/
- tags
在這里,我定義了兩個ci任務,分別是 compileTest 以及 publishToFir 。 script 表示該任務所執行的命令。 except 表示不對哪些分支進行構建。使用git-flow流程時,將發布的分支都是以 release/xxx 來命名,所以這里用正則來表示。 only 表示僅對哪些分支執行這個構建任務。 type 表示任務的類型。
將配置提交,然后推送到Gitlab上,就能夠觸發CI去執行我們所定義的構建任務了。如果你成功了配置了Gitlab上的郵箱發送服務,那么我們就可以不用主動去關心這個結果,因為如果構建失敗了,Gitlab將會向我們發送郵件通知。
構建失敗時的郵件通知
如果你不想使用docker來運行runner,可跳過下面這一節。
如果你也不需要同時在fir上發布不同flavor的APK,那么后面的也不用看了。
更高級的Docker版的CI Runner
上面雖然使用了gitlab-ci-multi-runner來完成自動化,但是它是在我本機上跑的。每次編譯時占用的內存及CPU會對開發略有影響,并且還需要我在每次開機后開個終端運行一下這個runner。公司內部是有一臺Ubuntu服務器專門用于代碼及項目相目的服務的,如果把我們的runner部署到這臺服務器上那就更好了。
公司的這臺服務器安裝了Docker,其他的服務都是以docker形式運行的。既然這樣,我也遵守規則用docker部署上runner吧。
在踩了ubuntu版本安裝不了JDK8、掛載Android SDK目錄、沒有32位動態庫導致Android SDK執行不了,以及中文亂碼等坑之后,目前我的Dockerfile如下:
FROM ubuntu:15.04
MAINTAINER HuangHaohang <msdx.android@qq.com>
ENV ANDROID_HOME /android-sdk
RUN apt update && apt install -y openjdk-8-jdk curl
#如果遇到android-sdk里的命令無法執行,則需要安裝32位的動態鏈接庫。
RUN apt install -y libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5 lib32z1
RUN curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | bash
RUN apt-get install -y gitlab-ci-multi-runner
# Ensure UTF-8 locale
#COPY locale /etc/default/locale
RUN locale-gen zh_CN.UTF-8 && \
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales
RUN locale-gen zh_CN.UTF-8
ENV LANG zh_CN.UTF-8
ENV LANGUAGE zh_CN:zh
ENV LC_ALL zh_CN.UTF-8
然后執行docker build創建docker鏡像之后,運行docker時掛載上android sdk以及可讀寫的gradle緩存目錄就可以了。
我這里把android-sdk打包并通過ssh上傳到了服務器,然后解壓在了公司的用戶目錄的android-sdk下,并在該目錄創建了一個文件夾GradleUserHome,用于放Gradle的緩存,最終啟動這個docker的腳本如下:
#!/bin/bash
sudo docker run -d \
--name gitlab-ci-multi-runner \
--restart always \
-v /home/irain/android-sdk:/android-sdk:ro \
-v /home/irain/GradleUserHome:/root/.gradle:rw \
irain/gitlab-runner:registered \
gitlab-ci-multi-runner --debug run
fir上的小技巧——多flavor的發布方式
前面提到我們公司的項目API地址是有分多個環境的(開發環境、測試環境以及生產環境)。本來我只需要打包測試環境的給測試人員用,生產環境在最終要發布的時候再自己打包。但是在這次新版本的開發中,服務端的人員也希望我能夠打包開發環境的Apk給他,這樣有時候他也可以自測一下。由于項目正在開發中,版本變化較快,所以我也想到通過自動構建發布到fir上,再由他自己去下載,這樣就可保證他可以獲得最新開發的版本。
然而,相當沮喪的一點是,fir上并不支持同一個應用多環境的發布,雖然這個需求在一年以前就有其他人提出。我問了客服,客服的建議是換一個bundleId(applicationId),當然這是不可能的,因為我們的應用使用到了高德地圖、微信分享及各種支付等許多和applicationId關聯的SDK,不可能重新部署一套。最后查看其他人分享的一個實現技巧。
首先,你需要在fir上注冊一個號(當然你也可以請你同事幫忙),然后把你的應用上傳上去,再進入應用的權限控制,把你的大號邀請進來,這樣你的大號上就有兩個這樣的應用了,并且可以對它上傳新版本來更新。當然,如果你使用API來上傳,則不需要邀請,只需要填不同的API Token即可。所以最終,我的app的 build.gradle 中關于fir發布的配置如下:
def envFlavor = hasProperty("flavor") ? getProperty("flavor") : "Test"
if (envFlavor == "Develop") {
fir {
apiToken "小號的api token"
bundleId android.defaultConfig.applicationId
flavor envFlavor
appName "XXX-開發版"
changeLog "git show -s --format=%B HEAD".execute().text
}
} else {
fir {
apiToken "大號的api token"
bundleId android.defaultConfig.applicationId
flavor envFlavor
appName "XXX-測試版"
changeLog file("./changeLog.txt")
}
}
其中,flavor是通過定義的envFlavor來設置,而envFlavor根據執行的時候傳入的flavor屬性的值來設置。對應的 .gitlab-ci.yml 也修改如下:
before_script:
- chmod +x ./gradlew
compileTest:
script: "./gradlew clean firApk -Ppublish -Pflavor=Develop"
except:
- /^release.*$/
- tags
publishToFir:
script: "./gradlew clean firApk -Ppublish"
type: deploy
only:
- /^release.*$/
- tags
來自:http://www.jianshu.com/p/d8ccdf08ad99