本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
1、Gradle生命周期
Gradle的生命周期也是一个十分重要的概念,当你了解它之后,就会理解许多事,也能在生命周期的各个阶段做一些切面处理的「黑科技」。
1.1、三个阶段
Gradle分三个阶段评价和运行构建,分别是 Initialization (初始化)
、Configuration (装备)
和 Execution (履行)
,且任何的构建使命都会履行这个三个阶段。
- 在 Initialization (初始化) 阶段,Gradle会决议构建中包括哪些项目,并会为每个项目创立Project实例。为了决议构建中会包括哪些项目,Gradle首先会寻觅settings.gradle来决议此次为单项目构建仍是多项目构建,单项目便是module,多项目即project+app+module(1+n)。
- 在 Configuration (装备) 阶段,Gradle会评价构建项目中包括的一切构建脚本,随后运用插件、运用DSL装备构建,并在最终注册Task,一起慵懒注册它们的输入,由于并不一定会履行。
- 最终,在 Execution (履行) 阶段,Gradle会履行构建所需的Task调集。
1.2、生命周期的实质
生命周期的实质是在各个阶段把使命(Task)组合起来,然后依照咱们的目的去构建项目。
Task
是Gradle构建的核心,其模型为有向无环图(DAG)。Task之间是有依靠的,Gradle会在构建期间(装备阶段)来生成依靠联系图,也便是Task调集。
这个Task调集的由来便是由上述的三个阶段组成,首先是初始化阶段,要明确哪些项目参加构建,然后装备阶段去解析一切参加构建项目的装备,这其中就包括注册Task,项目的装备决议了Task的履行顺序,比方某个子项目里有一个自定义的Task依靠了某个自带Task去做某些工作,那这个自定义的Task也是要加到调集里的,最终是履行阶段,顺次履行调集里边的Task去构建apk。
所以反历来推,一个apk是由许多文件组成的,这些文件是由Tasks履行的输入输出和merge组合的,而要详细履行哪些Task,即要打出什么样的包,是由生命周期的三个阶段决议的。
DAG图例:
2、Initialization (初始化)
在 Initialization (初始化) 阶段,Gradle会决议构建中包括哪些项目,并会为每个项目创立Project实例。为了决议构建中会包括哪些项目,Gradle首先会寻觅settings.gradle
来决议此次为单项目构建仍是多项目构建。
2.1、settings.gradle
2.1.1、Settings
前文中咱们介绍到build.gradle里边的装备和办法调用托付的是Project
方针,同样是构建脚本的settings.gradle里边的装备和办法调用托付的是Settings
方针。
在Gradle构建时会创立一个Settings实例,并依据它履行设置文件。Settings实例和settings.gradle文件是一对一的对应联系。
Settings:声明实例化和装备参加构建Project实例的层次结构所需的装备。
2.1.2、项目办理
Gradle支持单项目或多项目构建:
- 单项目构建,settings.gradle文件是可选的;
- 多项目构建,settings.gradle文件是必需的,且必须位于项目的根目录下;
多项目构建的settings.gradle
文件:
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "GradleX"
include ':app'
include ':lib'
目录:
.
├── app
│ ...
│ └── build.gradle
├── lib
│ ...
│ └── build.gradle
└── settings.gradle
核心是include
,表明给指定的项目添加到构建中,它能够指向咱们项目包括的module路径,也能够指向硬盘中子项目的肯定路径,这在项目aar和源码切换的时分十分方便,也是编译提速的重要手段之一。
2.1.3、插件办理
settings.gradle除了办理项目之外,另一个比较重要的便是办理插件(Plugin),即pluginManagement
。
2.1.3.1、插件库房
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
在pluginManagement中,repositories
指定了插件所需求的下载库房地址。假如自定义的插件发布在私有库房,就需求在这里加上私有库房的地址才能够找到你的插件。
2.1.3.2、插件替换
pluginManagement装备是由PluginManagementSpec
接口类解析的,其下有5个办法:
includeBuild办法需求在7.0版别之后才可用。
咱们首要用到的是resolutionStrategy
:
@HasInternalProtocol
public interface PluginResolutionStrategy {
/**
* Adds an action that is executed for each plugin that is resolved.
* The {@link PluginResolveDetails} parameter contains information about
* the plugin that was requested and allows the rule to modify which plugin
* will actually be resolved.
*/
void eachPlugin(Action<? super PluginResolveDetails> rule);
}
PluginResolutionStrategy允许在PluginRequest之前对其进行修改,并有唯一回调eachPlugin,eachPlugin的参数类型是PluginResolveDetails。
PluginResolveDetails:
- getRequested:获取恳求的插件,返回PluginRequest方针,包括id、version、module信息;
- useModule:设置插件的模块;
- useVersion:设置插件的版别;
- getTarget:恳求的方针插件;
插件替换首要用到的便是useModule
办法:
pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == "org.gradle.sample") {
useModule("com.yechaoa.plugin:gradlex:1.0")
}
}
}
}
2.1.3.3、插件版别
插件版别首要用到的是useVersion
办法:
pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.yechaoa.plugin") {
useVersion("2.0")
}
}
}
}
设置过版别后, 在一切的build script中经过 plugins { } 引入插件则无需再次指定版别。
2.2、怎么寻觅settings.gradle
前面现已介绍了settings.gradle的重要性,那Gradle在构建时是怎么寻觅settings.gradle文件的呢?
- 首先会在项目的根目录下找settings.gradle文件,假如没找到,则作为单项目构建。
- 假如找到了,会再次校验include装备的合法性,不合法,则继续作为单项目构建,合法,则作为多项目构建。
3、Configuration (装备)
在 Configuration (装备) 阶段,Gradle会评价构建项目中包括的一切构建脚本,随后运用插件、运用DSL装备构建,并在最终注册Task,一起慵懒注册它们的输入,由于并不一定会履行。
注意:不管恳求履行哪个Task,装备阶段都会履行。所以为了坚持构建简洁高效,要避免在装备阶段履行任何耗时操作,相似android里边的onDraw办法。
简单的说,装备阶段便是创立Projec方针,履行咱们的build.gradle文件,依据代码创立对应的Task依靠联系图。
3.1、Project
Gradle构建时,会依据Settings方针解析出来的项目结构为每个项目都创立一个Project
方针,Project方针和build.gradle文件之间存在一对一的联系。
在Gradle生成Task依靠联系图之前,Project方针还做了几件事:
- 引入插件
- 装备特点
- 编译依靠
3.1.1、引入插件
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
plugins是Project方针的一个办法,用于设置当前模块所运用的插件。
3.1.2、装备特点
android {
namespace 'com.yechaoa.gradlex'
compileSdk 32
defaultConfig {
applicationId "com.yechaoa.gradlex"
minSdk 23
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// ...
}
前文咱们剖析过android { } 装备的源码,android { } 装备实践是 id 'com.android.application'
插件的DSL
装备,也便是说咱们在build.gradle中一切的装备其实都是经过DSL对插件的装备,这些装备会影响插件的履行,然后影响整个构建流程。
3.1.3、编译依靠
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
// ...
}
dependencies { } 里边除了官方库之外,咱们还经常在里边添加所需求的三方库,比方okhttp、glide等等。
模块自有的三方依靠能够直接在build.gradle中添加库房下载地址:
repositories {
mavenCentral()
// other url
}
等同于7.0之前的subprojects { },settings.gradle中的dependencyResolutionManagement>repositories等同于7.0之前的allprojects { } 。
前文【Gradle-2】一文搞懂Gradle装备中漏了一点dependencyResolutionManagement里边的repositoriesMode
,即Gradle关于allprojects { } 和subprojects { }中的依靠解析战略。
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
repositoriesMode:
- PREFER_PROJECT:默认值,优先运用build.gradle中的repositories { },疏忽settings.gradle中的repositories { } ;
- PREFER_SETTINGS:优先settings.gradle中的repositories { } ,疏忽build.gradle中的repositories { };
- FAIL_ON_PROJECT_REPOS:这个厉害了,表明在build.gradle中声明的repositories { }会导致编译过错;
假如只有app模块,能够把库房地址都写在dependencyResolutionManagement>repositories里边,假如有多个模块,且依靠不同很大,仍是主张分隔写,毕竟从库房找依靠也是耗时的,虽然并不是编译痛点…(可疏忽)
当然Project方针也不止干这些事,通用来讲是这样,但你也可能有一些额外的装备,比方发布publishing等。
4、Execution (履行)
在 Execution (履行) 阶段,Gradle会履行构建所需的Task调集。
其实这个阶段才是真实的编译打包,于Android而言,比方咱们常见的compileDebugJavaWithJavac、mergeDebugNativeLibs等等。
5、Hook生命周期
Gradle在生命周期的各个阶段供给了丰厚的回调,这对咱们做切面处理时十分有用。
5.1、Initialization Hook
初始化阶段Hook,即Hook Settings方针。当settings.gradle被评价(evaluate)结束,此刻现已有初始化过的Settings方针。
咱们能够经过gradle.settingsEvaluated
办法添加Hook,例如在settings.gradle中:
println("---Gradle:开端初始化了")
gradle.settingsEvaluated {
println("---Gradle:settingsEvaluated Settings方针评价结束")
}
此刻能够获取到Settings方针,上面咱们介绍到Settings方针首要是用来办理项目和插件的,此刻就能够做一些大局的操作,比方一切项目的添加某个插件。
gradle.settingsEvaluated履行完是gradle.projectsLoaded
:
gradle.projectsLoaded {
println("---Gradle:projectsLoaded 准备加载Project方针了")
}
projectsLoaded回调时现已依据settings.gradle创立了各个模块的Project方针,咱们能够引用Project方针然后设置一些hook:
gradle.allprojects{
beforeEvaluate {
println("---Gradle:Projec beforeEvaluate Project开端评价,方针是 = "+project.name)
}
afterEvaluate {
println("---Gradle:Projec afterEvaluate Project评价结束,方针是 = "+project.name)
}
}
但此刻还归于Initialization(初始化)阶段,还没到Configuration(装备)阶段,所以Project方针只包括了项目的基本信息,是拿不到build.gradle里边的装备信息的,所以此阶段可用方针是Settings,Gradle方针是任何阶段都可用的。
5.2、Configuration Hook
初始化履行完就进入Configuration(装备)阶段,在装备阶段就能够拿到一切参加构建的项目的装备,首先会履行root project下的build.gradle,然后是module project下的build.gradle。
此刻能够Hook Project方针的履行前和履行后,由于是Project方针,就不能写在settings.gradle里边了,要写在build.gradle里边:
project.beforeEvaluate {
println("---project:beforeEvaluate Project开端评价,方针是 = " + project.name)
}
project.afterEvaluate {
println("---project:afterEvaluate Project评价结束,方针是 = " + project.name)
}
经过履行日志发现project.beforeEvaluate办法并没有履行,是由于该hook点在履行到build.gradle的内容是现已走过了,所以不会收效。
project.afterEvaluate
回调履行,即表明Project方针evaluate结束,此刻能够获取到Project方针里边的装备信息了。由于这个阶段Project方针刚刚装备结束, 因此许多动态使命都是在这个阶段添加到构建中的。
一切Project方针evaluate结束之后,会回调gradle.projectsEvaluated
:
gradle.projectsEvaluated {
println("---Gradle:projectsEvaluated 一切Project方针评价结束")
}
至此,一次构建的一切方针都已创立结束,有操控总体的Gradle方针,和统筹参加模块的Settings方针,以及各个子模块的Project方针。
5.3、Execution Hook
Gradle会在Execution (履行) 阶段履行Task,咱们能够添加TaskExecutionListener
来Hook Task的履行:
gradle.addBuildListener(new TaskExecutionListener(){
@Override
void beforeExecute(Task task) {
println("---Gradle:Task beforeExecute---")
}
@Override
void afterExecute(Task task, TaskState state) {
println("---Gradle:Task afterExecute---")
}
})
7.3以前可用,7.3之后现已抛弃了,并且会导致编译报错,由于在装备缓存的情况下,为了确保不管是否敞开装备缓存都一致的API,只好干掉了…
Gradle为了编译提速献身了很多啊,开发适配这个肯定又要被吐槽…
Task是Gradle中最小的构建单元,Action是最小的履行单元。
能够添加TaskActionListener
来Hook Task Action的履行:
gradle.addBuildListener(new TaskActionListener(){
@Override
void beforeActions(Task task) {
println("---Gradle:Task beforeActions---")
}
@Override
void afterActions(Task task) {
println("---Gradle:Task afterActions---")
}
})
同TaskExecutionListener相同,也被干掉了,且编译报错。
@deprecated This type is not supported when configuration caching is enabled.
5.4、构建结束
当一切Task履行结束,也就意味着构建结束,会回调gradle.buildFinished
:
gradle.buildFinished {
println("---Gradle:buildFinished 构建结束了")
}
也是抛弃了,原因同上,只不过编译不报错了…
除了gradle.xxx这种方法添加hook点之外,还能够用gradle.addListener()
的方法,作用相同。
gradle.addListener(new BuildListener() {
@Override
void settingsEvaluated(Settings settings) {
}
@Override
void projectsLoaded(Gradle gradle) {
}
@Override
void projectsEvaluated(Gradle gradle) {
}
@Override
void buildFinished(BuildResult result) {
}
})
5.4.1、整体输出
看下整体输出成果,进一步体会Gradle的生命周期和构建流程。
Executing tasks: [:app:assembleDebug] in project /Users/yechao/AndroidStudioProjects/GradleX
---Gradle:开端初始化了
---Gradle:settingsEvaluated Settings方针评价结束
---Gradle:projectsLoaded 准备加载Project方针了
> Configure project :
---Gradle:Projec beforeEvaluate Project开端评价,方针是 = GradleX
---Gradle:Projec afterEvaluate Project评价结束,方针是 = GradleX
> Configure project :app
---Gradle:Projec beforeEvaluate Project开端评价,方针是 = app
---Gradle:Projec afterEvaluate Project评价结束,方针是 = app
---project:afterEvaluate Project评价结束,方针是 = app
---Gradle:projectsEvaluated 一切Project方针评价结束
> Task :app:createDebugVariantModel UP-TO-DATE
> Task :app:preBuild UP-TO-DATE
...
> Task :app:assembleDebug
---Gradle:buildFinished 构建结束了
BUILD SUCCESSFUL in 3s
33 actionable tasks: 12 executed, 21 up-to-date
Build Analyzer results available
5.4.2、怎么适配
那TaskActionListener和buildFinished抛弃之后用什么替代呢,Gradle供给了Build Service
的方法来替代。
Build Service可用于在履行使命时接纳事件。为此,请创立并注册一个完成OperationCompletionListener的构建服务。然后,您能够运用BuildEventsListenerRegistry服务上的办法开端接纳事件。
看起来愈加杂乱了…
What is that supposed to mean? What’s a BuildEventsListenerRegistry? How do I use ‘the methods’?
示例:
// build.gradle.kts
abstract class BuildListenerService :
BuildService<BuildListenerService.Params>,
org.gradle.tooling.events.OperationCompletionListener {
interface Params : BuildServiceParameters
override fun onFinish(event: org.gradle.tooling.events.FinishEvent) {
println("BuildListenerService got event $event")
}
}
val buildServiceListener = gradle.sharedServices.registerIfAbsent("buildServiceListener", BuildListenerService::class.java) { }
abstract class Services @Inject constructor(
val buildEventsListenerRegistry: BuildEventsListenerRegistry
)
val services = objects.newInstance(Services::class)
services.buildEventsListenerRegistry.onTaskCompletion(buildServiceListener)
输出:
> Task :service:vp:assemble UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :service:arc:processResources NO-SOURCE
> Task :service:ar:processResources UP-TO-DATE
> Task :service:ara:processResources UP-TO-DATE
BuildListenerService got event Task :service:vp:assemble UP-TO-DATE
BuildListenerService got event Task :assemble UP-TO-DATE
BuildListenerService got event Task :service:arc:processResources skipped
BuildListenerService got event Task :service:ar:processResources UP-TO-DATE
BuildListenerService got event Task :service:ara:processResources UP-TO-DATE
> Task :service:ti:kaptGenerateStubsKotlin UP-TO-DATE
BuildListenerService got event Task :service:ti:kaptGenerateStubsKotlin UP-TO-DATE
> Task :service:ac:kaptGenerateStubsKotlin UP-TO-DATE
BuildListenerService got event Task :service:ac:kaptGenerateStubsKotlin UP-TO-DATE
> Task :service:ti:kaptKotlin UP-TO-DATE
BuildListenerService got event Task :service:ti:kaptKotlin UP-TO-DATE
> Task :service:ti:compileKotlin NO-SOURCE
BuildListenerService got event Task :service:ti:compileKotlin skipped
> Task :service:ti:compileJava NO-SOURCE
BuildListenerService got event Task :service:ti:compileJava skipped
> Task :service:ti:processResources NO-SOURCE
还有一个外国友人的sample:BuildService + projectsEvaluated callback example
TaskExecutionListener > BuildEventsListenerRegistry:
@Incubating
public interface BuildEventsListenerRegistry {
/**
* Subscribes the given listener to the finish events for tasks, if not already subscribed. The listener receives a {@link org.gradle.tooling.events.task.TaskFinishEvent} as each task completes.
*
* <p>The events are delivered to the listener one at a time, so the implementation does not need to be thread-safe. Also, events are delivered to the listener concurrently with
* task execution and other work, so event handling does not block task execution. This means that a task finish event is delivered to the listener some time "soon" after the task
* has completed. The events contain timestamps to allow you collect timing information.
* </p>
*
* <p>The listener is automatically unsubscribed when the build finishes.</p>
*
* @param listener The listener to receive events. This must be a {@link org.gradle.api.services.BuildService} instance, see {@link org.gradle.api.services.BuildServiceRegistry}.
*/
void onTaskCompletion(Provider<? extends OperationCompletionListener> listener);
}
buildFinished > OperationCompletionListener:
public interface OperationCompletionListener {
/**
* Called when an operation completes.
*/
void onFinish(FinishEvent event);
}
6、最终
本文先是介绍了Gradle生命周期的三个阶段,以及这三个阶段干了什么事,核心是三个方针(Gradle、Settings、Project),最终针对这个三个阶段介绍了对应的Hook点,相信看完本文,你对Gradle的生命周期和构建流程有了进一步的认识,假如有用,别忘了三连啊喂~
7、Github
github.com/yechaoa/Gra…
8、相关文档
- Gradle Build Lifecycle
- Gradle 与 AGP 构建 API: 装备您的构建文件
- Gradle
- Gradle Settings
- PluginManagementSpec
- Gradle Project
- Dependency Management
- Mastering Gradle
- Gradle BuildListener
- Upgrading your build from Gradle 7.x to the latest
- Configuration cache
- Shared Build Services