1. 程式人生 > >android gradle自動構建大總結

android gradle自動構建大總結

一、自動構建背景

   日常出版本和線上出版本時,需要手動修改一些配置,包括key配置、線上/測試環境配置、版本號增加等,過程繁瑣。所以對構建指令碼進行改進,達到自動構建目的。

   PS:build.gradle 完整指令碼在文章末尾

 

二、自動打包說明

1. 打測試包

   命令:gradle  assembleTest

2. 打線上包

   命令:gradle  assembleRelease

3. 根據需要修改

(1)版本號,前三個欄位在build.gradle修改

  版本號的第四個欄位初始值在conf.properties檔案修改:VERSION_NAME欄位

  該欄位值每打包一次,自增1

(2)輸出目錄設定

    在conf.properties檔案修改: OUT_APK_PATH欄位

4. debug時如果不希望版本號自增,有兩種方式

 (1)在strings.xml中,把下面的app_version_value直接改為版本號,如:1.1.1.2

<string name="app_version">app_version_value</string>

(2)在build.gradle指令碼中,getVersionName函式裡,在if判斷條件中,把兩個debug去掉

if(Consts.buildTypeOffline in runTasks || Consts.buildTypeDebug in runTasks || Consts.buildTypeRunDebug in runTasks) { //打測試包時才自增版本號, 線上包的版本號手動改
            targetVerName += "." + (++verName);  //版本號自增1
            versionProps[Consts.verNamePropKey] = verName.toString();
            versionProps.store(versionPropsFile.newWriter(), null) //增1後的版本號寫入properties檔案
        }

三、指令碼說明

1. key配置

老的方式:在Manifest檔案中,直接寫key值,如下:

<meta-data
          android:name="YZ_APP_ID"
          android:value="XXXXXXXXXXXXXX"/>

新的方式:通過變數的形勢定義在Manifest中,build.gradle中根據不同的buildType,設定不同的值,如下,Manifest中定義${YZ_APP_ID}

<meta-data
            android:name="YZ_APP_ID"
            android:value="${YZ_APP_ID}" />


在build.gradle中配置如下,打包時就會根據當前的buildType使用不同的值,比如打線上包 assembleRelease,則使用1處的值,打測試包assembleOffline,則使用2處的值

    buildTypes {
        release { //線上版本配置
   //下面的值根據自己專案需要進行修改
            manifestPlaceholders = [YZ_APP_ID:"xxxxxxxxx", YZ_APP_SECRET:"xxxxxxxxxxxxxxxx", RONG_CLOUD_APP_KEY:"xxxxxxxxxx"]
            minifyEnabled true
            zipAlignEnabled true  //開啟Zipalign優化
            shrinkResources true  //移除無用的resource檔案,此項只有在開啟混淆時才生效
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
            signingConfig signingConfigs.release
        }
        offline { //測試版本配置
            manifestPlaceholders = [YZ_APP_ID:"xxxxxxxxxxxxxx", YZ_APP_SECRET:"xxxxxxxxxxxxxxx", RONG_CLOUD_APP_KEY:"xxxxxxxxxxxxx"]
            minifyEnabled true
            zipAlignEnabled true  //開啟Zipalign優化
            shrinkResources true  //移除無用的resource檔案,此項只有在開啟混淆時才生效
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
            signingConfig signingConfigs.release
        }

        debug {//開發版本配置
            manifestPlaceholders = [YZ_APP_ID:"xxxxxxxxxxxxxx", YZ_APP_SECRET:"xxxxxxxxxxxxxxxxx", RONG_CLOUD_APP_KEY:"xxxxxxxxxxxxxxxxx"]
            signingConfig signingConfigs.release
        }
    }

2. 線上/測試環境配置

環境在檔案current_use.properties中配置,編譯之前,需要把該檔案內容替換為線上/測試的配置內容,在build.gradle用指令碼實現該步驟,如下,這個比較簡單,進行檔案內容的拷貝:

