Gradle
Groovy
-
类对象的访问
class Person { private def name } static void main(String[] args){ // 可通过参数名进行构造赋值 def person = new Person(name: "kuky") // 可通过 @ 来访问私有变量 println person.@name }
-
闭包,柯里化
// 闭包可作为参数传给方法 static def func(closure){ closure() } // 左柯里化和右柯里化只支持两个参数的闭包 // 如果多个参数的闭包需要通过基于索引柯里化进行处理 static void main(String[] args){ func { println 'test closure argument' } // 闭包 def content = { String str, int count-> str * count } // 左柯里化,给第一个参数设置默认值,并返回一个新的闭包 def multiContent = content.curry("test") // 右柯里化,给最后一个参数设置默认值,并返回一个新的闭包 def twiceContent = content.rcurry(2) println multiContent(2) == twiceContent("test") // true 计算的结果均为 "testtest" // 基于索引的柯里化 def multiCalucate = { double a, double b, double c, double e -> a + b + c + e} // 第一位为参数位置索引 index,从 index 位开始给参数按顺序赋值 def fixed = multiCalucate.ncurry(1, 2d, 3d) println multiCalucate(1d, 2d, 3d, 4d) == fixed(1d, 4d) // true 计算结果均为 10.0 }
-
拦截/注入
static void main(String[] args){ // 通过重写 metaClass.invokeMethod 进行拦截 // name 为方法名,Object arg 为方法带的参数 String.metaClass.invokeMethod = { String name, Object arg-> def method = delegate.metaClass.getMetaMethod(name) if (name == "toString" && method != null){ println "toString was interceptted" } } 'aaa'.toString() // toString was interceptted // 通过 metaClass 注入方法 String.metaClass.appendFunc = { int a-> println "an append func for String calss, a=$a" 'appendFunc end' } // an append func for String calss, a=1 // appendFunc end println 'aaa'.appendFunc(1) // 通过 Category 注入后,通过 use 即可使用 use(StringUtilExt){ println 1.isNotEmpty() println "1".isNotEmpty() } } // 通过 Category 注入方法 @Category(Object) class StringUtilExt { def isNotEmpty() { println 'isNotEmptyExt' if (this instanceof Integer) { println '数字类型' return true } println '非数字类型' return this != null && this.length() > 0 } }
Gradle
本文稿使用的 Gradle
环境为 7.3,不同版本之间会有 api 差异
Gradle
构建周期
Gradle
构建周期 分为三个阶段: Initialization
, Configuration
, Execution
- Initialization: 初始化阶段,支持单项目和多项目构,确定哪些项目将参与构建,并为项目创建
Project
实例 Configuration
: 配置阶段,分析每个build.gradle
文件,确定任务子集和任务的关系,并对任务做一些初始化工作Execution
: 执行阶段,根据配置阶段创建和配置的任务子集,执行任务
Settings
当有多个项目需要同时构建的时候,必须在根目录配置 settings.gradle
文件,在该文件中,配置了哪些项目进行多项目构建。除了配置多项目,该文件也可以配置一些 libraries
。该配置文件会在 initialization
阶段执行,读取该配置文件并生成 Settings
对象。
Gradle
通过 settings.gradle
文件来确定是单项目构建还是多项目构建,同时也允许单个子项目单独进行构建,那么当执行没有 settings.gradle
文件的项目时,会通过以下方式进行查找
- 查找父级目录是否存在
settings.gradle
文件 - 如果没有找到,则按照单项目进行构建
- 如果找到了,通过读取该文件,判断当前项目是否已经配置在多项目构建中,如果没有配置,则按照单项目构建,否则多项目构建
Gradle
会为每个参与构建的项目生成一个 Project
对象,每个对象的名称默认为顶层目录的名称,除了 rootProject
,每个 Project
都会有一个父 Project
,每个 Project
都可以有自己的子 Project
Task
Gradle Project
的本质是一个 Task
的集合,每个 Task
执行一些基本的工作,通过 TaskContainer.create()
构建 task
并添加到 project
,添加的时候可以指定 Task
的 type
例如指定 Zip
可执行文件压缩操作(也可通过自定义实现)。
// 通过 type 指定任务类型为 zip 压缩任务
// 执行任务后可以在 build/zips 目录下找到 outputs.zip 文件
// 如果目标文件不存在,构建后会提示 `> Task :zipFile NO-SOURCE`
task zipFile(type: Zip) {
from("$buildDir/outputs") // 目标文件
destinationDir(file("$buildDir/zips")) // 输出文件夹
archiveName("outputs.zip") // 输出文件名
}
一个 Task
又是又一串 Action
对象组成,当 Task
被执行的时候,每个 Action
依次执行,可以通过 doFirst
或者 doLast
向 Task
添加 Action
如果未配置 gradle
,在每个 gradle
项目下有个 gradlew
文件,可通过该文件进行 gradle
命令操作
- 使用
./gradlew tasks --all
查看全部的任务 - 使用
./gradlew xxx
执行 xxx 任务 - …
脚本构建基础
构建 task
创建任意空文件夹,在文件夹创建文件 build.gradle
并添加如下代码
tasks.register('count'){
doLast{
4.times { println it}
}
}
执行命令行 gradle -q count
,执行后文件夹会自动生成 .gradle
隐藏文件,控制台会输出 0 1 2 3
任务依赖
如果 Task A
的执行依赖于 Task B
的执行结果,可通过 dependsOn
处理依赖关系
tasks.register('hello'){
println 'Hello world'
}
tasks.register('intro'){
dependsOn hello
println 'I am gradle'
}
执行命令 gradle -q intro
,则会先执行 hello
任务再去执行 intro
用 defaultTasks
指定默认任务
可以通过 defaultTasks
指定默认执行任务,例如添加 defaultTasks 'count'
后执行命令 gradle -q
则会自动执行 count
任务。
在多任务构建中,每个子项目都可以有自己的 defaultTask
,如果该子项目没有 task
则默认使用父项目的
自定义 Task
自定义 Task
需要继承 DefaultTask
实现
abstract class BuildInfo extends DefaultTask {
// input 注解为输入参数
// 如果使用了 @Optinal 则表示该输入可选
@Input
abstract Property<String> getVersion()
// OutputFile 表示为输出文件
@OutputFile
abstract RegularFileProperty getOutputFile()
// TaskAction 表示任务的实际操作
@TaskAction
void setProperty() throws IOException{
def property = new Properties()
property.set("version", getVersion().get())
def outputFile = getOutputFile().getAsFile().get()
if (!outputFile.parentFile.exists()) outputFile.parentFile.mkdirs()
if (!outputFile.exists()) outputFile.createNewFile()
try(OutputStream output = new FileOutputStream(outputFile)){
property.store(output, null)
}
}
}
// 在 task 指定任务类型 type 为 BuildInfo
tasks.register("buildInfo", BuildInfo){
version = 'v1.0.0'
// buildDirectory.file 返回 Provider<RegularFile> 对象
outputFile = layout.buildDirectory.file('outputs/build-info.properties')
}
执行命令 gradle -q buildInfo
可以发现在对应的文件中写入了参数信息
使用 Gradle
扩展属性
使用 ext
闭包对任意对象属性进行扩展
-
对
project
进行使用ext
进行属性扩展,对所有子project
可见 -
一般在
root project
中进行ext
属性扩展,为子工程提供复用属性,通过rootProject
直接访问 -
任意对象都可以使用
ext
来添加属性: 使用闭包,在闭包中定义扩展属性。直接使用=
赋值,添加扩展属性。
ext {
springVersion = "3.1.0.RELEASE"
emailNotification = "build@master.org"
}
tasks.register('check'){
println springVersion
println emailNotification
}
命令行执行 gradle -q check
可看到控制台的输出
扩展属性也可以在根目录的 gradle.properties
文件中定义
// gradle.properties
CURRENT_VERSION=V1.0.0
// build.gradle
println CURRENT_VERSION
配置任意对象
// 定义一个 user 类
class User {
String name
int age
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
// 注册 task
tasks.register('userconfig') {
doLast {
// 通过 configure 对对象进行赋值
def user = configure(new User()) {
name = 'kuky'
age = 18
}
println user
}
}
// 读取 `user.gradle` 文件中的配置
tasks.register('userfile') {
dependsOn userconfig
doLast {
def user = new User()
apply from: 'user.gradle', to: user
println user
}
}
// 在 user.gradle 文件中配置属性
name = 'kuky'
age = 18
执行命令行 gradle -q userfile
则会在控制台输出同样结果
Gradle
插件
Gradle
插件 分为脚本插件和二进制插件(通过 Plugin
接口实现)
- 脚本插件
脚本插件的处理比较简单,通过 apply from:
即可
// 创建一个 listen.gradle 文件,用于做 gradle 的一些监听
// listen.gradle
allprojects {
afterEvaluate { Project project ->
println "project $project after evaluate "
}
}
gradle.afterProject { Project project ->
if (project.state.failure) {
println "evaluate project: $project failed"
} else {
println "evaluate project: $project succeed"
}
}
gradle.taskGraph.beforeTask { Task task ->
println "executing task: $task...."
}
gradle.taskGraph.afterTask { Task task, TaskState state ->
println state.failure ? "${task.name} failed" : "${task.name} done"
}
// 在 build.gradle 文件中引入该脚本文件
// build.gradle
apply from: 'listen.gradle' // apply from 引入脚本文件
tasks.register('ok')
tasks.register('broken') {
it.dependsOn(ok)
it.doLast { throw new RuntimeException("broken") }
}
执行命令行 gralde -q broken
可看到如下输出
- 二进制插件
二进制插件需要 Plugin
接口实现
// 定义一个 ListenPlugin 类,内容和 ScriptPlugin 类似
class ListenPlugin implements Plugin<Project> {
@Override
void apply(Project pro) {
pro.allprojects {
afterEvaluate {Project project ->
println "project $project after evaluate "
}
}
pro.gradle.afterProject { Project project ->
if (project.state.failure) {
println "evaluate project: $project failed"
} else {
println "evaluate project: $project succeed"
}
}
pro.gradle.taskGraph.beforeTask { Task task ->
println "executing task: $task...."
}
pro.gradle.taskGraph.afterTask { Task task, TaskState state ->
println state.failure ? "${task.name} failed" : "${task.name} done"
}
}
}
// 在 build.gradle 通过 apply plugin 引入二进制插件
// 替换 script plugin 的 apply from
apply plugin: ListenPlugin
// ...
执行命令行 gralde -q broken
可看到相同的输出
多项目构建
多任务构建包含一个 rootProject
和一个或多个 subProject
,必须在根目录配置 settings.gradle
文件
可以通过 gradle -q projects
来查看项目构建结构
假设 app
子项目是一个 java application
,并通过 build.gradle
来配置 mainClass
plugins{ id "application" }
applications{
mainClass = "com.kuky.Main"
}
public class Main {
public static void main(String[] args) {
System.out.println("Hello Multi-Gradle-Project");
}
}
执行命令行 gradle -q run
则会输出 Hello Multi-Gradle-Project
配置 subproject
只需要在 settings.build
中进行配置即可
rootProject.name = "GradleDemo"
// 通过 : 来定义项目路径,lib:libdoc 即为 lib 目录下的 libdoc 项目
include 'app', 'lib:libdoc', 'lib:libshare'
// 修改 libdoc 的项目名等,可通过 ProjectDescriptor 查看
// 获取某个 project 可以通过 `gradle -q projects` 得到的项目名进行获取
project(':lib:libdoc').name = 'doc'
settings.gradle
可以修改 gradle
项目的一些属性,具体可以参考 ProjectDescriptor
Gradle
依赖管理
声明存储库
Gradle
可以基于 Maven
、Ivy
或 flat
格式解析来自一个或多个存储库的依赖关系。
repositories
会转换成 RepositoryHandler 对象
repositories {
// 多个存储库的时候,如果前面的存储库没有对应的依赖,就会按顺序去存储库查找
// 引入 mavenCentral 和 google maven 存储库
mavenCentral()
google()
// 通过 url 引入存储库
maven {
// 优先在 url 中查找,如果没有找到则在 artifactUrls 备用地址中找
// artifactUrls 可多次配置
url "https://repo2.mycompany.com/maven2"
artifactUrls "https://repo.mycompany.com/jars"
// 存储库过滤
content {
includeGroup "myCompany" // 只引入仓库中 'myCompany' 组中的内容
excludeGroupByRegex "my\\.company.*" // 包含以 'myCompany' 开头的所有内容
}
}
// 当前项目下的依赖目录,也可声明多个 libs 目录
flatDir{ dirs 'libs' }
flatDir{ dirs 'lib1', 'lib2'}
// Gradle 可以使用本地 Maven 存储库中可用的依赖项
// Gradle 会将已解析的依赖项存储在自己的缓存中
// 推荐在如下情况使用 mavenLocal `https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:case-for-maven-local`
mavenLocal()
// 引入 ivy 存储库
ivy {
// 最简单的引入只需要定义 url 即可
url "https://repo2.mycompany.com/maven2"
// 配置一些账号信息等
credentials{
username "username"
password "passwod"
}
}
}
Gradle
依赖传递
项目之间的依赖传递关系可通过命令行 ./gradlew app:dependencies --configuration releaseRuntimeClasspath
查看
xx.xx.xx(*)
表示已经有了xx.xx.xx
依赖,不再重复依赖xx.xx.xx -> xxx.xxx.xxx
表示xx.xx.xx
被xxx.xxx.xxx
代替xx.xx.xx -> xxx.xxx.xxx(*)
表示xx.xx.xx
被xxx.xxx.xxx
代替,并且已经有依赖不再重复
移除冲突依赖
比如在项目中需要引入 com.google.android.material:material:1.4.0
依赖库,通过命令可以查看到依赖树(部分截图)
可以在依赖树看到子依赖都有依赖 androidx.annotation:annotation
,如果说版本不一致肯定会导致依赖冲突,可通过如下解决
dependencies {
implementation ("com.google.android.material:material:1.4.0"){
// 移除对应的子依赖
exclude group:'androidx.annotation', module: 'annotation'
}
// 指定具体版本的依赖
implementation("androidx.annotation:annotation:1.0.0")
}
强制指定版本
// 强制指定版本可通过如下处理
configurations.all {
resolutionStrategy {
force 'androidx.annotation:annotation:1.0.0'
}
}
dependencies{
//....
}
依赖约束力度
-
required
依赖某个版本,但如果别的依赖提供了更高的版本则优先用更高的版本,默认 -
strictly
严格限制某个版本,常用于依赖版本降级
dependencies {
// rxandroid 2.1.1 间接依赖了 rxjava:2.2.6
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
// 严格约束 rxjava 版本为 2.2.0
// 约束版本也可使用区间 [2.0, 2.2.0]!!
implementation("io.reactivex.rxjava2:rxjava:2.2.0!!")
}
使用 Version Catalog
实现依赖统一管理
在 7.x
之前的版本,如果多个模块需要实现同个版本的依赖管理,一般会通过 buildSrc
或者 composite build
来实现,从 7.x
开始,Gradle
引入了 version catalog
,在 7.4
的版本正式稳定使用,如果需要在之前的版本使用,需要通过 settings.gradle
中通过 enableFeaturePreview("VERSION_CATALOGS")
开启。
-
在
settings.gradle
中配置version catalog
// settings.gradle 文件 dependencyResolutionManagement{ // 在 versionCatalogs.libs 闭包中定义 versionCatalogs{ libs{ // 使用 version 函数定义一个版本号,参数分别为 别名,版本号 version("appcompat", "1.4.1") version("compose", "1.2.0-alpha07") // 使用 library 函数定义一个依赖,参数分别为 // 参数 1 为依赖别名, 定义别名的时候,如果通过 -,_ 分割多个字符,依赖引入的时候需要通过 . 来替换分隔符 // 例如 `android-appcompat` 在依赖的时候通过 libs.android.appcompat 引入 // 参数 2 为依赖 // versionRef 函数为对应的版本号 library("android-appcompat", "androidx.appcompat:appcompat").versionRef("appcompat") library("compose-ui", "androidx.compose.ui:ui").versionRef("compose") library("compose-foundation", "androidx.compose.foundation:foundation").versionRef("compose") library("compose-compiler", "androidx.compose.compiler:compiler").versionRef("compose") // bundle 可以讲多个依赖组合成一个整体,通过 bundle 即可依赖 // bundle 函数参数分别为别名和依赖列表 // 在依赖侧,直接通过 `implementation(libs.bundles.compose)` 即可依赖 bundle("compose", ["compose-ui", "compose-foundation", "compose-compiler"]) // plugin 用来统一管理插件版本 // 参数分别为别名和插件 id,通过 version 函数可以指定插件的版本 // 在使用插件侧,需要通过 alias() 函数引入插件 plugin("kotlin", "org.jetbrains.kotlin.android").version("1.6.20") } // catalog 允许多个 libs // 引入依赖的时候通过指定 libs 的名即可 // testImplementation(testLibs.junit) testLibs{ library("junit", "junit:junit:4.13.2") } } } // 根目录 build.gradle 文件 plugins{ // 通过 alias 引入定义的 kotlin 插件 // apply false 表示不立刻解析该插件 alias(libs.plugins.kotlin) apply false } // app/build.gradle 文件 plugins{ alias(libs.plugins.lotlin) } dependencies{ testImplementation(testLibs.junit) // 引入 testLibs 下的 junit implementation(libs.android.appcompat) // 引入 appcompat 依赖 implementation(libs.bundles.compose) // 引入 compose 组合依赖 }
-
在
toml
文件中配置version catalog
# libs.versions.toml # 除了在 `settings.gradle` 文件中配置 version catalog # 还可以通过 toml 文件进行配置 [versions] # 在这里指定版本号 compileSdk = 31 # 指定 compileSdk appcompat = "1.4.1" compose = "1.2.0-alpha07" [libraries] # 在这里指定依赖的版本 android-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose"} compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compose" } [bundles] # 在这里指定依赖的集合 compose = ["compose-ui", "compose-foundation", "compose-compiler"] [plugins] # 在这里指定插件 kotlin = { id = "org.jetbrains.kotlin.android", version = "1.6.20" }
处理完
toml
文件后,需要在settings.gradle
中将toml
文件指定给version catalog
// settings.gradle 文件 versionCatalogs { libs { // 根据 toml 具体位置进行指定 // 配置完成后就可以同上述一致进行依赖 from(files("${rootDir.getAbsolutePath()}/libs.versions.toml")) } testLibs{ // 指定测试依赖 from(files("${rootDir.getAbsolutePath()}/testLibs.versions.toml")) } } // build.gradle 文件 // 获取 versions 中的 compileSdk android{ compileSdk libs.versions.compileSdk.get().toInteger() }
AGP(Android Gradle Plugin)
本文稿环境为 AGP 7.1.2
,不同版本之间会有 api 差异
安利查看 AGP
源码的一个方法,直接在项目依赖对应 AGP
版本即可,不需要手动下载源码
implementation "com.android.tools.build:gradle:7.1.2"
新建 Android
项目,项目根目录包含一个 setting.gradle
和 build.gradle
文件
settings.gradle
配置了多项目运行的一些基本配置,
pluginManagement
用于管理需要用到的插件,
dependencyResolutionManagement
用于管理需要用到的远程依赖,
以及 rootProject
和 subProject
,新建项目一般只有 app
一个 subProject
,
RepositoriesMode
用于配置加载策略,具体的可以查看 RepositoriesMode
build.gradle
通过 plugins
配置了用到的插件,通过 apply
使用,如果指定值为 false
表示该插件不会被立即解析使用,还注册了一个 clean task
,Delete
通过实现 DeleteSpec
用于删除指定的文件,delete
为指定的路径,所以该 task
用于删除生成的 build
目录,可以在运行 app
后,执行 ./gradlew -q clean
查看,生成的 build
文件被删除
app/build.gradle
配置了 Android Application
的配置信息,通过 'com.android.application'
插件引入了 android {}
闭包,映射的类为 BaseAppModuleExtension
, BaseAppModuleExtension
通过一系列的继承和实现,顶级父类为 BaseExtension
, BaseExtension
为全部 Android Plugin
的父类,包括
ApplicationExtension
- 用于创建android application
LibraryExtension
- 用于创建android library
TestExtension
- 用于创建测试DynamicFeatureExtension
- 用于创建一些动态属性
BaseExtension
实现了接口 AndroidConfig
(旧版本的属性都在这,目前该接口已经过时,配置的额属性已迁移到 BaseExtension
)
defaultConfig
defaultConfig
映射为类 DefaultConfig
,DefaultConfig
通过继承 BaseFlavor
,实现 BaseFlavor
, VariantDimension
等接口及其子接口,来扩展属性
-
applicationId
&&applicationIdSuffix
配置应用最终的包名,一般suffix
被省略,可通过applicationIdSuffix
来区分不同情况下的包名,例如buildTypes { release { applicationId 'com.xxx.app' // 构建应用的包名为 com.xxx.app } debug { initWith release applicationIdSuffix '.debug' // 构建应用的包名为 com.xxx.app.debug } }
同样
versionNameSuffix
和versionName
是一样的效果 -
externalNativeBuild
用于配置cpp/c
的信息,具体可以参考externalNativeBuild
-
dimension
用于多渠道包的维度,在defaultConfig
意义不大 -
manifestPlaceholders
一般在多渠道配置不同的AndroidManifest.xml
属性,例如给不同的包配置不同图标buidTypes { release { manifestPlaceholders = [icon: '@mipmap/app_icon'] } debug { manifestPlaceholders = [icon: '@mipmap/app_debug_icon'] } }
配置
manifestPlaceholders
在AndroidManifest.xml
种通过${key}
进行调用<application icon="${icon}"> <activity/> </application>
-
multiDexEnabled
用于开启MultiDex
-
multiDexKeepProguard
开启分包的时候将配置的类打进主包defaultConfig { // multidex-keep-rules.pro 为配置文件 multiDexKeepProguard file('multidex-keep-rules.pro') }
-
ndk
主要用于ABI
的配置,同时也可以配置一些cFlags
,moduleName
等等defaultConfig { ndk { abiFilters "armeabi-v7a", "arm64-v8a" } }
-
javaCompileOptions
用于配置编译java
时的一些参数defaultConfig { javaCompileOptions { annotationProcessorOptions { classNames 'xxxx' // 编译类,是个 vararg String arguments = ["arg1": "test"] // 编译时的参数 } } }
-
proguardFiles
&&testProguardFiles
混淆规则文件,AGP
自带了两个混淆规则proguard-android.txt
和proguard-android-optimize.txt
文件,可通过getDefaultProguardFile(String)
方法获取完整路径 -
signingConfig
签名属性,一般在多渠道进行配置 -
resConfig
/resourceConfigurations
需要保留的资源defaultConfig { // resConfig, resConfigs 该版本已过时。使用 resourceConfigurations 代替 resourceConfigurations = ['zh', 'en'] // apk 只保留中文和英文的资源 }
-
vectorDrawables
需要minSdk < 21
的时候使用defaultConfig { vectorDrawables{ // 当 sdk < 21 时生成对应密度下的 png 图片 generatedDensities 'mhdpi', 'xhdpi' // true 会使 generatedDensities 属性失效,加入 svg 兼容包,不生成 png useSupportLibrary true } }
-
buildConfigField(type: String, name: String, value: String)
方法用于在BuildConfig
类中生成对应的参数defaultConfig { buildConfigField("String", "appName", "'xxxApp'") // 生成 String 类型的时候,需要用 '' 再次包裹 buildConfigField("int", "versionCode", "1") }
在类中可直接通过
BuildConfig.appName
和BuildConfig.versionCode
直接调用 -
resValue(type: String, name: String, value: String)
方法用于创建res
属性defaultConfig { resValue('string', 'age', '12 years') // 生成 R.string.age 属性,可直接调用 resValue('color', 'black', '#000') // 生成 R.color.black }
buildTypes
buildTypes
映射类为 BuildType
, BuildType
通过继承 AbstractBuildType
, 实现 BuildType
, VariantDimension
等接口及其一系列子接口扩展属性,所以其配置属性类似 defaultConfig
主要通过 VariantDimension
及其子接口确定,buildTypes
用于配置不同类型包的一些不同属性
// buildTypes 内置 release 和 debug 包,通过 `CommonExtensionImpl` 源码可以得知,
// release 和 debug 通过方法 getByName(String) 获取
buildTypes {
release { // 也可以写成 getByName("release"){}
// 开启代码压缩, 该属性由 AbstractBuildType 继承而来
minifyEnabled true
shrinkResources true // shrinkResources 需要在 minifyEnabled 开启的情况下开启
// 以下属性同 defaultConfig,可用于配置不同类型包的特殊参数
manifestPlaceholders = [icon: "@mipmap/ic_launcher"]
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField('String', 'type', '"Release"')
}
debug { // 也可以写成 getByName("debug"){}
// debuggable 属性由 AbstractBuildType 继承而来,在 debug 模式下默认为开启
debuggable true
jniDebuggable true
renderscriptDebuggable true
//
applicationIdSuffix '.debug'
manifestPlaceholders = [icon: "@mipmap/ava"]
buildConfigField('String', 'type', '"Debug"')
}
custom { // 自定义属性包
initWith debug // initWith 方法用于从别的类型下复制完全一样的属性
//
manifestPlaceholders = [icon: "@mipmap/ic_launcher"]
applicationIdSuffix '.debugcustom'
buildConfigField('String', 'type', '"Custom"')
}
}
-
minifyEnabled && shrinkResources
用于压缩代码和资源文件 -
debuggable && jniDebuggable && renderscriptDebuggable
用于配置是否可断点调试,debug
模式下默认开启 -
initWith
方法为复制相同属性来初始化当前的模式以上的方法和参数通过
AbstractBuildType
继承而来,别的属性和defaultConfig
类似,通过VariantDimension
接口及其子接口实现而来
构建不同变体可通过 Android Studio Build Variants
处理
signingConfigs
signingConfigs
映射类为 SigningConfig
, 通过继承 DefaultSigningConfig
, 实现SigningConfig
接口, 用于配置打包签名属性
storeFile
指定签名文件,如果使用相对路径,是相对于设置该属性的gradle
文件位置storePassword
签名文件密码keyAlias
签名的别名keyPassword
签名密码storeType
签名类型,不填时,默认为jks
类型isV1SigningEnabled
是否使用v1
签名,默认为true
, 如果设置为false
,会导致在7.0
以下无法正常安装isV2SigningEnabled
是否使用v2
类型的签名方案。默认为true
,需要7.0
以后支持
一般配置签名文件,官方建议通过在根目录创建 keystore.properties
文件来存放签名配置,再通过 gradle
引入
productFlavors && flavorDimensions
productFlavors
映射类为 ProductFlavor
,ProductFlavor
通过继承 BaseFlavor
,实现 ProductFlavor
, VariantDimension
等接口及其子接口,来扩展属性
产品风味类似构建不同类型包,在 productFlavors
中也可以配置和 defaultConfig
一样的属性,因为 defaultConfig
也属于 ProductFlavor
flavorDimensions
为字符串列表,用于配置 productFlavors
的 dimension
,所有的 productFlavor
都必须配置 dimension
属性(flavorDimensions
只有一个的情况下除外)
android {
// 旧版本的通过 flavorDimensions 'abm', 'xxx' 配置
// 当前版本该方法已过时,通过 add 方法添加即可
flavorDimensions.add("abm")
productFlavors {
demo {
dimension 'abm' // 单个 flavor 的情况下可以省略,会默认配置
applicationIdSuffix '.demo'
}
v2 {
// dimension 'abm' 可省略配置
applicationIdSuffix '.v2'
}
}
}
通过 Build Variants
可以看到,生成了不同 flavor
的变体
配置多个 flavorDimensions
同单个类似,但是必须在 flavor
指定 dimension
,多个 dimension
会有优先级,优先级按添加的顺序从高到低排列
android {
buildTypes{
debug {
applicationIdSuffix '.debug'
}
release {
applicationIdSuffix '.release'
}
custom {
initWith debug
applicationIdSuffix '.customdebug'
}
}
flavorDimensions.add("api") // 新增一个 api dimension,优先级会高于 abm
flavorDimensions.add("abm")
// agp 会按照优先级对 product 进行组合
// 最终变体的组合数量为 (api dimension 数量) * (abm dimension 数量) * (buildTypes 数量)
// 例如本例中,api dimension 数量为 2,abm dimension 数量 2, buildTypes 数量为 3,最终有 12 个变体
// 组合方式为 minV30v2Debug, minV30v2Release, minV30v2Custom ...
// 组合包名方式为,取最高等级 dimension 的 applicationId,按照 dimension 优先级组合 applicationIdSuffix,
// 再组合 buildTypes 的 applicationIdSuffix 得到最后的包名
// 例如 minV30DemoDebug 的最终包名为 `com.kuky.api.min30.demo.debug`
productFlavors {
demo {
applicationId 'com.kuky.abm'
dimension 'abm'
applicationIdSuffix '.demo'
}
v2 {
dimension 'abm'
applicationIdSuffix '.v2'
}
minV21 {
dimension 'api'
applicationIdSuffix '.min21'
versionNameSuffix '-min21v'
minSdk 21
}
minV30 {
applicationId 'com.kuky.api'
dimension 'api'
applicationIdSuffix '.min30'
minSdk 30
}
}
}
当依赖的 module
存在多种风味的情况下,嘿,那就好玩了,需要在 build
中使用 matchingFallbacks
指定对应的风味,matchingFallbacks
可传入 flavor
列表,编译的时候会按照顺序进行查找,如果不进行配置则无法编译成功。
// lib build.gradle
android {
flavorDimensions.add('api')
flavorDimensions.add('abm')
productFlavors {
free1 {
dimension 'api'
}
pro1 {
dimension 'api'
}
free {
dimension 'abm'
}
pro {
dimnsion 'abm'
}
}
}
// app build.gradle
productFlavors {
demo {
// 指定 lib 下的风味 dimension
matchingFallbacks = ['free', 'pro']
}
minV21 {
// 指定 lib 下的风味 dimension
matchingFallbacks = ['pro1', 'free1']
}
}
通过 productFlavors
可以配置多个变体,但是不是所有的变体都是我们需要的,那就需要通过过滤器 variantFilter
进行一些过滤操作
variantFilter
variantFilter
映射类为 VariantFilter
, 主要有以下的属性
ignore: Boolean
是否忽略当前变体defaultConfig: ProductFlavor
当前变体的defaultConfig
buildType: BuildType
当前变体的构建类型flavors: List<ProductFlavor>
当前变体的风味包name: String
当前变体名
android {
variantFilter {variant->
def flavorNames = variant.flavors*.name // *.name 获取 flavors 列表的所有 name 属性
if (flavorNames.contains("minV30") && flavorNames.contains("v2")) {
ignore = true // 忽略 minV30V2 开头的所有变体,那么在 `Build Variants` 中就没有这系列的变体了
}
}
}
sourceSets
sourceSets
映射到接口 AndroidSourceSet
, 用于配置 java
, aidl
, RenderScript
源文件以及一些资源文件
java
用于指定java
资源集kotlin
用于指定kotlin
资源集resources
用于指定java
资源文件manifest
用于指定AndroidManifest.xml
文件res
用于指定Android
资源文件assets
用于指定assets
目录aidl
用于指定aidl
目录jniLibs
用于指定依赖库文件,旧版本为jni
renderscript
用于指定Android
渲染脚本目录setRoot(String)
方法用于设置资源集的根目录
1 ~ 9
配置属性为 AndroidSourceDirectorySet
, 通过 srcDirs
来配置资源文件目录集合
// 多渠道包的情况下,可以指定不同的目录文件夹
sourceSet{
main { // main 为默认的
jniLibs.srcDirs = ['libs']
}
demo { // demo flavor 的配置, 不会覆盖 main 的属性
// 旧版本有 excludes, includes 属性,用于排除一些文件目录
// 当前版本源码未找到,可能是被移除?
java.srcDirs = [
'src/main/java',
'src/main/filters' // 该文件夹只有在 demo flavor 的渠道下生效
]
}
}
lintOptions
/ lint
lintOptions
属性已过时,被 lint
代替,lint
主要用于协助发现代码质量问题,通过 ./gradlew app:lint
启动 lint
检查,运行完毕后会生成 lint-result
文件,也可以指定渠道包进行分析 ./gradlew app:lintAnalyzeMin21V2Debug
lint
的配置可通过 官方文档 查看,常用的属性应该是 abortOnError
,发现错误的时候立即停止构建,构建 release
包的时候,该属性默认为打开,可能会遇到如下问题,可通过关闭解决
Lint found errors in the project; aborting build.
Fix the issues identified by lint, or add the following to your build script to proceed with errors:
android {
lintOptions { // lintOptions 已被 lint 代替
abortOnError false
}
}
splits
splits
映射为 Splits
类,主要用于 apk
拆分
abi: AbiSplit
根据架构进行拆分enbale
是否开启exclude
需要移除的架构,如果在defaultConfig
配置了ndk.abiFilters
则需要在这些范围内进行选择include
需要构建的架构,在设置include
之前建议通过reset()
方法重置包含目录isUniversalApk
是否包含全架构
density: DensitySplit
根据屏幕尺寸拆分enbale
是否开启exclude
需要移除的屏幕尺寸include
需要构建的尺寸compatibleScreens
指定与应用程序兼容的屏幕尺寸, 会在AndroidManifest
添加<compatible-screens>
节点
abiFilters
配置的架构属性densityFilters
配置的屏幕尺寸属性
splits {
abi {
enable true
exclude("x86", "x86_64")
}
density {
enable true
compatibleScreens "small", "large", "normal", "xlarge"
}
}
配置完成后打包查看属性,架构移除了 x86
, x86_64
并按照屏幕尺寸进行了分包,在 AndroidManifest
配置了对应的节点
externalNativeBuild
externalNativeBuild
映射类为 ExternalNativeBuild
实现接口 CoreExternalNativeBuild
和 ExternalNativeBuild
, 用于控制 native
的编译
cmake: Cmake
path
设置CMakeLists.txt
的路径buildStagingDirectory
配置native
构建后文件的存放路径version
设置Android
编译CMake
的版本
ndkBuild: NdkBuild
path
设置Android.mk
的路径buildStagingDirectory
配置native
构建后文件的存放路径
目前更多的项目会通过 cmake
来处理 native
编译
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
buildStagingDirectory "./outputs/cmake"
version "3.18.1"
}
}
aaptOptions
/ androidResources
aaptOptions
已过时,被 androidResources
代替,映射为接口 AndroidResources
-
additionalParameters
给aapt
执行时添加额外参数,可添加的参数可通过aapt --help
查看,或者文档androidResources { // 修改 manifest package 包名 additionalParameters "--rename-manifest-package", "com.test.gradle" }
-
failOnMissingConfigEntry
如果找不到配置条目,则强制aapt
返回错误 -
noCompress
添加不被压缩的文件扩展名,如果设置的为 ‘’ 则全部压缩都被禁止 -
ignoreAssetsPattern
忽略assets
下的文件,例如设置ignoreAssetsPattern '*.svg'
则忽略assets
下的svg
文件
adbOptions
/ installation
adbOptions
已过时,被 installation
代替,映射为接口 Installation
installOptions
用于增加执行adb install
的参数,可选类型如下-r: replace existing application
-t: allow test packages
-d: allow version code downgrade (debuggable packages only)
-p: partial application install (install-multiple only)
-g: grant all runtime permissions
timeOutInMs
设置adb
操作的超时时长
applicationVariants
applicationVariants
会返回当前项目包含的变体集合,通过 all
来遍历
import com.android.build.gradle.internal.api.ApplicationVariantImpl
import com.android.build.gradle.internal.api.ApkVariantOutputImpl
applicationVariants.all{/*ApplicationVariantImpl*/ variant->
// variant 的属性比较多,但是基本都是上述的属性,这边不重复
// outputs 为变体的输出列表,可通过修改 outputs 的内容来修改最终输出文件
variant.outputs.all{/*ApkVariantOutputImpl*/ output->
def date = new Date().format("yyyyMMddHHmmss")
// 修改 app versionName
output.versionNameOverride = "v${date}"
// 修改 app versionCode,年月日作为版本号
output.versionCodeOverride = Long.parseLong(date) / 10000
// 修改 apk 文件名
output.outputFileName = "${output.versionNameOverride}-${variant.buildType.name}-${date}.apk"
}
}
aar
打包发布
-
aar
可通过gradle
的task assemble
直接打包,高版本的Android Studio
在Sync
期间默认关闭了Gradle tasks
的生成,需要通过Preferences -> Experimental -> Do not build Gradle tasks during Gradle sync
关闭,再Sync
之后就可以看到将生成的
aar
文件放到libs
目录,可直接依赖 -
通过
maven-publish
插件来生成并发布,gradle 6.0
之前是通过maven
插件发布,不过这个已废弃plugins{ id "maven-publish" // 引入 maven-publish 插件 } // 因为 components 只能在 afterEvaluate 阶段生成 // 需要通过 components 来获取变体信息 afterEvaluate { publishing{ repositories{ // 指定 maven 地址为本地目录 // 也可以是私有服务器的地址,如果是私有服务器需要配置用户登录信息 maven{ url("${rootProject.projectDir.getAbsolutePath()}/repository") // 如果是私有仓库,需要配置认证信息 credentials{ username = "xxxxx" password = "xxxxx" } } } publications{ releaseLib(MavenPublication){ // 获取 release 变体,也可以指定成别的 flavour 的变体 from components.relese // 配置依赖信息 groupId = "com.kuky.library" artifactId = "mylibrary" version = "0.0.1" } } } } // settings.gradle dependencyResolutionManagement{ repositories{ // 指定依赖库的远程 url maven{ url("${rootProject.projectDir.getAbsolutePath()}/repository") } } } // build.gradle dependencies{ implementation "com.kuky.library:mylibrary:0.0.1" // 依赖发布的 aar }
publishing
映射类为PublishingExtension
,其属性就只有repositories
发布的仓库publications
配置需要发布内容的格式
publications
映射类为MavenPublication
,具体属性和方法可查看官方文档,官方文档中还提供了发布Jar
包 的过程,相比于发布
aar
,多了打Jar
包的task
,并将该task
指定到publications.artifact
属性,而且发布不会限制在afterEvaluate
阶段才可以生成
迁移kts
AGP
源码分析
AGP
源码分析可以参考这篇博客 🍵补齐Android技能树——从AGP构建过程到APK打包过程
项目地址
项目代码已上传 Github