Gradle 爬坑指南 -- Varian

2,661 阅读4分钟

Gradele 更多文章请看我的主页

Gradle 中的动态参数

这部分参数是不用写死的,可以在 Gradle 构建过程中由我们手动赋值,使用非常广泛,具体写法我老是记不住,每次用都得现 baidu

1. manifestPlaceholders

Gradle 中声明的 manifestPlaceholders 参数,在 AndroidManifest.xml 中可以通过 $hostName 的方式引用到

manifestPlaceholders 可以写在 Gradle 脚本 android{...} DSL 中,常见的应用位置: defaultConfig {...}productFlavors{...}buildTypes{...}

android {
    defaultConfig {
        manifestPlaceholders(["hostName":"www.example.com"])
    }
}

可以在配置文件中的任何位置使用

<?xml .....>
<manifest ....>
    <application ....>
        <activity ....>
            <intent-filter>
                <data android:scheme="http" android:host="${hostName}"/>
            </intent-filter>
        </...>
    </...>
</...>

2. buildConfigField

buildConfigField 方法会在生成 BuildConfig.java 时, 向其中插入该属性。日志模块我们就是依托这个参数,来统一控制 release 版本不打印日志

android {
    defaultConfig {
        buildConfigField("int", "i", "1234")
        buildConfigField("String", "str", "\"some text\"")
        buildConfigField("boolean", "isRelease", "true")
    }
}

可以看到 build 后,BuildConfig 中有这个参数了

3. resValue

resValue 比大家可能见的少,用来向 res/values/strings.xml 中添加数据

android {
    defaultConfig {
        resValue("string", "app_name_test", "some text")
    }
}

4. resConfigs

defaultConfig {
	// 资源打包过滤
	resConfigs("en", "ldltr")
}

resConfigs(...) 是系统资源过滤,同一类型的资源,只有 resConfigs{...} 声明的配置才能打入 APK 中,可以用在:defaultConfig {...}productFlavors{...}buildTypes{...}

  • en: 只选择英文
  • ldltr: 方向性资源上只要从左到右的资源文件

变体

记住变体使用的单词:Variants,变体也叫:差异化构建

脚本中 android{...} 之外的地方我们也可以通过 this.android.xxx 配置

1. 什么是变体

变体 Variants 这个概念来自 Gradle Android Plugin,单看 productFlavors{...}buildTypes{...} 都写在 android{...} 就能猜到了

那么变体是什么呢,就是我们打包 APK 得到的最终产物,图中就是,新版本的 Android Plugin 还多了一个 json 描述文件,写的都是打包该 APK 时的配置参数

注意看图中 APK 所在的文件夹,每一个 变体 Variants 实际代表的是这个 APK 文件所在的文件夹,按图中理解,Variants 就是 debug、release 这2个文件夹

2. 变体的维度

Android 插件设计 Variants 时设计了3个维度:

  • dimension
  • flavor
  • buildType

这样理解:

  • dimension: 渠道分级,dimension 级别最高
  • flavor: 渠道,flavor 级别次之
  • buildType: 构建类型,级别最低

最终的 Variant.name = flavor + buildType,不过这里 flavor 取的是 不同 dimension 的 flavor 之间笛卡尔集,看图:

理解重点是:不同 dimension 分组之间的 flavor 交叉取集 笛卡尔集

dimensionflavor 这2个级别可以不用,写不写全看你

在这里废了不少时间,网上资料这里很多写的都不对,费大事了

3. DSL 编写

android {

    flavorDimensions "xiaomi", "huawei"
    productFlavors {
        JD {
            dimension("xiaomi")
        }
        TAO {
            dimension("xiaomi")
        }
        PIN {
            dimension("huawei")
        }
    }

    buildTypes {
        release{}
        vip{}
    }   
}

4. 主项目和子项目 buildTypes 必须同步

从 android 3.0 版插件开始,主项目和子项目之间的 buildTypes 必须一样,除了默认的 debug、release 之外,主项目多写一个 buildType,子项目也必须有这个 buildType 才行,要不报错

// app module
buildTypes {
	release {}
	vip{}
}

// libs module
buildTypes {
	vip{}
}

这样才能过,如果不想每个子项目都写的话,可以这样,把多出来的 buildType 写到基类 gradle 脚本中,每个项目继承这个基类脚本就行了

// base_gradle.gradle
android {
    buildTypes {
        pay {}
    }
}

// app module
apply from: rootProject.file("base_gradle.gradle")

// libs module
apply from: rootProject.file("base_gradle.gradle")

5. 过滤变体

有的变体我们不想打出来,我们可以过滤他

没有自动提示真蛋疼,这段我没跑过去,我也不知道哪拼错了,我这 VPN 被封了没法翻墙看 Android 插件文档,蛋疼,大家都想办法配一个 VPN 吧,有好使的 VPN 评论通知我一声,我也去搞一个

android{
        variantFilter{ variant ->
            if (variant.buildType.name.equals("release") ){
                variant.productFlavors.each{ flavor ->
                    if (flavor.name.equals("PIN")){
                        variant.ignore = true
                    }
                }
            }
        }
}

6. 修改 apk 文件名

Android Gradle 插件提供了3个属性,applicationVariants(适用于Android 应用Gradle插件),libraryVariants(适用于Android库gradle插件),testVariants(以上两种都适用)

applicationVariants 是一个集合,内部元素是一个个 Variants 变体,就是 xxxRelease、xxxDebug 这些,而每一个 Variants 变体的输出物不仅仅只有 apk 文件,还有其他一些文件,所以 variant.outputs 也是一个集合,要修改 APK 名称的话,我们淂多一道手,判断是不是 APK 文件

applicationVariants 是 android 插件提供的设置,必须写在 android{...} DSL 里面,一般我们都是写在 app module 的脚本里

def buildTimes() {
    return new Date().format("yyyyMMdd")
}

applicationVariants.all { variant ->
    variant.outputs.all { output ->
        if (output.outputFile == null && !output.outputFile.name.endsWith('.apk')) {
            contiue
    }

    def info = ""
	
    // 因为维度存在,所以一个 variant 可能由有一个以上的 Flavor 交叉组成,所以这里 Flavor 名也是复数的
    variant.productFlavors.each{
        info = info + it.name + "_"
    }

    println("TAG:${variant.name}/${info}")
    outputFileName = ${info}${variant.buildType.name}_v${variant.versionCode}_${buildTimes()}.apk"
}

上面这点代码本身不复杂,就是2层遍历,但是蛋疼的是,AS 没有自动提示,DSL 这种语法糖也不提示参数类型,这叫我们怎么写呀,点进去看看有啥参数都找不到地方

Gradle 与组件化的结合

一般的就不说了,写几个常用的,有意思的,其他的设置可以参考下面:

1. 测试、发版时切换 BaseUrl

buildTypes {
	debug{
		buildConfigField( 'String','baseUrl','"baseUrl_debug"' )
	}
	release {
		buildConfigField( 'String','baseUrl','"baseUrl_release"' )
	}
}

2. flavor引用不同的module

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
 
    // 引用本的项目
    devImplementation project(':devModule')
    stageImplementation project(':stageModule')
    prodImplementation project(':prodModule')
 
    // 也可以分渠道引用网络的。因为这里都相同,所以地址也就都一样了
    devImplementation 'com.roughike:bottom-bar:2.0.2'
    stageImplementation 'com.roughike:bottom-bar:2.0.2'
    prodImplementation 'com.roughike:bottom-bar:2.0.2'
}