Gradle 多渠道打包
我们在日常开发中多多少少都会遇到多渠道打包的情况。这些版本可能会上传到不同的应用市场,也可能是是线下多渠道推荐。有时候可能不同的渠道使用的资源图片都不一样。古老的做法就是,需要打多少个渠道包拉出多少份代码分支,分别替换对应的资源文件和包名配置信息等。这种做法非常的耗时耗力。Gradle 可以帮我们用一份代码通过配置实现打出所有的渠道包。
1. 创建多渠道资源文件目录
首先,我们新创建一个工程,然后在 main 模块下面,根据不同渠道创建不同的资源文件目录。我们先定义一个简单的页面,里面显示渠道跟一张图片。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>
然后我们根据我们用到的资源,对不同渠道配置不同的资源。我们这里 model 比较简单,主要是图片和字符串,图片这里我们不同渠道显示的不同。具体目录如下所示:
Tips: 注意这里我们创建资源文件目录的时候不能包含 test ,否则会编译报错的。我亲自尝试过
res-test
。
2. 配置多渠道资源路径
前面我们创建了多渠道的资源目录,那么我们就需要将它配置在 build.gradle 中。我们前面介绍 AS 中 Android 项目的 Gradle 配置时讲到过,我们在 SourceSet 闭包配置。具体多渠道配置如下:
//配置资源文件路径,可动态指定不同版本资源文件
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'
}
//用各自对应的资源文件路径
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字段命令(会运行报错的,如res-test)
* */
productFlavors {
chanlA{
applicationId "com.bthvi.chanla"//可为不同版本动态指定包名
}
chanlB{
applicationId "com.bthvi.chanlb"//可为不同版本动态指定包名
}
}
4. 配置 apk 包输出的路径
我们如果不配置 APK 包的输出路径默认是在,build 文件夹下的。但是我们这里为了方便可以将编译生成的 APK 包放在一个自己指定的目录下面。我们这里放在根目录的 apks 文件夹下,然后以不同渠道的 applicationId 以及日期存放不同的渠道的包。具体的 Gradle 配置信息如下:
// 版本比较多时,自定义导出的APK文件的名称
applicationVariants.all {
//获取是release还是debug版本
def buildType = it.buildType.name
def fileName
//下面的channel是获取渠道号,你获取渠道号不一定会和我的代码一样,因为有可能你的渠道名称的规则和我的不一样,我的规则是${渠道名}-${applicationId},所以我是这样取的。
def channel = it.productFlavors[0].name.split("-")[0]
//获取当前时间的"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 {
//我此处的命名规则是:渠道名_版本名_创建时间_构建类型.apk[大家也可以根据自己的需求命名]
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. 经常遇到的坑
-
这里我们需要注意的一点就是自己的 Gradle 版本,根据不同的版本命名 apk 文件的命令也是不一样的。在 Gradle 4.0 以上使用
it.outputFile = new File(it.outputFile.parent, fileName)
会抛出以下的错误.
-
我们还是需要注意自己的 Gradle 版本。由于 Gradle 3.0.0 之后有一种自动匹配消耗库的机制,便于 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'
}
}
//配置资源文件路径,可动态指定不同版本资源文件
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'
}
//用各自对应的资源文件路径
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')
}
// 版本比较多时,自定义导出的APK文件的名称
applicationVariants.all {
//获取是release还是debug版本
def buildType = it.buildType.name
def fileName
//下面的channel是获取渠道号,你获取渠道号不一定会和我的代码一样,因为有可能你的渠道名称的规则和我的不一样,我的规则是${渠道名}-${applicationId},所以我是这样取的。
def channel = it.productFlavors[0].name.split("-")[0]
//获取当前时间的"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 {
//我此处的命名规则是:渠道名_版本名_创建时间_构建类型.apk[大家也可以根据自己的需求命名]
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字段命令(会运行报错的,如res-test)
* */
productFlavors {
chanlA{
applicationId "com.bthvi.chanla"//可为不同版本动态指定包名
}
chanlB{
applicationId "com.bthvi.chanlb"//可为不同版本动态指定包名
}
}
}
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'
}
然后我们就可以执行命令打包了,我们这里执行 gradle build
就可以打出所有渠道的 debug 和 release 包了。具体打包输出及 APK 包的输出目录如下所示:
下面我们装上不同渠道的包,看下是否不同渠道的图片和字符串是不同的。
7. 小结
这一节内容我们主要讲了如何通过 Gradle 配置不同资源文件实现一份代码打出多个渠道包。相较于之前我们用不同的代码分支来打多渠道包,效率提升很多。