欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-30 16:37:41
...

本文环境基于:

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的配置和使用

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

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

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按钮Android 中 app module和lib module同时多productFlavors的配置和使用

 

完整配置代码如下,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的配置和使用Android 中 app module和lib module同时多productFlavors的配置和使用