在一個App從開發到測試的過程當中,我有很長一段時間都是這樣作的:打包,上傳到tower,在tower上編寫本次更新說明,通知測試。通常狀況下,打包及上傳的過程大概也就2分鐘。除此以外,因爲項目代碼有做混淆,而且使用了bugly,所以在發出每一個版本以後還須要將混淆的mapping.txt傳到bugly上。當日復一日,而且有時還遇到網絡較差的狀況時,這種人工手動的工做方式就很影響工做效率及心情了。所以,自動化構建及發佈就成了必須掌握的技能了。
本篇分享的是我在Android自動化構建的一些經驗,涉及到的工具及網站以下:
- Gradle
- fir.im
- Gitlab
- gitlab-ci-multi-runnerlinux
所述內容包含:
- 使用Gradle自動構建併發布到fir
- 使用Gitlab-CI,在提交時自動化構建併發布到fir
- 在服務器配置docker版的gitlab-ci-multi-runner
- 多flavor時,在fir上同時發佈的解決方案android
我接觸fir.im的時間比較早,那時官方就已經提供了一個命令行打包並上傳的工具fir-cli。可是有兩個問題是我難以忍受的:
1. 它須要安裝,而且因爲使用ruby編寫,因此還須要ruby環境
2. 它會構建全部flavor的版本,雖然最後只上傳一個(該問題後來已經解決)c++
因而,在發現它有提供API以後,我查閱了下Gradle的文檔,本身寫了一個簡單的fir發佈插件——fir-publish。
這個插件很小很輕,沒有使用額外依賴庫,網絡請求使用的也是Gradle自己就有的http-client的API。使用方式以下:
首先在根項目的build.gradle中加入如下依賴:git
buildscript { repositories { jcenter() }
dependencies { classpath 'com.githang:fir:0.1.6' }
}
而後在app裏的build.gradle文件末尾加入如下配置:github
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以後,點擊本身的帳號就能夠獲取。docker
該插件向你的project添加如下4個任務:
- firCert 獲取上傳憑證
- firIcon 上傳圖標,依賴於firCert
- firApk 上傳APK,依賴於firCert及assembleRelease(或assembleFlavorRelease)
- firAll 上傳圖標及APK,依賴於firIcon及firApkshell
在上面的配置中,更新日誌能夠直接寫在上面,也能夠單首創建一個更新日誌的文件(推薦),每次要發佈時只須要修改這個文件上的更新日誌,而後執行如下命令便可自動構建併發布到fir:ubuntu
./gradlew firAll
注:windows用戶在前面不須要加./
這樣,咱們只須要讓測試人員關注fir上的更新動態便可,而沒必要本身去等待構建完成再上傳而後等待上傳完成再去寫更新日誌。windows
關於自動上傳mapping.txt到bugly的問題,其實bugly自己已有提供相關的Gradle插件bugly。可是它會在每次構建Release版本中都執行上傳mapping.txt,而一般咱們只是在最終打包版本給測試的時候才須要,因此我修改了一下配置:api
def isPublish = hasProperty("publish")
bugly {
appId = '你的appId'
appKey = '你的appKey'
execute = isPublish
upload = isPublish
}
在這裏新增長了一個變量 ,僅在有publish
屬性時才執行上傳的命令,這個屬性在執行的時候帶入,所以咱們打包併發布的命令將進一步演變爲以下:
./gradlew firApk -Ppublish
在上面的過程當中,其實咱們解決的最大問題是把構建——發佈——編寫版本更新日誌這三個步驟合成一步,少去了中間過程的等待,可是結果仍是咱們要在每次須要時去手動執行這一步。
CI類的服務可以讓咱們把代碼推送到服務器上時便可開始構建,使得咱們的整個構建過程達到真正的自動化,而不用人工參與。
因爲公司使用的是Gitlab,因此這裏只談Gitlab-CI相關的內容。
新版的Gitlab CI中使用的是gitlab-ci-multi-runner,關於它的安裝能夠到參考其官方文檔,這裏再也不贅述。
須要注意一點的是,在安裝以後進行註冊時,若是你是想註冊爲共享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,那麼後面的也不用看了。
上面雖然使用了gitlab-ci-multi-runner來完成自動化,可是它是在我本機上跑的。每次編譯時佔用的內存及CPU會對開發略有影響,而且還須要我在每次開機後開個終端運行一下這個runner。公司內部是有一臺Ubuntu服務器專門用於代碼及項目相目的服務的,若是把咱們的runner部署到這臺服務器上那就更好了。
公司的這臺服務器安裝了Docker,其餘的服務都是以docker形式運行的。既然這樣,我也遵照規則用docker部署上runner吧。
向公司的技術大伽問來內部服務器的管理員帳號,又翻了一遍《Docker — 從入門到實踐》的PDF,而後就開始寫Dockerfile並在本身電腦上試驗了。
在踩了ubuntu版本安裝不了JDK八、掛載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
注:後續若是有變化,將在個人github項目上更新,地址爲:https://github.com/msdx/dockerfile/blob/master/gitlab-ci-multi-runner/Dockerfile
而後執行docker build建立docker鏡像以後,運行docker時掛載上android sdk以及可讀寫的gradle緩存目錄就能夠了,其餘問題包括runner的註冊等可參見我這裏的項目說明:https://github.com/msdx/dockerfile/tree/master/gitlab-ci-multi-runner 。因爲篇幅緣由,這裏不做贅述。
我這裏把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
前面提到咱們公司的項目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