我們的專案情況是這樣的:線上/測試的環境配置,包括url等, 分別放test.properties在兩個配置檔案中寫好:online.properties、test.properties,打包時,如果打線上包,則把online.properties檔案內容複製到current_use.properties中,打測試包,則把test.properties檔案內容複製到current_use.properties中,程式碼從current_use.properties中檔案中讀取;

/**
 * current_use.properties內容設定,根據線上、線下區分
 */
def copyCurprop() {
    def curPropFile = file(Consts.curUsePropFp);
    def varPropFp;

    def runTasks = gradle.startParameter.taskNames;
    if(Consts.buildTypeRelease in runTasks) { //線上
        varPropFp = Consts.onlinePropFp;
    }else { //測試
        varPropFp = Consts.offlinePropFp;
    }
    println "copyCurprop :" + varPropFp;
    def varPropFile = file(varPropFp);
    String varContent = varPropFile.getText(Consts.UTF8);
    curPropFile.write(varContent, Consts.UTF8);
}

3. 版本號自增

  我們的專案版本號規則有點複雜,如果你自己的專案沒這麼複雜,那麼本文章你參考一下就可以了。

   目前客戶端測試包版本號為4個欄位,最後一個欄位每打一個版本,版本號增1;而線上版本版本號為前3個欄位;用指令碼實現測試包版本號自增1。

(1)在build.gradle檔案中,手動設定好版本號的前3位(前3位版本號主觀修改),如:1.1.1;

(2)計算出當前自增後的版本號

/** 版本號前3段: 根據需要修改 */
    def verNamePrefix = "1.1.1"
    /** 計算出當前版本號,自增 */
    def currentVersionName = getVersionName(verNamePrefix)

         計算當前版本號指令碼如下,如果是打線上包,則版本號為手動設定的前3個欄位,如果是測試包,則從配置檔案中讀取出第上一次的版本號(第四個欄位),版本號加1,與前3個欄位合併,組成新的版本號;

/**
 * 版本號自增計算,測試環境版本號為4個段,線上環境版本號為3個段
 * @param verNamePrefix : 版本號字首
 * @return
 */
def getVersionName(verNamePrefix) {
    def targetVerName = verNamePrefix;

    def versionPropsFile = file(Consts.confPropFp);
    if(versionPropsFile.canRead()) {
        println 'conf.properties can read'
        def Properties versionProps = new Properties()
        versionProps.load(new FileInputStream(versionPropsFile))
        def verName = versionProps[Consts.verNamePropKey].toInteger()
        def runTasks = gradle.startParameter.taskNames
        println 'in getVersionName , runTasks: ' + runTasks;
        if(Consts.buildTypeOffline in runTasks || Consts.buildTypeDebug in runTasks || Consts.buildTypeRunDebug in runTasks) { //打測試包時才自增版本號, 線上包的版本號手動改
            targetVerName += "." + (++verName);  //版本號自增1
            versionProps[Consts.verNamePropKey] = verName.toString();
            versionProps.store(versionPropsFile.newWriter(), null) //增1後的版本號寫入properties檔案
        }
    }
    println "targetVerName: " + targetVerName;
    return targetVerName;
}

(3)編譯過程中,對產生的中間檔案values.xml的版本號進行修改,指令碼如下:

/**
     * 修改版本號, 修改gradle生成的中間檔案values.xms
     */
    def replaceVerName = { variant, fromString, toString ->
        File valuesFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values/values.xml");
        println "in replaceVerName , toString: " + toString;
        if (valuesFile.canRead()) {
            println 'in replaceVerName, values.mxl can read'
            String content = valuesFile.getText(Consts.UTF8);
            content = content.replaceAll(fromString, toString);
            valuesFile.write(content, Consts.UTF8);
        }else {
            println 'in replaceVerName, values.mxl can not read'
        }
    }

