本文为稀土技术社区首发签约文章,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图例:

【Gradle-4】Gradle的生命周期

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个办法:

【Gradle-4】Gradle的生命周期

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文件的呢?

  1. 首先会在项目的根目录下找settings.gradle文件,假如没找到,则作为单项目构建。
  2. 假如找到了,会再次校验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等等。

【Gradle-4】Gradle的生命周期

5、Hook生命周期

Gradle在生命周期的各个阶段供给了丰厚的回调,这对咱们做切面处理时十分有用。

【Gradle-4】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