Gradle 多渠道打包
我們在日常開發(fā)中多多少少都會遇到多渠道打包的情況。這些版本可能會上傳到不同的應(yīng)用市場,也可能是是線下多渠道推薦。有時候可能不同的渠道使用的資源圖片都不一樣。古老的做法就是,需要打多少個渠道包拉出多少份代碼分支,分別替換對應(yīng)的資源文件和包名配置信息等。這種做法非常的耗時耗力。Gradle 可以幫我們用一份代碼通過配置實現(xiàn)打出所有的渠道包。
1. 創(chuàng)建多渠道資源文件目錄
首先,我們新創(chuàng)建一個工程,然后在 main 模塊下面,根據(jù)不同渠道創(chuàng)建不同的資源文件目錄。我們先定義一個簡單的頁面,里面顯示渠道跟一張圖片。layout 布局文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="26sp"
android:textColor="@color/colorPrimary"
android:text="@string/chanl_name" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@mipmap/girl"
>
</ImageView>
</LinearLayout>
然后我們根據(jù)我們用到的資源,對不同渠道配置不同的資源。我們這里 model 比較簡單,主要是圖片和字符串,圖片這里我們不同渠道顯示的不同。具體目錄如下所示:
Tips: 注意這里我們創(chuàng)建資源文件目錄的時候不能包含 test ,否則會編譯報錯的。我親自嘗試過
res-test
。
2. 配置多渠道資源路徑
前面我們創(chuàng)建了多渠道的資源目錄,那么我們就需要將它配置在 build.gradle 中。我們前面介紹 AS 中 Android 項目的 Gradle 配置時講到過,我們在 SourceSet 閉包配置。具體多渠道配置如下:
//配置資源文件路徑,可動態(tài)指定不同版本資源文件
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/resources']
aidl.srcDirs = ['src/main/aidl']
renderscript.srcDirs = ['src/maom']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
jniLibs.srcDir 'src/main/jniLibs'
}
//用各自對應(yīng)的資源文件路徑
chanlA.res.srcDirs = ['src/main/res-a']
chanlB.res.srcDirs = ['src/main/res-b']
androidTest.setRoot('tests')
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
3. 配置渠道及不同渠道包名等
配置多渠道在 Gradle 中使用 productFlavors
閉包,在這個閉包中我們可以配置所有的渠道,以渠道名為閉包,每個渠道可以配置 applicationId 和簽名信息等。我們這里簡單配置了applicationId。如下:
/*
* 渠道Flavors,配置不同Chanl的app
* 資源文件不能用test字段命令(會運(yùn)行報錯的,如res-test)
* */
productFlavors {
chanlA{
applicationId "com.bthvi.chanla"//可為不同版本動態(tài)指定包名
}
chanlB{
applicationId "com.bthvi.chanlb"//可為不同版本動態(tài)指定包名
}
}
4. 配置 apk 包輸出的路徑
我們?nèi)绻慌渲?APK 包的輸出路徑默認(rèn)是在,build 文件夾下的。但是我們這里為了方便可以將編譯生成的 APK 包放在一個自己指定的目錄下面。我們這里放在根目錄的 apks 文件夾下,然后以不同渠道的 applicationId 以及日期存放不同的渠道的包。具體的 Gradle 配置信息如下:
// 版本比較多時,自定義導(dǎo)出的APK文件的名稱
applicationVariants.all {
//獲取是release還是debug版本
def buildType = it.buildType.name
def fileName
//下面的channel是獲取渠道號,你獲取渠道號不一定會和我的代碼一樣,因為有可能你的渠道名稱的規(guī)則和我的不一樣,我的規(guī)則是${渠道名}-${applicationId},所以我是這樣取的。
def channel = it.productFlavors[0].name.split("-")[0]
//獲取當(dāng)前時間的"YYYY-MM-dd"格式。
def createTime = new Date().format("YYYY-MM-dd", TimeZone.getTimeZone("GMT+08:00"))
it.getPackageApplication().outputDirectory = new File(project.rootDir.absolutePath + "/apks/${it.productFlavors[0].applicationId}/${createTime}")
it.outputs.each {
//我此處的命名規(guī)則是:渠道名_版本名_創(chuàng)建時間_構(gòu)建類型.apk[大家也可以根據(jù)自己的需求命名]
fileName = "${channel}_v${defaultConfig.versionName}_${createTime}-${buildType}.apk"
//打印出apk文件名稱,以便及時查看是否滿足要求
logger.quiet("文件名:>>>>>>>>>>>>>>>>>>>>>>${fileName}")
//重新對apk命名。
//Gradle4.0以下版本
//it.outputFile = new File(it.outputFile.parent, fileName)
//Gradle4.0(含)以上版本
it.outputFileName = fileName
}
}
5. 經(jīng)常遇到的坑
-
這里我們需要注意的一點(diǎn)就是自己的 Gradle 版本,根據(jù)不同的版本命名 apk 文件的命令也是不一樣的。在 Gradle 4.0 以上使用
it.outputFile = new File(it.outputFile.parent, fileName)
會拋出以下的錯誤.
-
我們還是需要注意自己的 Gradle 版本。由于 Gradle 3.0.0 之后有一種自動匹配消耗庫的機(jī)制,便于 debug variant 自動消耗一個庫,然后就是必須要所有的flavor 都屬于同一個維度。所以如果你的 Gradle 是 3.0.0 之后的版本,我們需要在主 app 的 build.gradle 里面的 defaultConfig 閉包中加入 flavorDimensions “versionCode”。意思是flavor dimension 它的維度就是版本號。否則編譯會報如下錯:
6. 最終的效果
配置完上面的所有之后,我們先看下完整的 build.gradle 長啥樣。
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.bthvi.chanl"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
flavorDimensions "versionCode"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
//配置資源文件路徑,可動態(tài)指定不同版本資源文件
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/resources']
aidl.srcDirs = ['src/main/aidl']
renderscript.srcDirs = ['src/maom']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
jniLibs.srcDir 'src/main/jniLibs'
}
//用各自對應(yīng)的資源文件路徑
chanlA.res.srcDirs = ['src/main/res-a']
chanlB.res.srcDirs = ['src/main/res-b']
androidTest.setRoot('tests')
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
// 版本比較多時,自定義導(dǎo)出的APK文件的名稱
applicationVariants.all {
//獲取是release還是debug版本
def buildType = it.buildType.name
def fileName
//下面的channel是獲取渠道號,你獲取渠道號不一定會和我的代碼一樣,因為有可能你的渠道名稱的規(guī)則和我的不一樣,我的規(guī)則是${渠道名}-${applicationId},所以我是這樣取的。
def channel = it.productFlavors[0].name.split("-")[0]
//獲取當(dāng)前時間的"YYYY-MM-dd"格式。
def createTime = new Date().format("YYYY-MM-dd", TimeZone.getTimeZone("GMT+08:00"))
it.getPackageApplication().outputDirectory = new File(project.rootDir.absolutePath + "/apks/${it.productFlavors[0].applicationId}/${createTime}")
it.outputs.each {
//我此處的命名規(guī)則是:渠道名_版本名_創(chuàng)建時間_構(gòu)建類型.apk[大家也可以根據(jù)自己的需求命名]
fileName = "${channel}_v${defaultConfig.versionName}_${createTime}-${buildType}.apk"
//打印出apk文件名稱,以便及時查看是否滿足要求
logger.quiet("文件名:>>>>>>>>>>>>>>>>>>>>>>${fileName}")
//重新對apk命名。
//Gradle4.0以下版本
//it.outputFile = new File(it.outputFile.parent, fileName)
//Gradle4.0(含)以上版本
it.outputFileName = fileName
}
}
/*
* 渠道Flavors,配置不同Chanl的app
* 資源文件不能用test字段命令(會運(yùn)行報錯的,如res-test)
* */
productFlavors {
chanlA{
applicationId "com.bthvi.chanla"//可為不同版本動態(tài)指定包名
}
chanlB{
applicationId "com.bthvi.chanlb"//可為不同版本動態(tài)指定包名
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
然后我們就可以執(zhí)行命令打包了,我們這里執(zhí)行 gradle build
就可以打出所有渠道的 debug 和 release 包了。具體打包輸出及 APK 包的輸出目錄如下所示:


下面我們裝上不同渠道的包,看下是否不同渠道的圖片和字符串是不同的。


7. 小結(jié)
這一節(jié)內(nèi)容我們主要講了如何通過 Gradle 配置不同資源文件實現(xiàn)一份代碼打出多個渠道包。相較于之前我們用不同的代碼分支來打多渠道包,效率提升很多。