android.applicationVariants.all { variant ->
        // apk輸出路徑,注意,從AS執行,預設使用debug包路徑
        def runTasks = gradle.startParameter.taskNames
        if(Consts.buildTypeRelease in runTasks || Consts.buildTypeOffline in runTasks) {
            variant.outputs.each{ output ->
                output.outputFile = new File(getOutApkPath(variant, currentVersionName));
            }
        }

        // 刪除gradel生成的中間檔案values.xml,確保執行mergeResources這個tasks; PS:debug時如果無需合併資源,則不會走mergeResources,所以通過刪除values.xml,確保執行
        if(Consts.buildTypeRunDebug in runTasks || Consts.buildTypeDebug in runTasks) {
            deleteValueFile(variant);
        }

        // 版本號自增,在mergeResources task結束後執行
        variant.mergeResources.doLast {
            println "mergeResources.doLast, dir name : " + variant.dirName;
            replaceVerName(variant, Consts.appVerNameValue, currentVersionName);
        }

    }

 PS: 這裡不直接修改strings.xml,因為如果本次打包修改了strings.xml,那麼下次打包時strings.xml裡無法通過匹配替換版本號;

     debug執行時,如果資原始檔沒任何修改且之前有編譯過了,則不會執行mergeResource這個task,所以debug打包時會先刪除values.xml檔案,確保執行mergeResource這個task,達到版本號自增的目的

4.  apk輸出目錄配置

     每次打包完後,需要把apk檔案拷貝到指定目錄,並按規則給apk命名: kydd_線上/測試環境_versionName.apk。目錄在配置檔案(conf.properties)中設定好,如下:

  編譯前,設定好輸出路徑:

