本文已参与「新人创作礼」活动,一起开启掘金创作之路
Android gradle插件配置
Variants
variant直接翻译叫变体,实际就是指打包输出不同版本的apk
我们随便新建的一个AndroidStudio工程,打包的apk默认都会有两个版本,debug和release,这两个不同版本的apk就称之为变体
在gradle中,变体主要在两个闭包中进行配置,buildTypes和productFlavors
buildTypes
buildTypes是一个域对象,内部声明的每一个闭包都会被处理为一个BuildType对象,例如默认会有debug和release两个,我们也可以完全自定义,例如添加一个local
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
}
// 可根据需要随便定义一个新的BuildType
local {}
}
执行一次assemble任务,就会在outputs文件夹中输出三个文件夹,分别对应了这3个BuildType
productFlavors
productFlavors跟buildTypes是同一个纬度的配置,都用于输出不同的apk包,但是buildTypes偏向于编译上的不同配置,相对来说是偏技术性的,例如接口环境,混淆配置等等,而productFlavors则更偏向于业务产品上的不同,例如不同商店的渠道包,标准版和收费版等
productFlavor是一个通用意义上的产品的变体纬度,但具体是根据什么方向划分,这就需要先细分产品业务具体的维度,例如根据渠道
// 声明一个名为“channel”的维度
flavorDimensions "channel"
// 类似buildTypes,productFlavors中创建的每一个闭包都对应一个ProductFlavor类型
productFlavors {
xiaomi {
// 每一个flavor需要指明当前对应的维度
dimension "channel"
}
oppo {
dimension "channel"
}
}
从输出的apk可以看出,productFlavors中的配置会和buildTypes中的配置进行组合
首先会根据channel维度,分成对应的两个文件夹,然后在文件夹内,又会按照buildType的类型分成3个文件夹
再继续加上一个产品维度,是否收费的版本类型(标准版和pro收费版)
flavorDimensions "channel","type"
productFlavors {
xiaomi {
dimension "channel"
}
oppo {
dimension "channel"
}
//
stantard {
dimension "type"
}
pro {
dimension "type"
}
}
从这次输出的apk可以看出,不同维度的flavor也是会相互组合的
manifestPlaceholders
manifestPlaceholders,顾名思义,用于manifest文件的属性值占位
在我们开发中,有时候会用到一些第三方的库或者工具,需要在AndroidManifest文件中去声明一个meta-data的值
类似这样
<meta-data
android:name="Third-AppKey"
android:value="ABCDEFG123456789" />
例如上述我们提到的不同的渠道包,需要在AndroidManifest中去设置不同的channel值,但是如果每打一个渠道包都去手动修改肯定不现实,这种情况就可以借助manifestPlaceholders
manifestPlaceholders是VariantProperties接口的一个属性,是一个Map,而BuildType,ProductFlavor,DefaultConfig这几个类型都实现了这个接口,所以我们defaultConfig,buildTypes下具体的Type中以及productFlavors的具体flavor中都可以访问到这个manifestPlaceholders,并给他设置或添加对应的key-value
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName APP_VERSION
// 所有的版本默认的值
manifestPlaceholders = [APP_KEY: "key123456", APP_SECRET: "secret"]
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// release版本指定特殊的key
manifestPlaceholders.put("APP_KEY","releaseKey")
}
debug {
minifyEnabled false
}
local {}
}
flavorDimensions "channel", "type"
productFlavors {
xiaomi {
dimension "channel"
manifestPlaceholders.put("CHANNEL_NAME","xiaomi")
}
oppo {
dimension "channel"
manifestPlaceholders.put("CHANNEL_NAME","oppo")
}
stantard {
dimension "type"
}
pro {
dimension "type"
}
}
在AndroidManifest.xml中可直接使用
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<meta-data
android:name="AppKey"
android:value="${APP_KEY}"/>
<meta-data
android:name="Channel"
android:value="${CHANNEL_NAME}"/>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.MyApplication.NoActionBar">
</activity>
</application>
buildConfigField
buildConfigField的作用是为输出的不同编译版本设置不同的参数值,以便代码中可以动态根据版本进行逻辑控制。例如最常见的,debug版和release版使用不同的api接口,代码中判断是否是付费pro版本等
buildConfigField也是可以在defaultConfig,buildTypes以及productFlavors中使用
buildConfigField函数主要接收3个参数
/**
* type为字段类型,例如String,int,boolean
* name位字段名称
* value为字段取值,tips:如果type为String,对应的value需要在单引号里面使用双引号设置具体字符串
*/
public void buildConfigField(String type,String name, String value)
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName APP_VERSION
manifestPlaceholders = [APP_KEY: "key123456", APP_SECRET: "secret"]
buildConfigField 'int','API_VERSION','1'
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
manifestPlaceholders.put("APP_KEY","releaseKey")
buildConfigField('String','HOST','"https://www.release.com"')
}
debug {
minifyEnabled false
buildConfigField('String','HOST','"https://www.debug.com"')
}
local {}
}
flavorDimensions "channel", "type"
productFlavors {
xiaomi {
dimension "channel"
manifestPlaceholders.put("CHANNEL_NAME","xiaomi")
}
oppo {
dimension "channel"
manifestPlaceholders.put("CHANNEL_NAME","oppo")
}
stantard {
dimension "type"
buildConfigField 'boolean','isPro','false'
}
pro {
dimension "type"
buildConfigField 'boolean','isPro','true'
}
}
定义的这些字段,会以常量的形式添加到编译后生成的BuildConfig类中,在代码中就可以通过BuildConfig使用到这些字段
// type为pro,buildType为debug生成的
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.example.myapplication";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "oppoPro";
public static final String FLAVOR_channel = "oppo";
public static final String FLAVOR_type = "pro";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0.1";
// Field from default config.
public static final int API_VERSION = 1;
// Field from build type: debug
public static final String HOST = "https://www.debug.com";
// Field from product flavor: pro
public static final boolean isPro = true;
}
//type为stantard,buidType为release生成的
public final class BuildConfig {
public static final boolean DEBUG = false;
public static final String APPLICATION_ID = "com.example.myapplication";
public static final String BUILD_TYPE = "release";
public static final String FLAVOR = "oppoStantard";
public static final String FLAVOR_channel = "oppo";
public static final String FLAVOR_type = "stantard";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0.1";
// Field from default config.
public static final int API_VERSION = 1;
// Field from build type: release
public static final String HOST = "https://www.release.com";
// Field from product flavor: stantard
public static final boolean isPro = false;
}
resValue
resValue也是一个函数,它的作用跟buildConfig类似,根据不同的编译版版本输出不同的属性值,但它主要作用于res/values文件夹下定义的资源取值,例如字符串,颜色,尺寸等,可以在defaultConfig,buildTypes以及productFlavors中使用
/**
* type为字段类型,例如string,dimen,color,与资源文件中定义的类型一致
* name位字段名称
* value为字段取值
*/
public void resValue(String type,String name, String value)
defaultConfig {
manifestPlaceholders = [APP_KEY: "key123456", APP_SECRET: "secret"]
buildConfigField 'int','API_VERSION','1'
resValue 'color','gradle_color','#00FF00'
resValue 'dimen','test_dimen',"10dp"
}
buildTypes {
release {
resValue 'string','app_name',"releaseApp"
}
debug {
resValue 'string','app_name',"debugApp"
resValue 'dimen','test_dimen',"16dp"
resValue 'color','gradle_color','#FF0000'
}
}
定义的这些字段都会输出在编译之后生成的gradleResValues文件中
在代码中使用这些资源变量
// 布局文件使用color和dimen
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gradle_color"
android:layout_margin="@dimen/test_dimen"/>
// AndroidManifest文件使用string
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.MyApplication">
</application>
不难看出resValue,BuildConfig,manifestPlaceHolders都是跟上面的Variant有关系,作用很简单,就是针对不同版本的apk,进行差异化配置
修改输出的APK名字
有时候,我们需要根据自定的规则命名apk,这种可以通过applicationVariants属性去指定,具体用法可以看下注释
android {
// 遍历applicationVariants拿到的是每一个变体variant
applicationVariants.all { variant ->
// 每一个变体有一个outputs属性,代表该变体的所有输出,也是一个集合,可能不仅仅包含apk文件
variant.outputs.all { output ->
if (output.outputFile != null || output.outputFile.name.endWith('.apk')) {
def info = ''
variant.productFlavors.each {
info = info + it.name + '_'
}
// 为outputFileName属性重新赋值,就自定义了对应apk的名字
output.outputFileName = "$info${variant.buildType.name}_${buildTime()}.apk"
}
}
}
}
// 获取当前日期
def buildTime() {
return new Date().format("yyyyMMdd")
}
输出的apk就是按照编译类型+时间日期组成的
资源文件分包
在Android开发中,随着长时间迭代,项目会越来越大,资源文件也越来越多,一般我们的资源文件都放在同一个res目录下,管理起来就很混乱,不过现在可以借助gradle的sourceSets,将这些资源文件按照一定的业务逻辑进行分包处理
首页在main目录下按照业务逻辑拆成几个res相关目录
然后在build.gradle的android闭包中通过sourceSets把这几个文件夹的路径配置上,这样就实现了资源分包,可以方便后续资源文件维护
android{
sourceSets {
main {
res.srcDirs (
'src/main/res',
'src/main/res_common',
'src/main/res_home'
)
}
}
}
gradle模块化
gradle模块其实在前面系列文章中讲ext属性的时候有提到过,模块化无非就是抽象封装,提高代码的复用性,便于维护
提取公共属性
例如可以提取一个config.gradle文件,用于声明一些在各个build.gradle文件中都会用到的一些参数
// config.gradle文件
ext {
android = [
compileVersion: 31,
buildVersion : "30.0.3"
]
version = [
appcompat:'1.2.0',
constraintlayout:'2.0.1'
]
dependenciesMap = [
appcompat:"androidx.appcompat:appcompat:${version.appcompat}",
constraintlayout:"androidx.constraintlayout:constraintlayout:${version.constraintlayout}"
]
}
// 在module的build.gradle中引入使用
apply from: rootProject.file('config.gradle')
android {
compileSdkVersion android.compileVersion
buildToolsVersion android.buildVersion
}
提取功能脚本
我们一个项目会有多个module,每个module会输出aar,这些aar需要上传到maven上,我们就可以写一个upload.gradle,其他需要上传的模块引入就可以实现上传maven的功能
// upload.gradle
apply plugin: 'maven'
uploadArchives {
repositories.mavenDeployer {
repository(url: uri('../repo')) // 本地仓库路径
pom.groupId = GROUP_ID// 唯一标识(通常为模块包名,也可以任意)
pom.artifactId = ARTIFACT_ID// 项目名称(通常为类库模块名称,也可以任意)
pom.version = VERSION// 版本号
}
}
// 在module下也增加一个gradle.properties,用于配置区分不同aar的groupId,version等信息
GROUP_ID = com.tu.example
ARTIFACT_ID = library2
VERSION = 1.0.0
// 在具体module的build.gradle中引入
apply from:rootProject.file('upload.gradle')
以上配置之后,就会在每个moudule的task中,增加uploadArchives的Task
提取base基础脚本
如果项目有很多个module,且module有很多的共性,则可以考虑类似java,kotlin开发中,抽象出一个base基类,抽象出一个base.gradle
apply plugin:'com.android.library'
apply plugin: 'kotlin-android'
apply from:rootProject.file('config.gradle')
// 只作演示,抽离了部分属性,实际项目中,所有变量值都可抽离到config.gradle中统一管理
android {
compileSdkVersion android.compileVersion
buildToolsVersion android.buildVersion
defaultConfig {
minSdkVersion 21
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
}
然后module下的gradle脚本引入,这里面的内容都是基于base.gradle脚本中的内容进行添加
apply from:rootProject.file('base.gradle')
// 同样可添加自己特有的依赖配置等
dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
}