天天看點

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

之前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           

第一次運作的頁面

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

輸入密碼之後點選continue選擇要安裝的插件

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

由于Jenkins的插件之間存在依賴關系,并且Jenkins不會幫我們自動安裝依賴的插件,是以插件安裝過程比較容易出錯,是以我們建議自己選擇要安裝的插件,不選擇Jenkins建議安裝的插件。點選

Select plugins to install

進入下一個頁面

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

首先把預設選中的插件都取消掉,然後選擇我們要安裝的插件,對于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

如果插件安裝過程中由于依賴關系造成安裝失敗,可以根據錯誤資訊先安裝依賴的插件再重新安裝需要的插件。

插件安裝完成之後按照提示建立一個管理者賬号即可使用,登入之後進行首頁面。

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

配置環境變量

需要配置的環境變量有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進入配置頁面

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

Job配置一共分為六個部分:General、源碼管理、建構觸發器、建構、建構後操作。

General

General中可以配置Job的基本資訊,名字、描述等資訊,我們需要關注的是關于建構的配置,如果伺服器資源比較緊張可以選擇丢棄舊的建構,然後選中參數化建構過程,這樣就能夠在打包的時候輸入一些必要的參數,比如App版本号、打包類型、伺服器位址、管道等資訊,這些輸入參數會在建構過程中替換掉gradle.properties中定義的變量。Jenkins中支援的參數類型有Boolean、Choice(下拉選擇形式的)、String、Git(需要安裝插件)。網上其他文章中提到的

Dynamic Parameter Plug-in

由于安全性問題已經不再支援。下面看一下我們需要添加參數:

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

BUILD_TYPE表示建構版本是Release版還是Debug版,這樣可以區分App是正式版本還是内容測試版本。JS_JENKINS表示這是從Jenkins打包的,預設值為true

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

PRODUCT_FLAVORS表示App的管道,我們目前隻設定了應用寶這個一個管道,如果管道包多的話這樣打包效率比較低,需要一個專門進行多管道打包的工具。APP_VERSION表示APP的版本号,這裡添加這個參數是為了能夠讓運維人員在App釋出時能夠指定釋出的版本号。

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

GIT_TAG用于在打包時選擇使用倉庫上哪個分支或者TAG,其中Parameter Type可以選擇Tag、Branch、Branch or Tag或者revision,這裡我們選擇Branch or Tag

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

NODEJS_ADDRESS表示伺服器位址,這裡可以配置上測試環境、生産環境位址,在打包時選擇要哪個背景服務。

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

REMARK用來描述本次打包的版本,比如這次打包使用來驗證哪個問題等等,要不然單憑版本号很難想起當時打包這個版本是用來幹什麼的。

源碼管理

我們公司使用Gitlab進行代碼管理,這裡選擇git,然後輸入倉庫位址,并在Branch Specifier綁定GIT_TAG變量,這樣GIT_TAG會自動讀取倉庫上的分支和TAG清單。

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

建構觸發器

建構觸發器用來配置什麼時候觸發建構,一般做法有手動觸發、定時觸發、或者送出代碼時觸發。送出代碼觸發需要在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版本

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

建構

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

選中

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上傳到第三方平台;另一個是本地搭建一個服務,實作靜态檔案伺服器的功能。我們這裡選擇在本地伺服器搭建一個靜态檔案服務,同時将檔案位址生成一個二維碼展示出來。

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

在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

最後打包成功之後的效果

使用Jenkins進行Android自動打包,自定義版本号等資訊【轉】

轉載于:https://www.cnblogs.com/a00ium/p/10536516.html