android.applicationVariants.all { variant ->
        // apk輸出路徑,注意,從AS執行,預設使用debug包路徑
        def runTasks = gradle.startParameter.taskNames
        if(Consts.buildTypeRelease in runTasks || Consts.buildTypeOffline in runTasks) {
            variant.outputs.each{ output ->
                output.outputFile = new File(getOutApkPath(variant, currentVersionName));
            }
        }

   輸出路徑拼接:

/**
 * 輸出apk檔案完整路徑,apk命名方式:根據專案需要自己定
 * @param variant
 * @param versionName
 * @return
 */
def getOutApkPath(variant, versionName) {
    def outPath = "./"
    def confPropsFile = file(Consts.confPropFp);
    if(confPropsFile.canRead()) {
        def Properties confProps = new Properties();
        confProps.load(new FileInputStream(confPropsFile));
        outPath = confProps[Consts.outApkPathPropKey];
    }
    def String finalOutPath = outPath + "XXXX_v" + versionName + ".apk";  //apk名稱自己定義
    println "最終輸出路徑:" + finalOutPath
    return finalOutPath;
}


主專案的build.gradle 完整內容:

apply plugin: 'com.android.application';

android {
    /** 版本號前3段: 根據需要修改 */
    def verNamePrefix = "1.1.1"
    /** 計算出當前版本號,自增 */
    def currentVersionName = getVersionName(verNamePrefix)
    /** 根據buildType,拷貝當前的環境檔案:線上 或者 測試 */
    copyCurprop();

    signingConfigs {
        release {
            keyAlias 'xxx'
            keyPassword 'xxxxx'
            storeFile file('xxxx.keystore')
            storePassword 'xxxx'
            v2SigningEnabled false
        }
    }
    compileSdkVersion 24
    buildToolsVersion '25.0.1'
    aaptOptions.cruncherEnabled = false
    aaptOptions.useNewCruncher = false
    lintOptions {
        abortOnError false
    }
    //android6.0 沒有httpclient了,如用android23編譯,需要加上該行,android23以下的不需要加這行
    useLibrary 'org.apache.http.legacy'
    defaultConfig {
        applicationId "com.wtyt.yzone"
        minSdkVersion 13
        targetSdkVersion 23  //只能用23,否則融雲SDK在android7上無法連線;如果要使用大於23,則需要加入 sqlite.so等包
        versionName currentVersionName

        //支援多個dex檔案的編譯
        multiDexEnabled true

        jackOptions {
            enabled false
        }

        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_7
            targetCompatibility JavaVersion.VERSION_1_7
        }
    }
    buildTypes {
        release { //線上版本配置
            manifestPlaceholders = [YZ_APP_ID:"xxxx", YZ_APP_SECRET:"xxxxx", RONG_CLOUD_APP_KEY:"xxxxxx"]
            minifyEnabled true
            zipAlignEnabled true  //開啟Zipalign優化
            shrinkResources true  //移除無用的resource檔案,此項只有在開啟混淆時才生效
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
            signingConfig signingConfigs.release
        }
        offline { //測試版本配置
            manifestPlaceholders = [YZ_APP_ID:"xxxxxxx", YZ_APP_SECRET:"xxxxxxxxxxxxx", RONG_CLOUD_APP_KEY:"xxxxxxxxxxxx"]
            minifyEnabled true
            zipAlignEnabled true  //開啟Zipalign優化
            shrinkResources true  //移除無用的resource檔案,此項只有在開啟混淆時才生效
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
            signingConfig signingConfigs.release
        }

        debug {//開發版本配置
            manifestPlaceholders = [YZ_APP_ID:"xxxxxxxxxxxxxx", YZ_APP_SECRET:"xxxxxxxxxxxxxxxxx", RONG_CLOUD_APP_KEY:"xxxxxxxxxxxxxx"]
            signingConfig signingConfigs.release
        }
    }

    /**
     * 修改版本號, 修改gradle生成的中間檔案values.xms
     */
    def replaceVerName = { variant, fromString, toString ->
        File valuesFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values/values.xml");
        println "in replaceVerName , toString: " + toString;
        if (valuesFile.canRead()) {
            println 'in replaceVerName, values.mxl can read'
            String content = valuesFile.getText(Consts.UTF8);
            content = content.replaceAll(fromString, toString);
            valuesFile.write(content, Consts.UTF8);
        }else {
            println 'in replaceVerName, values.mxl can not read'
        }
    }

    /** 刪除中間生成檔案 values.xml */
    def deleteValueFile = {variant ->
        File valuesFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values/values.xml");
        try {
            println '刪除檔案路徑:' + valuesFile.absolutePath;
            valuesFile.delete();
        }catch (Exception e) {
            println '刪除檔案異常:' + e.getMessage();
        }
    }

    android.applicationVariants.all { variant ->
        // apk輸出路徑,注意,從AS執行,預設使用debug包路徑
        def runTasks = gradle.startParameter.taskNames
        if(Consts.buildTypeRelease in runTasks || Consts.buildTypeOffline in runTasks) {
            variant.outputs.each{ output ->
                output.outputFile = new File(getOutApkPath(variant, currentVersionName));
            }
        }

        // 刪除gradel生成的中間檔案values.xml,確保執行mergeResources這個tasks; PS:debug時如果無需合併資源,則不會走mergeResources,所以通過刪除values.xml,確保執行
        if(Consts.buildTypeRunDebug in runTasks || Consts.buildTypeDebug in runTasks) {
            deleteValueFile(variant);
        }

        // 版本號自增,在mergeResources task結束後執行
        variant.mergeResources.doLast {
            println "mergeResources.doLast, dir name : " + variant.dirName;
            replaceVerName(variant, Consts.appVerNameValue, currentVersionName);
        }

    }

    /*configurations {
        compile.exclude group: "me.imid.swipebacklayout.lib.app", module: "swipeBackLib"
    }*/

}

/**
 * current_use.properties內容設定,根據線上、線下區分
 */
def copyCurprop() {
    def curPropFile = file(Consts.curUsePropFp);
    def varPropFp;

    def runTasks = gradle.startParameter.taskNames;
    if(Consts.buildTypeRelease in runTasks) { //線上
        varPropFp = Consts.onlinePropFp;
    }else { //測試
        varPropFp = Consts.offlinePropFp;
    }
    println "copyCurprop :" + varPropFp;
    def varPropFile = file(varPropFp);
    String varContent = varPropFile.getText(Consts.UTF8);
    curPropFile.write(varContent, Consts.UTF8);
}


