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