本文环境基于:
studio版本:3.2.1和gradle-4.10.2-all
截止目前,网上的方法都是基于旧版本的studio,在这里记录一下最新环境的配置。
查阅了很多网站和资料,都是旧的方法,一直没搞好,前前后后耗时将近了两个星期。主要还是太low,一开始没太看懂官方的文档:添加编译依赖项|Android Developers。
github还有个很新的demo:multi-flavor-lib-demo。
注:本文的app module是指module为apply plugin: 'com.android.application'的应用module,非库module.
demo包含主要的两个app和enginelib module。
1.第一步,既然是多产品,那就定义多个product,在这,app module中定义productA、productB、productC三个产品。见app的build.gradle:
productFlavors {
productA {
applicationIdSuffix ".a"
dimension 'app'
}
productB {
applicationIdSuffix ".b"
dimension 'app'
}
productC {
applicationIdSuffix ".c"
dimension 'app'
}
}
注:由于新版本studio多产品需要指定flavorDimensions,别忘了在defaultConfig{}标签添加。
2.第二步,lib module也要多产品,那同样的也给它定义上,如engineA、engineB。见enginelib的build.gradle:
productFlavors {
engineA {
dimension 'sdk'//定义维度,方便在app module指定属性
}
engineB {
dimension 'sdk'
}
}
注:别忘了添加flavorDimensions。
到此,两个module的多productFlavors已经定义好了,先sync一下,发现没什么问题。接着继续,既然lib module是lib,那肯定要用来依赖的。
3.第三步,把lib module依赖到app module,添加依赖。见app的build.gradle:
implementation project(path: ':enginelib')
再次点击sync,发觉有报错并不能成功。接下来需要加一个属性定义才行(真料在此),见下一步。
4.第四步,添加missingDimensionStrategy,至于它的作用见名知意哈,可以自己查阅官方文档。
defaultConfig {
...
//使用missingDimensionStrategy指定lib module中的两个flavor,"sdk"为lib module中定义的dimension
//不然,app module将无法sync成功和找到lib module中的类
missingDimensionStrategy "sdk", "engineA", "engineB"
}
大概意思是:告诉编译插件缺少“sdk”维度的engineA和engineB两个flavor。
此时依赖能正常编译,lib中的类也都能被app module中找到和使用了。
但是!但是!但是!当你打包运行,不管你怎么切换Build variants中的lib module的flavor,出来的包都是engineA的部分。engineB却怎么也切不到。请注意编译器的警告:
也就是app module 默认选择了engineA,你不能选择engineB。到底问题出现在哪里?
如果有大佬去看了missingDimensionStrategy方法的实现应该已经明白了,源码如下:
get(0)?,大概就它了。上面定义的:
missingDimensionStrategy "sdk", "engineA", "engineB"
也就是“engineB”定义了并没有什么用,不信你可以去掉,照样能正常运行。
既然问题出现在这里,那我能不能换个思路:动态改变! emmmmmm
下面我们用了个巧妙的方式,给他动态的改变,见第五步。
5.第五步,改造
在这里思路是先把Build Variants中选择后的flavor保存起来,然后再给app module用。
1)在enginelib的build.gradle中添加逻辑代码(代码放android{}标签里面),获取当前flavor并保存到文件:
android.libraryVariants.all { v ->
//当flavor被切换时(如在Build Variants中切换)
//重新读取当前被切换的variant并保存到配置文件
def currName = getCurrentFlavor()
// println("------111------$currName")
File conf = new File("curr-flavor.config")
if (!conf.exists()) {
conf.createNewFile()
}
conf.withWriter('UTF-8') { writer ->
writer.write("flavor=$currName")
writer.flush()
writer.close()
}
}
def getCurrentFlavor() {
Gradle gradle = getGradle()
//过滤出本module的当前flavor,正则表达式的[enginelib]需要根据自己的进行修改
Pattern pattern = Pattern.compile(":enginelib:(assemble|generate)(\\w+)(Release|Debug)")
String find = ""
def tsks = gradle.getStartParameter().getTaskRequests()
tsks.forEach { tsk ->
tsk.args.forEach { tskName ->
Matcher matcher = pattern.matcher(tskName.toString())
if (matcher.find()) {
//正则表达式中匹配到的第三组:(\\w+)部分
find = matcher.group(2)
return find
}
}
}
return find
}
2)在app module的build.gradle中添加逻辑代码,获取当前flavor的值:
def flavor = getCurrentFlavor()
/**
* 读取配置文件的内容
* @return
*/
def getCurrentFlavor() {
def flavorProperties = new Properties()
String rtn = "engineA"
def conf = new File("curr-flavor.config")//自定义的配置文件
if (conf.exists()) {
conf.withReader('UTF-8') { reader ->
flavorProperties.load(reader)
}
}
rtn = flavorProperties.getProperty('flavor')
if (rtn != null && rtn.length() > 1)
rtn = "${Character.toLowerCase(rtn.charAt(0))}${rtn.substring(1)}"
else rtn = "engineA"
return rtn
}
6.第六步,替换第四步的写法
//使用missingDimensionStrategy指定lib module中的两个flavor,"sdk"为lib module中定义的dimension
//不然,app module将无法sync成功和找到lib module中的类
// missingDimensionStrategy "sdk", "engineA", "engineB"
missingDimensionStrategy "sdk", "$flavor"
注:完美!
7.第七步,不完美的方案
现在你可以尽情的切换Build variants吧,什么?切换了还是不行?emmmmmm 。切换完后记得再次点击sync按钮哈
完整配置代码如下,app 的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
def flavor = getCurrentFlavor()
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.nextstage.modularity"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
flavorDimensions "app"
//使用missingDimensionStrategy指定lib module中的两个flavor,"sdk"为lib module中定义的dimension
//不然,app module将无法sync成功和找到lib module中的类
// missingDimensionStrategy "sdk", "engineA", "engineB"
missingDimensionStrategy "sdk", "$flavor"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
productA {
applicationIdSuffix ".a"
dimension 'app'
//重新定义此属性,动态的获取lib module的当前flavor
// missingDimensionStrategy "sdk", "$flavor"
}
productB {
applicationIdSuffix ".b"
dimension 'app'
// missingDimensionStrategy "sdk", "$flavor"
}
productC {
applicationIdSuffix ".c"
dimension 'app'
// missingDimensionStrategy "sdk", "$flavor"
}
}
android.applicationVariants.all { variant ->
variant.outputs.all {
if (outputFileName.endsWith('.apk')) {
def fileName = "Test-${variant.flavorName}-$flavor-v${defaultConfig.versionName}-${buildType.name}.apk"
outputFileName = fileName
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
implementation project(path: ':utillib')
implementation project(path: ':base')
implementation project(path: ':enginelib')
}
/**
* 读取配置文件的内容
* @return
*/
def getCurrentFlavor() {
def flavorProperties = new Properties()
String rtn = "engineA"
def conf = new File("curr-flavor.config")//自定义的配置文件
if (conf.exists()) {
conf.withReader('UTF-8') { reader ->
flavorProperties.load(reader)
}
}
rtn = flavorProperties.getProperty('flavor')
if (rtn != null && rtn.length() > 1)
rtn = "${Character.toLowerCase(rtn.charAt(0))}${rtn.substring(1)}"
else rtn = "engineA"
return rtn
}
enginelib module的build.gradle
import java.util.regex.Matcher
import java.util.regex.Pattern
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 26
defaultConfig {
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
flavorDimensions "sdk"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
engineA {
dimension 'sdk'//定义维度,方便在app module指定属性
}
engineB {
dimension 'sdk'
}
}
android.libraryVariants.all { v ->
//当flavor被切换时(如在Build Variants中切换)
//重新读取当前被切换的variant并保存到配置文件
def currName = getCurrentFlavor()
if (currName.isEmpty()) return
// println("------111------$currName")
File conf = new File("curr-flavor.config")
if (!conf.exists()) {
conf.createNewFile()
}
conf.withWriter('UTF-8') { writer ->
writer.write("flavor=$currName")
writer.flush()
writer.close()
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
testImplementation 'junit:junit:4.12'
implementation project(':base')
}
def getCurrentFlavor() {
Gradle gradle = getGradle()
//过滤出本module的当前flavor,正则表达式的[enginelib]需要根据自己的进行修改
Pattern pattern = Pattern.compile(":enginelib:(assemble|generate)(\\w+)(Release|Debug)")
String find = ""
def tsks = gradle.getStartParameter().getTaskRequests()
tsks.forEach { tsk ->
tsk.args.forEach { tskName ->
Matcher matcher = pattern.matcher(tskName.toString())
if (matcher.find()) {
//正则表达式中匹配到的第三组:(\\w+)部分
find = matcher.group(2)
return find
}
}
}
return find
}
Demo工程目录结构