/**
 * 版本號自增計算,測試環境版本號為4個段,線上環境版本號為3個段
 * @param verNamePrefix : 版本號字首
 * @return
 */
def getVersionName(verNamePrefix) {
    def targetVerName = verNamePrefix;

    def versionPropsFile = file(Consts.confPropFp);
    if(versionPropsFile.canRead()) {
        println 'conf.properties can read'
        def Properties versionProps = new Properties()
        versionProps.load(new FileInputStream(versionPropsFile))
        def verName = versionProps[Consts.verNamePropKey].toInteger()
        def runTasks = gradle.startParameter.taskNames
        println 'in getVersionName , runTasks: ' + runTasks;
        if(Consts.buildTypeOffline in runTasks || Consts.buildTypeDebug in runTasks || Consts.buildTypeRunDebug in runTasks) { //打測試包時才自增版本號, 線上包的版本號手動改
            targetVerName += "." + (++verName);  //版本號自增1
            versionProps[Consts.verNamePropKey] = verName.toString();
            versionProps.store(versionPropsFile.newWriter(), null) //增1後的版本號寫入properties檔案
        }
    }
    println "targetVerName: " + targetVerName;
    return targetVerName;
}

/**
 * 輸出apk檔案完整路徑,apk命名方式(自己去定):xxxxxxxxx_versionName.apk
 * @param variant
 * @param versionName
 * @return
 */
def getOutApkPath(variant, versionName) {
    def outPath = "./"
    def confPropsFile = file(Consts.confPropFp);
    if(confPropsFile.canRead()) {
        def Properties confProps = new Properties();
        confProps.load(new FileInputStream(confPropsFile));
        outPath = confProps[Consts.outApkPathPropKey];
    }
    def String finalOutPath = outPath + "xxxxxxx_v" + versionName + ".apk"; //apk名稱自己去定
    println "最終輸出路徑:" + finalOutPath
    return finalOutPath;
}

/**
 * 指令碼用到的常量定義
 */
interface Consts{
    /** buildType 型別名 */
    final String buildTypeRelease = 'assembleRelease';
    final String buildTypeOffline = 'assembleOffline';
    final String buildTypeDebug = 'assembleDebug';
    final String buildTypeRunDebug = ':app:assembleDebug'; //自己debug的時候看一下自己主工程名

    /** current_use.properties 檔案路徑 */
    final String curUsePropFp = './src/main/res/raw/current_use.properties';
    /** online.properties 檔案路徑 */
    final String onlinePropFp = './src/main/res/raw/online.properties';
    /** test.properties 檔案路徑 */
    final String offlinePropFp = './src/main/res/raw/test.properties';
    final String UTF8 = 'UTF-8';

    /** 配置檔名 */
    final String confPropFp = 'conf.properties';
    /** VERSION_NAME 在配置檔案中的欄位名:版本號中的第四個欄位 */
    final String verNamePropKey = 'VERSION_NAME';
    /** OUT_APK_PATH 在配置檔案中的欄位名:apk輸出目錄 */
    final String outApkPathPropKey = 'OUT_APK_PATH';

    /** 在strings.xml中版本號值名稱 */
    final String appVerNameValue = 'app_version_value';

}

dependencies {
    compile project(':xutils')
    compile 'com.google.code.gson:gson:2.2.4'
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile fileTree(include: '*.jar', exclude: 'android-support-multidex.jar', dir: 'libs')
    compile 'com.android.support:multidex:1.0.1'
    compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.+'

}

專案中的conf.properties檔案內容:

其中,欄位值根據自己專案進行配置

#Thu Feb 16 12:47:40 CST 2017
OUT_APK_PATH=F\:/kydd/android_apk_out/
VERSION_NAME=14