之前App在送出測試和最終部署的過程中App打包一直是由開發人員來完成的,由于項目比較大, 再加上Android打包本身就比較慢,是以每次打包還是很耗時的。并且按照嚴格的研發流程來講,開發人員應該隻負責送出代碼,測試和部署過程中的打包都不應該由開發人員來完成,是以我就想着給測試和運維人員搭建一個可以自動打包的環境。後來在網上看到很多網友分享使用Jenkins進行Android自動打包的文章,幾經嘗試終于把環境搭建起來了。
Jenkins安裝
Jenkins作為一個開源的持續內建工具,不僅可以用來進行Android打包,也可以用來進行iOS打包、NodeJs打包、Jave服務打包等。官方位址為:jenkins.io/。Jenkins是使用Java開發的,官方提供一個war包,并且自帶servlet容器,可以獨立運作也可以放在Tomcat中運作。我們這裡使用獨立運作的方式。運作指令為:
java -jar jenkins.war
運作成功,打開浏覽器通路
http://locahost:8080
,首次運作會要求輸入管理者密碼,Jenkins在首次運作時生成的,會在控制台列印出來或者按照頁面提示的檔案路徑檢視管理者密碼。控制台輸出的密碼:
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
b7004e63acb940368e62a5dacaa2b246
This may also be found at: /Users/dmx/.jenkins/secrets/initialAdminPassword
第一次運作的頁面
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISMvwlcvJncl1SZy9mbnl2LcBnYld3LcRXYtJ3bm9CXwYTOvwFavwFM4ITMvw1dvwFMvwlM3VWaWV2Zh1Wa-YGNiVjMzYDZkdjM2UmM2EzLcFjMvwFNvwFOxAjMvw1bp5Sd0lGeu4GZj1CZs92ZtIXZzV3Lc9CX6MHc0RHaiojIsJye.jpg)
輸入密碼之後點選continue選擇要安裝的插件
由于Jenkins的插件之間存在依賴關系,并且Jenkins不會幫我們自動安裝依賴的插件,是以插件安裝過程比較容易出錯,是以我們建議自己選擇要安裝的插件,不選擇Jenkins建議安裝的插件。點選
Select plugins to install
進入下一個頁面
首先把預設選中的插件都取消掉,然後選擇我們要安裝的插件,對于Android打包來講一般需要的插件有
- Git plugin
- Gradle Plugin
- Email Extension Plugin
- description setter plugin
- build-name-setter
- user build vars plugin
- Post-Build Script Plug-in
- Branch API Plugin
- SSH plugin
- Scriptler
- Git Parameter Plug-In
- Gitlab plugin
如果插件安裝過程中由于依賴關系造成安裝失敗,可以根據錯誤資訊先安裝依賴的插件再重新安裝需要的插件。
插件安裝完成之後按照提示建立一個管理者賬号即可使用,登入之後進行首頁面。
配置環境變量
需要配置的環境變量有Android Home、JDK目錄、Gradle目錄。首先點選系統管理=>系統設定,選中
Environment variables
,然後新增Android Home環境變量
然後在系統管理=>Global Tool Configuration中配置JDK目錄和Gradle目錄
JDK和Gradle建議提前下載下傳好放到伺服器上,不要使用自動安裝,Jenkins自動下載下傳安裝非常慢
配置打包腳本
Jenkins配置完成之後需要我們來完善我們的gradle腳本讓它能夠滿足我們的打包要求,既能支援在Jenkins中打包,也能支援我們使用Android Studio進行打包。首先我們需要一個變量
IS_JENKINS
用來辨別目前是在Jenkins中打包還是在Android Studio中打包,在不同環境下打包時證書的路徑和APK生成的路徑不同,我們定義一個函數來獲驗證書路徑,然後在gradle中指定打包時使用的證書
def getMyStoreFile(){
if("true".equals(IS_JENKINS)){
return file("使用Jenkins打包時的證書路徑")
}else{
return file("使用Android Studio打包時證書路徑")
}
}
android{
signingConfigs {
release {
keyAlias '*****'
keyPassword '****'
storeFile getMyStoreFile()
storePassword '****'
}
}
buildTypes{
debug{
....
signingConfig signingConfigs.release
}
release{
....
signingConfig signingConfigs.release
}
}
....
}
然後配置不同打包環境下apk的生成路徑
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
//新名字
def newName
//輸出檔案夾
def outDirectory
//是否為Jenkins打包,輸出路徑不同
if ("true".equals(IS_JENKINS)) {
//BUILD_PATH為伺服器輸出路徑
outDirectory = BUILD_PATH
newName = "你的應用名稱" + "-" + defaultConfig.versionName + "-" + BUILD_TYPE + ".apk"
} else {
outDirectory = output.outputFile.getParent()
newName = "你的應用名稱" + "-" + defaultConfig.versionName + "-" + BUILD_TYPE + ".apk"
}
output.outputFile = new File(outDirectory, newName)
}
}
最終完成的gradle腳本為
apply plugin: 'com.android.application'
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
....
}
def getMyStoreFile(){
if("true".equals(IS_JENKINS)){
return file("使用Jenkins打包時的證書路徑")
}else{
return file("使用Android Studio打包時證書路徑")
}
}
android {
signingConfigs {
release {
keyAlias '*****'
keyPassword '****'
storeFile getMyStoreFile()
storePassword '****'
}
}
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
dexOptions {
jumboMode true
}
defaultConfig {
applicationId project.APPLICATION_ID
minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION)
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
versionName project.APP_VERSION
versionCode Integer.parseInt(project.VERSION_CODE)
ndk {
abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "mips", "mips64", "x86", "x86_64"
}
// Enabling multidex support.
multiDexEnabled true
}
buildTypes {
debug {
minifyEnabled false
shrinkResources false
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
// 移除無用的resource檔案
shrinkResources true
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
//新名字
def newName
//輸出檔案夾
def outDirectory
//是否為Jenkins打包,輸出路徑不同
if ("true".equals(IS_JENKINS)) {
//BUILD_PATH為伺服器輸出路徑
outDirectory = BUILD_PATH
newName = "你的app名字" + "-" + defaultConfig.versionName + "-" + BUILD_TYPE + ".apk"
} else {
outDirectory = output.outputFile.getParent()
newName = "你的app名字" + "-" + defaultConfig.versionName + "-" + BUILD_TYPE + ".apk"
}
output.outputFile = new File(outDirectory, newName)
}
}
flavorDimensions("channel")
productFlavors {
yingyongbao { dimension "channel" }
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/notice.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/dependencies.txt'
exclude 'META-INF/LGPL2.1'
}
}
gradle腳本中使用了在gradle.properties中定義的變量,gradle.properties内容如下
org.gradle.daemon=true
org.gradle.parallel=true
manifestmerger.enabled=true
android.useDeprecatedNdk=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4096m -XX\:MaxPermSize\=4096m -XX\:+HeapDumpOnOutOfMemoryError -Dfile.encoding\=UTF-8
ANDROID_BUILD_MIN_SDK_VERSION=14
ANDROID_BUILD_TOOLS_VERSION=25.0.1
ANDROID_BUILD_TARGET_SDK_VERSION=22
ANDROID_BUILD_SDK_VERSION=24
VERSION_CODE=176
APPLICATION_ID=你的applicationId
#jenkins中用到的變量
NODEJS_ADDRESS=app要通路的伺服器位址
API_VERSION=api版本号
APP_VERSION=app版本号
IS_JENKINS=false
BUILD_PATH=apk輸出路徑
BUILD_TYPE=Debug
建立Job
經過上面對gradle的配置我們已經做好了準備工作,現在需要在Jenkins上建立一個任務來完成對上面腳本的調用。
在Jenkins中點選建立,輸入Job名字,由于Jenkins會根據Job名字生成目錄是以建議使用英文不要使用中文,然後選擇建構一個自由風格的軟體項目,然後點選OK進入配置頁面
Job配置一共分為六個部分:General、源碼管理、建構觸發器、建構、建構後操作。
General
General中可以配置Job的基本資訊,名字、描述等資訊,我們需要關注的是關于建構的配置,如果伺服器資源比較緊張可以選擇丢棄舊的建構,然後選中參數化建構過程,這樣就能夠在打包的時候輸入一些必要的參數,比如App版本号、打包類型、伺服器位址、管道等資訊,這些輸入參數會在建構過程中替換掉gradle.properties中定義的變量。Jenkins中支援的參數類型有Boolean、Choice(下拉選擇形式的)、String、Git(需要安裝插件)。網上其他文章中提到的
Dynamic Parameter Plug-in
由于安全性問題已經不再支援。下面看一下我們需要添加參數:
BUILD_TYPE表示建構版本是Release版還是Debug版,這樣可以區分App是正式版本還是内容測試版本。JS_JENKINS表示這是從Jenkins打包的,預設值為true
PRODUCT_FLAVORS表示App的管道,我們目前隻設定了應用寶這個一個管道,如果管道包多的話這樣打包效率比較低,需要一個專門進行多管道打包的工具。APP_VERSION表示APP的版本号,這裡添加這個參數是為了能夠讓運維人員在App釋出時能夠指定釋出的版本号。
GIT_TAG用于在打包時選擇使用倉庫上哪個分支或者TAG,其中Parameter Type可以選擇Tag、Branch、Branch or Tag或者revision,這裡我們選擇Branch or Tag
NODEJS_ADDRESS表示伺服器位址,這裡可以配置上測試環境、生産環境位址,在打包時選擇要哪個背景服務。
REMARK用來描述本次打包的版本,比如這次打包使用來驗證哪個問題等等,要不然單憑版本号很難想起當時打包這個版本是用來幹什麼的。
源碼管理
我們公司使用Gitlab進行代碼管理,這裡選擇git,然後輸入倉庫位址,并在Branch Specifier綁定GIT_TAG變量,這樣GIT_TAG會自動讀取倉庫上的分支和TAG清單。
建構觸發器
建構觸發器用來配置什麼時候觸發建構,一般做法有手動觸發、定時觸發、或者送出代碼時觸發。送出代碼觸發需要在gitlab中添加webhook,我們這裡使用手動觸發是以這裡不做配置
建構環境
通過選中
Set Build Name
設定建構名稱,我們這裡設定名稱為
#${BUILD_NUMBER}_${BUILD_USER}_${APP_VERSION}_${BUILD_TYPE}
在Jenkins中${}表示引用變量,其中BUILD_NUMBER為建構編号,為Jenkins提供的變量;BUILD_USER為建構人,即目前登入使用者,需要選中
Set jenkins user build variables
;APP_VERSION為App版本号;BUILD_TYPE為建構類型。一個實際的建構名稱為
#14_admin_1.2_Release
,表示第14次建構,建構人為admin,建構的App版本為1.2Release版本
建構
選中
invoke gradle
通過調用gradle腳本進行建構,選擇在系統管理中配置的gradle的版本,這裡為gradle4.0
然後在Tasks輸入打包指令
clean assemble${PRODUCT_FLAVORS}${BUILD_TYPE}
首先執行clean,然後執行assemble進行打包。以PRODUCT_FLAVORS選擇yingyongbao,BUILD_TYPE為Release為例,則實際執行的指令為
clean assembleYingyongbaoRelease
然後選中
Pass job parameters as Gradle properties
這樣才能将我們自定義參數在打包時傳遞到gradle腳本中
這樣我們就能成功打包出apk了
實作二維碼下載下傳
為了能夠更友善的使用,我們還應該提供一個二維碼功能,這樣手機掃描之後就能下載下傳安裝。一般做法有兩個:一是選擇将打包出來的apk上傳到第三方平台;另一個是本地搭建一個服務,實作靜态檔案伺服器的功能。我們這裡選擇在本地伺服器搭建一個靜态檔案服務,同時将檔案位址生成一個二維碼展示出來。
在Excute Shell中輸入在建構完成之後執行的腳本,根據apk路徑生成一個二維碼
node /opt/jenkins_node/qr.js http://10.1.170.154:3000/apk/yundiangong-${APP_VERSION}-${BUILD_TYPE}.apk /opt/jenkins_node/apk/yundiangong-${APP_VERSION}-${BUILD_TYPE}.png
即通過node 執行/opt/jenkins_node(需要根據自己實際的目錄設定)下的qr.js檔案,同時傳遞兩個參數,第一個參數檔案apk檔案通路路徑,我在gradle打包腳本中設定apk輸出路徑為/opt/jenkins_node/apk目錄,通過靜态檔案服務的通路位址
http://10.1.170.154:3000/apk/yundiangong-${APP_VERSION}-${BUILD_TYPE}.apk
(10.1.170.154為我們公司内部伺服器,需要根據自己情況設定);第二個參數為生成二維碼的儲存路徑,同樣為/opt/jenkins_node/apk目錄,這樣靜态檔案服務既可以提供apk下載下傳,也可以提供二維碼下載下傳。
然後通過設定build description顯示二維碼功能,通過定義一個html片段,需要在系統管理=>Configure Global Security中将
Markup Formatter
選擇為
Safe HTML
![](http://10.1.170.154:3000/apk/yundiangong-${APP_VERSION}-${BUILD_TYPE}.png)<br> <a target="_blank" href="http://10.1.170.154:3000/apk/yundiangong-${APP_VERSION}-${BUILD_TYPE}.apk" target="_blank" rel="external nofollow" >點選下載下傳</a><p>${REMARK}</p>
這樣建構成功之後會展示一個二維碼,同時提供一個點選下載下傳的連結,并且還會展示該建構版本的描述資訊
我們使用nodeJs實作一個靜态檔案服務,通過nodejs啟動一個http服務,然後通過解析請求傳回對應的apk檔案。代碼如下
const http = require('http')
const path = require('path')
const url = require('url')
const fs = require('fs')
const mime = require('mime')
const port = '3000'
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.end('Hello World')
return
}
if (req.url === '/favicon.ico') return //不響應favicon請求
// 擷取url->patnname 即檔案名
let pathname = path.join(__dirname, url.parse(req.url).pathname)
pathname = decodeURIComponent(pathname) // url解碼,防止中文路徑出錯
if (fs.existsSync(pathname)) {
if (!fs.statSync(pathname).isDirectory()) {
// 以binary讀取檔案
fs.readFile(pathname, 'binary', (err, data) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' })
res.end(JSON.stringify(err))
return false
}
res.writeHead(200, {
'Content-Type': `${mime.lookup(pathname)};charset:UTF-8`
})
res.write(data, 'binary')
res.end()
})
} else {
res.statusCode = 404;
res.end('Directory Not Support')
}
} else {
res.statusCode = 404;
res.end('File Not Found')
}
});
server.listen(port);
生成二維碼的小程式也是使用nodejs實作,通過使用qr-image子產品實作生成二維碼功能
const qr=require('qr-image')
const args = process.argv.splice(2);
const filePath=args[0]//源檔案位址
const distPath=args[1]//目标檔案位址
const img=qr.image(filePath,{size:5})//生成二維碼圖檔
img.pipe(require('fs').createWriteStream(distPath));//儲存圖檔
代碼完整位址為:github.com/dumingxin/j…,首先需要安裝nodejs,然後在代碼目錄執行
npm install
,最後執行
node web.js
啟動靜态檔案服務即可。如果想背景運作可以使用pm2啟動web.js
最後打包成功之後的效果
轉載于:https://www.cnblogs.com/a00ium/p/10536516.html