Android 中 app module和lib module同时多productFlavors的配置和使用

本文环境基于:

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却怎么也切不到。请注意编译器的警告:

Android 中 app module和lib module同时多productFlavors的配置和使用_第1张图片

也就是app module 默认选择了engineA,你不能选择engineB。到底问题出现在哪里?

如果有大佬去看了missingDimensionStrategy方法的实现应该已经明白了,源码如下:Android 中 app module和lib module同时多productFlavors的配置和使用_第2张图片

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工程目录结构

 

 

Android 中 app module和lib module同时多productFlavors的配置和使用_第3张图片Android 中 app module和lib module同时多productFlavors的配置和使用_第4张图片

 

你可能感兴趣的:(Android功能)