Android的持續(xù)化集成及多版本打包

文檔概述

關(guān)于Android開發(fā),除了技術(shù)方面需要掌握,還有發(fā)布流程需要了解。本文檔就包括以上兩個方面,主要介紹:

  • 使用配置文件配置不同功能的apk
  • 使用gradle為Android構(gòu)建簽名包
  • Jenkins集成Android自動化打包
  • 使用gradle為Android生成不同配置的簽名包

一、場景描述

在項目開發(fā)中,我們可能有多個功能有區(qū)別但是整體框架一致的工程。

情景:我們有一app,基礎(chǔ)功能包括a,b,c,擴(kuò)張功能包括d,e,f,其中客戶1需要擴(kuò)展功能d,f,客戶2需要e,f。

二、配置文件簡介

鑒于以上場景,開發(fā)app過程應(yīng)該怎么做?如果客戶1創(chuàng)建一份工程代碼,客戶2創(chuàng)建一份工程代碼效率就太低了。因此我們需要另辟思路,采用配置文件的方式進(jìn)行開發(fā)。

配置文件分類

關(guān)于配置文件目前有兩種方式:

  1. apk文件寫注釋
  2. apk源碼屬性文件

配置方式的區(qū)別

方式1主要適用于輕量的注釋,例如書寫渠道名等一些簡單的注釋,不用修改源碼。方式2就比較適合當(dāng)前的場景,但是缺點在于配置文件在源碼部分,所以修改配置文件就必須修改源碼。

配置文件的使用

關(guān)于方式1的使用,可以: 美團(tuán)批量打包

基本原理就是在apk文件生成之后,修改apk文件的部分字段,而不影響apk本身簽名驗證,在源碼中根據(jù)apk安裝的位置獲取安裝包文件再讀取其中的字段。

方式2就是使用屬性文件。實現(xiàn)方式就是使用java.util.Properties類進(jìn)行文件加載。
預(yù)先在Android工程的main文件夾下創(chuàng)建assets文件夾,里面存放配置文件,客戶1的配置文件命名為:a.properties,b.properties,里面的內(nèi)容如下:

fun_a=true
fun_b=true
fun_c=true
fun_d=true
fun_e=false
fun_f=true

讀取配置屬性代碼:

public String funX(Context context, String x){
    Properties props = new Properties();
    try {
        props.load(context.getAssets().open("a.properties"));
    //  props.load(context.getAssets().open("b.properties"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    return props.getProperties("fun_" + x);
}

在源碼中根據(jù)對應(yīng)函數(shù)的配置信息決定是否執(zhí)行對應(yīng)操作。

對于客戶1和客戶2的不同需求可以選擇性的加載配置文件進(jìn)行打包操作。

以上就是不同需求的具體操作。但是我們可以發(fā)現(xiàn),這個操作效率太低,每次打包都要修改源碼。下面介紹自動化過程。

三、 自動化之gradle打包

配置操作

為了之后可以執(zhí)行自動化操作,簽名必須也要能進(jìn)行自動化執(zhí)行。

首先放置簽名文件到:項目的根目錄。

modulebuild.gradle文件中添加以下內(nèi)容:

apply plugin: 'com.android.application'

def keystorePSW = ''
def keystoreAlias = ''
def keystoreAliasPSW = ''
// default keystore file, PLZ config file path in local.properties
Properties properties = new Properties()
// local.properties file in the root director
properties.load(project.rootProject.file('gradle.properties').newDataInputStream())

keystorePSW = properties.getProperty("keystore.password")
keystoreAlias = properties.getProperty("keystore.alias")
keystoreAliasPSW = properties.getProperty("keystore.alias_password")

android {
    ...
    signingConfigs {
        release {
            keyAlias keystoreAlias
            keyPassword keystoreAliasPSW
            storePassword keystorePSW
            storeFile file('../xxx.jks')            // 簽名文件的位置
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
}
dependencies {
    ...
}

為了能夠在Jenkins環(huán)境也能使用自動打包操作,在項目的根目錄gradle.properties文件中添加如下內(nèi)容:

keystore.password = xxx             // 密鑰的密碼
keystore.alias = xxx                // 密鑰的別稱
keystore.alias_password = xxx       // 別稱的密碼

隨后在項目的根目錄下使用gradle即可進(jìn)行打包。

執(zhí)行命令

打包命令:

gradlew clean               // 清除build文件夾
// 二選一
gradlew build               // 檢查依賴并編譯打包,會生成debug和release兩個包
gradlew assesmRelease       // 生成release包

執(zhí)行以上命令之后,會在項目的app/build/output/apk/*.apk生成對應(yīng)的apk。

注:Windows操作系統(tǒng)使用gradlew,Linux系統(tǒng)使用./gradlew。

以上,使用gradle自動打包就以完成。下面就是集成Jenkins持續(xù)集成環(huán)境了。

四、Jenkins集成

創(chuàng)建項目

關(guān)于構(gòu)建介紹可以參考:Jenkins+Gradle實現(xiàn)android開發(fā)持續(xù)集成、打包

配置信息沒什么特別的。主要是輸出文件路徑:直接填寫app/build/output/app/*.apk即可。

輸出文件路徑

可能遇到的問題

  • 由于Jenkins自動構(gòu)建,所以對語法要求比較嚴(yán)格。如果出現(xiàn)以下錯誤:

      FAILURE: Build failed with an exception.
      * What went wrong:
      Execution failed for task ':app:lint'.
      > Lint found errors in the project; aborting build.
       
        Fix the issues identified by lint, or add the following to your build script to proceed with errors:
        ...
        android {
            lintOptions {
                abortOnError false
            }
        }
        ...
      
      * Try:
      Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
    

    這個是因為代碼不符合規(guī)范,lint檢查時報錯,因此中斷了整個編譯過程。

    只要在當(dāng)前app的app/build.gradle文件內(nèi)增加如下代碼:

      android{
          ...
          lintOptions{
              abortOnError false
          }
          ...
      }
    
  • 安裝Jenkins配置局域網(wǎng)訪問

    在mac安裝Jenkins之后,局域網(wǎng)無法訪問,原因在于使用brew安裝jenkins會避免很多其他安裝方式產(chǎn)生的用戶權(quán)限問題,但是會將httpListenAddress默認(rèn)設(shè)置為127.0.0.1,這樣我們雖然可以在本地用localhost:8080訪問,但是本機(jī)和局域網(wǎng)均無法用ip訪問。解決辦法為修改兩個路徑下的plist配置。

      ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist
      /usr/local/opt/jenkins/homebrew.mxcl.jenkins.plist
    

    修改之后重啟Jenkins即可訪問。

      brew services start jenkins         // 開啟服務(wù)
      brew services stop jenkins          // 關(guān)閉服務(wù)
      brew services restart jenkins       // 重啟服務(wù)
    
  • 更多錯誤查看:

    使用Jenkins持續(xù)集成Android項目遇到的坑

通過以上操作,Android項目就可以使用Jenkins自動集成了。

但是我們第一部分的場景問題還是沒有解決,如何自動化區(qū)分客戶的版本呢?

五、自動化版本區(qū)分

Android官網(wǎng)介紹了 構(gòu)建變體

通過配置不同的 productFlavors我們可以獲取不同版本的apk。

因此第一部分的需求通過以下操作實現(xiàn)。

更新buidl.gradle文件

android {
    ...
    buildTypes {
        ...
    }

    productFlavors {
        fun_a {
            buildConfigField "String", "CONF_NAME", "\"a.properties\""
        }
        fun_b {
            buildConfigField "String", "CONF_NAME", "\"b.properties\""
        }
    }
}

修改讀取配置文件代碼

配置屬性文件不變,讀取的代碼進(jìn)行如下修改:

public String funX(Context contex, tString x){
    Properties props = new Properties();
    try {
        props.load(context.getAssets().open(BuildConfig.CONF_NAME));
    } catch (IOException e) {   
        e.printStackTrace();
    }
    return props.getProperties("fun_" + x);
}

之后使用打包命令就會生成兩個apk安裝包,一個是客戶1定制的功能,一個是客戶2定制的功能。

輸出路徑不變,生成包名:

  • app-fun_a-releas.apk
  • app-fun_b-releas.apk

這樣,Jenkins一次編譯也就可以獲取到正確的版本。

修改輸出文件的包名

以上操作之后已經(jīng)可以正確獲取目標(biāo)apk,但是并不直觀。如果可以在輸出文件名中添加輸出版本號和打包時間就完美了。

在項目的build.gradle文件中,最后節(jié)點添加:

def releaseTime() {
    return new Date().format("yyyyMMdd-HHmm", TimeZone.getTimeZone("GMT+08:00"))
}
android{
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (variant.buildType.name.equals('release')) {
                def fileName = outputFile.name.replace("app-","").replace("release", "v${defaultConfig.versionName}-${releaseTime()}")
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }
}

修改之后輸出文件名:

fun_a-v2.0.5-20171115-1655.apk
fun_b-v2.0.5-20171115-1655.apk

以上。

六、小結(jié)

通過使用gradle+Jenkins,可以讓程序員從繁復(fù)的打包任務(wù)中解放出來,更多時間去做核心開發(fā)的相關(guān)業(yè)務(wù)。

gradle的功能強(qiáng)大到我沒法想象,好好學(xué)習(xí),好好鉆研。

技術(shù),可以解放你。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容