2 分钟阅读

1.背景

2.基本原理

在Android Gradle中,定义了一个叫Build Variant的概念。一个Build Variant = Build Type + Product Flavor 。 也就是构建类型(如比release、debug) + 构建渠道(比如red、blue).它们组合起来就是:redRelease、redDebug、blueRelease、blueDebug。示例如下:

buildTypes配置

android {
...
    buildTypes {
        debug {
        ...
        }
        release {
        ...
        }
    }
}

productFlavors配置 官方文档 :build-variants

android {
    ...
    // Specifies a flavor dimension.
    flavorDimensions "color"

    productFlavors {
        red {
            // Assigns this product flavor to the 'color' flavor dimension.
            // This step is optional if you are using only one dimension.
            dimension "color"
            ...
        }

        blue {
            dimension "color"
            ...
        }
    }
}

BuildVariants 预览

20221030_build_variants.png

3.具体实现

对于build.gradle属性的详细解释可以参考: Android App模块下的的build.gradle配置详解

配置signingConfigs

    ...
    signingConfigs {
        release {
            def keystoreProperties = new Properties()
            rootProject.file("keystore/debugkey.properties").withInputStream {
                keystoreProperties.load(it)
            }
            keyAlias = keystoreProperties["keyAlias"] as String
            keyPassword = keystoreProperties["keyPassword"] as String
            storeFile = rootProject.file(keystoreProperties["storeFile"] as String)
            storePassword = keystoreProperties["storePassword"] as String
        }
    }

    buildTypes {
        debug {
         ...
        }
        release {
         ...
        signingConfig signingConfigs.release
        }
    }

20221030_3

我个人倾向于创建一个文件夹单独管理.jks文件,通过读取自己创建的.properties文件来获取定义的属性。上面file(“ keystore/debugkey.properties”).withInputStream{}是一个闭包函数,可以自动关闭流。

配置buildTypes

...
buildTypes {
    debug {
        applicationIdSuffix ".debug"  //applicationId 追加后缀名 .debug
        versionNameSuffix ".debug"  //versionName 追加后缀名 .debug
        }
        release {
        minifyEnabled true
        shrinkResources true
        zipAlignEnabled true
        signingConfig signingConfigs.release
        proguardFiles getDefaultProguardFile('proguard-android.txt'),  'proguard-rules.pro'
    }
    customType {
            initWith debug  //可以完全复制 debug 的所有属性
            minifyEnabled true //自定义打开混淆
    }
}

在代码中获取:

//获取buildConfigField的值
val API = BuildConfig.DEBUG_API_URL

配置productFlavor

...
flavorDimensions "color"
productFlavors {
    red {
        dimension "color"
        applicationId "com.example.red" 
        resConfigs 'zh' //或者 resConfigs “hdpi", “xhdpi", “xxhdpi" ,用来配置哪些类型的资源才被打到包中去
        //applicationId 应用的包名,会覆盖 defaultConfig 中的 applicationId,同时applicationId 会替换 AndroidManifest.xml 中的 manifest 标签下 package 的 value.
        manifestPlaceholders = [ //修改 AndroidManifest.xml 里渠道变量
                CHANNEL: "red"
        ]
        //动态修改 常量 
        // buildConfigField "int", "age", "10"   //自定义 int 值
        // buildConfigField "Boolean", "openLog", "true" //自定义 boolean 值
        // buildConfigField "String", "DEBUG_API_URL", "\"http://www.xxx.com\"" //自定义String 值
    
        //动态在string.xml 添加字段(string.xml中不能有这个字段,会重名),不建议这样添加,因为国际化的时候这里没办法处理
        // resValue "string", "app_name", "百度"
        // resValue "bool", "auto_updates", 'false'
        
        
    }
    blue {
        dimension "color"
        applicationId "com.example.blue" 
        resConfigs 'cn'
        manifestPlaceholders = [
                CHANNEL: "blue"
        ]
        
    }
}
//AndroidManifest.mxl  这样就可以通过获取application
        ...
<application
        ...>
        <!--通过gradle和AndroidManifest的配置就可以在代码里获取application meta-data的值了 -->
<meta-data android:name="CHANNEL" android:value="${CHANNEL}"/>
        ...
        </application>

applicationId是应用程序的唯一标识,用于指写生成的APP的包名,默认情况下是null。若为null,则会在建的时候从AndroidManifest.xml文件中配置的manifest标签的package属性读取,该标识用于上传到应用店区分不同的应用

配置sourceSets

Android 项目中,引入了配置sourceSets来管理目录。代码的摆放位置都是约定好的,但是我们可以使用sourceSets来从新配置路径,举个例子:

    ...
    sourceSets {
        //sourceSets 下可以装载多个 sourceSet,我们这里的 main,red和 blue即为 sourceSet
        main {
          //main 为主干,其他的flavor设置为分支,分支的设置会合并到主干中,例如这里的【red flavor】的配置会合并 main 的配置。
        ...
        }
        red {
        //sourceSets 可以针对不同的 flavor 进行个性化配置
        res.srcDirs = ['src/main/res-red']
        java.srcDirs = ['src/red/java']
        ...
        }
        blue {
        res.srcDirs = ['src/main/res-blue']
        zh.java.srcDirs = ['src/blue/java']
        ...
        }
    }
    //注意 'src/main/res-prod' 和 'src/main/res-uat' 需要自己手动创建;

方式一:在工程目录下创建和flavor名称相同的文件夹 (Google推荐)

在main的同级目录下,有几个渠道就新建几个渠道的文件夹,这种方式在程序编译打包的时候会优先选择flavor目录下的资源,如果main包下存在同名资源则进行替换,否则会和main包的资源进行合并。

20221030_2.png

方式二:使用manifestPlaceholders

build.gradle脚本配置manifestPlaceholders

...
android {
    defaultConfig {
        ... ...
        manifestPlaceholders = [my_app_ame: "demo1", my_app_icon: "@mipmap/logo1"]
    }
    // 依据debug/release变动的话设置如下
    buildTypes {
        debug {
            manifestPlaceholders = [my_app_ame: "demo1", my_app_icon: "@mipmap/logo1"]
        }
    }
 
    // 依据flavors变动的话设置如下
    flavorDimensions "color"
    productFlavors {
        red {
            ...
            dimension "color"
            manifestPlaceholders = [my_app_ame: "demo1", my_app_icon: "@mipmap/logo1"]
         }

        blue {
            ...
            dimension "color"
            manifestPlaceholders = [my_app_ame: "demo2", my_app_icon: "@mipmap/logo2"]
         }
    }
}

defaultConfig实际上属于productFlavors,提供所有变体的默认配置。buildType也可视作一个变种维度flavorDimensions, 并且默认有debug和release两个变体,所以其实有些属性是通用的。

对应的AndroidManifest.xml

...
<application
        android:icon="${my_app_icon}"
        android:label="${my_app_ame}">
    ···
</application>

5.马甲包有时候需要垃圾代码

参考插件:AndroidJunkCode

6.总结

通过以上配置就可以利用Gradle实现差异化构建,这也是google官方demo中推荐的方式。 对于替换资源的需求其实无论是利用manifestPlaceholders、buildConfigField、sourceSets还是通过创建和flavor名称相同的文件夹都能实现,具体想怎样完全看你自己。

除了使用官方的方式以外还可以利用apktool解包之后对资源进行替换,当然还有一些优秀的三方框架可供选择:wallepacker-ng-plugin 等。

留下评论