【灵魂七问】深度探索 Gradle 自动化构建技术(五、Gradle 插件架构实现原理剖析 — 上(3)

  • 5、Finished
  • 四、关于 Gradle 中依赖实现的原理
  • 1、通过 MethodMissing 机制,间接地调用 DefaultDependencyHandler 的 add 方法去添加依赖。
  • 2、不同的依赖声明,其实是由不同的转换器进行转换的。
  • 3、Project 依赖的本质是 Artifacts 依赖,也即 产物依赖。
  • 4、什么是 Gradle 中的 Configuration?
  • 5、Task 是如何发布自己 Artifacts 的?
  • 五、AppPlugin 构建流程
  • 1、准备工作
  • 2、configureProject 配置项目
  • 3、configureExtension 配置 Extension
  • 4、TaskManager#createTasksBeforeEvaluate 创建不依赖 flavor 的 task
  • 5、BasePlugin#createAndroidTasks 创建构建 task
  • 六、assembleDebug 打包流程浅析
  • 1、Android 打包流程回顾
  • 2、assmableDebug 打包流程浅析
  • 七、重要 Task 实现源码分析
  • 1、资源处理相关 Task
  • 2、将 Class 文件打包成 Dex 文件的过程
  • 八、总结
  • 最后的最后

一、Gradle 插件实现架构概述

Android Gradle plugin 团队在 Android Gradle V3.2.0 之前一直是都是用 Java 编写的 Gradle 插件,在 V3.2.0 便采用了 Kotlin 进行大面积的重写。尽管 Groovy 语法简洁,且其闭包的写法非常灵活,但是 Android Studio 对 Groovy 的支持非常不友好,因此,目前写自定义的 Gradle 插件时我们还是尽量使用 Kotlin,这样能尽量避免编写插件时提示不够造成的坑

下面,我们就来看看 Gradle 插件的整体实现架构,如下图所示:

在最下层的是底层 Gradle 框架,它主要提供一些基础的服务,如 task 的依赖,有向无环图的构建等等

上面的则是 Google 编译工具团队的 Android Gradle plugin 框架,它主要是 在 Gradle 框架的基础上,创建了很多与 Android 项目打包有关的 task 及 artifacts(每一个 task 执行完成之后通常都会输出产物)

最上面的则是开发者自定义的 Plugin,关于自定义 Plugin 通常有两种使用套路,如下所示:

  • 1)、在 Android Gradle plugin 提供的 task 的基础上,插入一些自定义的 task
  • 2)、增加 Transform 进行编译时代码注入

二、了解 Android Gradle Plugin 的更新历史

下表列出了各个 Android Gradle 插件版本所需的 Gradle 版本。我们应该使用 Android Gradle Plugin 与 Gradle 这两者的最新版本以获得最佳的性能

插件版本所需的 Gradle 版本
1.0.0 - 1.1.32.2.1 - 2.3
1.2.0 - 1.3.12.2.1 - 2.9
1.5.02.2.1 - 2.13
2.0.0 - 2.1.22.10 - 2.13
2.1.3 - 2.2.32.14.1+
2.3.0+3.3+
3.0.0+4.1+
3.1.0+4.4+
3.2.0 - 3.2.14.6+
3.3.0 - 3.3.24.10.1+
3.4.0 - 3.4.15.1.1+
3.5.0+5.4.1-5.6.4

目前最新的 Android Gradle Plugin 版本为 V3.6.2,Gradle 版本为 V5.6.4。下面,我们了解下 Android Gradle Plugin 更新历史中比较重要的更新变化

1、Android Gradle Plugin V3.5.0(2019 年 8 月)

本次更新的重中之重是 提高项目的构建速度

2、Android Gradle Plugin V3.4.0(2019 年 4 月)

如果使用的是 Gradle 5.0 及更高版本,默认的 Gradle 守护进程内存堆大小会从 1 GB 降到 512 MB。这可能会导致构建性能降低。如果要替换此默认设置,请在项目的 gradle.properties 文件中指定 Gradle 守护进程堆大小

1)、新的 Lint 检查依赖项配置

增加了新的依赖项配置 lintPublish,并更改了原有 lintChecks 的行为,它们的作用分别如下所示:

  • lintChecks仅用于在本地构建项目时运行的 Lint 检查
  • lintPublish在已发布的 AAR 中启用 Lint 检查,这样使用此 AAR 的项目也会应用那些 Lint 检查

其示例代码如下所示:

dependencies {
// Executes lint checks from the ‘:lint’ project at build time.
lintChecks project(‘:lint’)
// Packages lint checks from the ‘:lintpublish’ in the published AAR.
lintPublish project(‘:lintpublish’)
}

2)、Android 免安装应用功能插件弃用警告

在之前的版本可以使用 com.android.feature 插件构建免安装应用,现在建议使用动态功能插件,这样便可以通过单个 Android App Bundle 发布安装版应用和免安装应用。

3)、R8 默认处于启用状态

R8 将 desugar(脱糖:将 .class 字节码转换为 .dex 字节码的过程)、压缩、混淆、优化和 dex 处理整合到了一个步骤中,从而显著提升了构建性能。R8 是在 Android Gradle Plugin V3.2.0 中引入的,对于使用插件 V3.4.0 及更高版本的应用和 Android 库项目来说,R8 已经默认处于启用状态

R8 引入之前的编译流程

R8 引入之后的编译流程

可以看到,R8 组合了 Proguard、D8 的功能。如果遇到因 R8 导致的编译失败的问题,可以配置以下代码停用 R8:

Disables R8 for Android Library modules only.

android.enableR8.libraries = false

Disables R8 for all modules.

android.enableR8 = false

3)、弃用 ndkCompile

此时使用 ndkBuild 编译原生库会收到构建错误。我们应该 使用 CMake 或 ndk-build 将 C 和 C++ 代码添加到项目中

3、Android Gradle Plugin V3.3.0(2019 年 1 月)

1)、为库项目更快地生成 R 类

在老版本中,Android Gradle 插件会为项目的每个依赖项生成一个 R.java 文件,然后将这些 R 类和应用的其他类一起编译。现在,插件会直接生成包含应用的已编译 R 类的 JAR,而不会先编译中间的 R.java 类。这不仅可以显著提升包含多个库子项目和依赖项的项目的编译性能,还可以加快在 Android Studio 中索引文件的速度

4、Android Gradle Plugin V3.2.0(2018 年 9 月)

新的代码压缩器 R8

R8 是一种执行代码压缩和混淆的新工具,替代了 ProGuard。我们只需将以下代码添加到项目的 gradle.properties 文件中,即可开始使用 R8 的预览版本:

android.enableR8 = true

使用 D8 进行 desugar 的功能现已默认处于启用状态

5、Android Gradle Plugin V3.1.0(2018 年 3 月)

新的 DEX 编译器 (D8)

默认情况下,Android Studio 此时会使用名为 D8 的新 DEX 编译器。DEX 编译是指针对 ART (对于较早版本的 Android,则针对 Dalvik)将 .class 字节码转换为 .dex 字节码的过程。与之前的编译器(DX)相比,D8 的编译速度更快,输出的 DEX 文件更小,同时却能保持相同甚至更出色的应用运行时性能

如果在使用 D8 的过程中出现了问题,可以在 gradle.properties 配置以下代码暂时停用 D8 并使用 DX:

android.enableD8=false

6、Android Gradle Plugin V3.0.0(2017 年 10 月)

1)、通过精细控制的任务图提升了多模块项目的并行性。

更改依赖项时,Gradle 通过不重新编译那些无法被访问的依赖项 API 模块来加快编译速度。此时可以利用 Gradle 的新依赖项配置(implementation、api、compileOnly 和 runtimeOnly)限制哪些依赖项会将其 API 泄露给其他模块。

2)、借助每个类的 dex 处理,可加快增量编译速度。

每个类现在都会编译成单独的 DEX 文件,并且只会对修改过的类重新进行 dex 处理

3)、启用 Gradle 编译缓存优化某些任务来使用缓存的输出

启用 Gradle 编译缓存能够优化某些任务来使用缓存的输出,从而加快编译速度。

4)、AAPT2 默认已启用并改进了增量资源处理

如果在使用 AAPT2 时遇到了问题,我们可以停用 AAPT2,在 gradle.properties 文件中设置如下代码:

android.enableAapt2=false

然后,通过在命令行中运行 ./gradlew --stop 来重启 Gradle 守护进程

7、Android Gradle Plugin V2.3.0(2017 年 2 月)

1)、增加编译缓存

存储编译项目时 Android 插件生成的特定输出。使用缓存时,编译插件的速度会明显加快,因为编译系统在进行后续编译时可以直接重用这些缓存文件,而不必重新创建。此外,我们也可以 使用 cleanBuildCache Task 去清除编译缓存

更老版本的的更新细节请查阅 gradle-plugin

三、Gradle 构建核心流程解析

当我们输入 ./gradlew/gradle … 命令之后,一个 Gradle 构建就开始了。它包含如下 三个步骤

  • 1)、首先,初始化 Gradle 构建框架自身
  • 2)、然后,把命令行参数包装好送给 DefaultGradleLauncher
  • 3)、最后,触发 DefaultGradleLauncher 中 Gradle 构建的生命周期,并开始执行标准的构建流程

在开始深入源码之前,我们可以先自顶向下了解下 Gradle 构建的核心流程图,以便对 Gradle 的构建流程建立一个整体的感知。

当我们执行一个 gralde 命令时,便会调用 gradle/wrapper/gradle-wrapper.jar 里面 org.gradle.wrapper.GradleWrapperMain 类的 main 方法,它就是 gradle 的一个入口方法。该方法的核心源码如下所示:

public static void main(String[] args) throws Exception {

// 1、索引到 gradle-wrapper.properties 文件中配置的 gradle zip 地址,并由此包装成一个 WrapperExecutor 实例。
WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
// 2、使用 wrapperExecutor 实例的 execute 方法执行 gradle 命令。
wrapperExecutor.execute(args, new Install(logger, new Download(logger, “gradlew”, “0”), new PathAssembler(gradleUserHome)), new BootstrapMainStarter());
}

然后,我们继续看看 wrapperExecutor 的 execute 方法,源码如下所示:

public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {
// 1、下载 gradle wrapper 需要的依赖与源码。
File gradleHome = install.createDist(this.config);
// 2、从这里开始执行 gradle 的构建流程。
bootstrapMainStarter.start(args, gradleHome);
}

首先,在注释1处,会下载 gradle wrapper 需要的依赖与源码。接着,在注释2处,便会调用 bootstrapMainStarter 的 start 方法从这里开始执行 gradle 的构建流程。其内部最终会依次调用 DefaultGradleLauncher 的 getLoadedSettings、getConfiguredBuild、executeTasks 与 finishBuild 方法,而它们对应的状态都定义在 DefaultGradleLauncher 中的 Stage 枚举类中,如下所示:

private static enum Stage {
LoadSettings,
Configure,
TaskGraph,
RunTasks {
String getDisplayName() {
return “Build”;
}
},
Finished;

private Stage() {
}

String getDisplayName() {
return this.name();
}
}

下面,我们就对这五个流程来进行详细地分析。

1、LoadSettings

当调用 getLoadedSettings 方法时便开始了加载 Setting.gradle 的流程。其源码如下所示:

public SettingsInternal getLoadedSettings() {
this.doBuildStages(DefaultGradleLauncher.Stage.LoadSettings);
return this.gradle.getSettings();
}

这里又继续调用了 doBuildStages 方法进行处理,内部实现如下所示:

private void doBuildStages(DefaultGradleLauncher.Stage upTo) {
Preconditions.checkArgument(upTo != DefaultGradleLauncher.Stage.Finished, “Stage.Finished is not supported by doBuildStages.”);

try {
// 当 Stage 是 RunTask 的时候执行。
if (upTo == DefaultGradleLauncher.Stage.RunTasks && this.instantExecution.canExecuteInstantaneously()) {
this.doInstantExecution();
} else {
// 当 Stage 不是 RunTask 的时候执行。 this.doClassicBuildStages(upTo);
}
} catch (Throwable var3) {
this.finishBuild(upTo.getDisplayName(), var3);
}

}

继续调用 doClassicBuildStages 方法,源码如下所示:

private void doClassicBuildStages(DefaultGradleLauncher.Stage upTo) {
// 1、当 Stage 为 LoadSettings 时执行 prepareSettings 方法去配置并生成 Setting 实例。
this.prepareSettings();
if (upTo != DefaultGradleLauncher.Stage.LoadSettings) {
// 2、当 Stage 为 Configure 时执行 prepareProjects 方法去配置工程。
this.prepareProjects();
if (upTo != DefaultGradleLauncher.Stage.Configure) {
// 3、当 Stage 为 TaskGraph 时执行 prepareTaskExecution 方法去构建 TaskGraph。
this.prepareTaskExecution();
if (upTo != DefaultGradleLauncher.Stage.TaskGraph) {

// 4、当 Stage 为 RunTasks 时执行 saveTaskGraph 方法 与 runWork 方法保存 TaskGraph 并执行相应的 Tasks。 this.instantExecution.saveTaskGraph();
this.runWork();
}
}
}
}

可以看到,doClassicBuildStages 方法是个很重要的方法,它对所有的 Stage 任务进行了分发,这里小结一下:

  • 1)、当 Stage 为 LoadSettings 时执行 prepareSettings 方法去配置并生成 Setting 实例
  • 2)、当 Stage 为 Configure 时执行 prepareProjects 方法去配置工程
  • 3)、当 Stage 为 TaskGraph 时执行 prepareTaskExecution 方法去构建 TaskGraph
  • 4)、当 Stage 为 RunTasks 时执行 saveTaskGraph 方法 与 runWork 方法保存 TaskGraph 并执行相应的 Tasks

然后,我们接着继续看看 prepareSettings 方法,其源码如下所示:

private void prepareSettings() {
if (this.stage == null) {
// 1、回调 BuildListener.buildStarted() 回调接口。
this.buildListener.buildStarted(this.gradle);
// 2、调用 settingPreparer 接口的实现类 DefaultSettingsPreparer 的 prepareSettings 方法。
this.settingsPreparer.prepareSettings(this.gradle);
this.stage = DefaultGradleLauncher.Stage.LoadSettings;
}
}

prepareSettings 方法做了两件事:

  • 1)、回调 BuildListener.buildStarted 接口
  • 2)、调用 settingPreparer 接口的实现类 DefaultSettingsPreparer 的 prepareSettings 方法

我们继续看到 DefaultSettingsPreparer 的 prepareSettings 方法,如下所示:

public void prepareSettings(GradleInternal gradle) {
// 1、执行 init.gradle,它会在每个项目 build 之前被调用,用于做一些初始化的操作。
this.initScriptHandler.executeScripts(gradle);
SettingsLoader settingsLoader = gradle.getParent() != null ? this.settingsLoaderFactory.forNestedBuild() : this.settingsLoaderFactory.forTopLevelBuild();
// 2、调用 SettingLoader 接口的实现类 DefaultSettingsLoader 的 findAndLoadSettings 找到 Settings.gradle 文件的位置。
settingsLoader.findAndLoadSettings(gradle);
}

prepareSettings 方法中做了两项处理:

  • 1)、执行 init.gradle,它会在每个项目 build 之前被调用,用于做一些初始化的操作
  • 2)、调用 SettingLoader 接口的实现类 DefaultSettingsLoader 的 findAndLoadSettings 找到 Settings.gradle 文件的位置

DefaultSettingLoader 的 findAndLoadSettings 方法关联的实现代码非常多,限于篇幅,我这里直接点出 findAndLoadSettings 方法中的主要处理流程

  • 1)、首先,查找 settings.gradle 位置
  • 2)、然后,编译 buildSrc(Android 默认的 Plugin 目录)文件夹下的内容
  • 3)、接着,解析 gradle.properites 文件:这里会读取 gradle.properties 文件里的配置信息与命令行传入的配置属性并存储
  • 4)、然后,解析 settings.gradle 文件:这里最后会调用 BuildOperationScriptPlugin.apply 去执行 settings.gradle 文件
  • 5)、最后,根据 Settings.gradle 文件中获得的信息去创建 project 以及 subproject 实例

2、Configure

当执行完 LoadSetting 阶段之后,就会执行 Configure 阶段,而配置阶段所作的事情就是 把 gradle 脚本编译成 class 文件并执行。由前可知,此时会执行 prepareProjects 方法,如下所示:

private void prepareProjects() {
if (this.stage == DefaultGradleLauncher.Stage.LoadSettings) {
// 1、调用 ProjectsPreparer 接口的实现类 DefaultProjectsPreparer 的 prepareProjects 方法。
this.projectsPreparer.prepareProjects(this.gradle);
this.stage = DefaultGradleLauncher.Stage.Configure;
}
}

这里会继续调用 ProjectsPreparer 接口的实现类 DefaultProjectsPreparer 的 prepareProjects 方法。其源码如下所示:

public void prepareProjects(GradleInternal gradle) {

// 1、如果在 gradle.properties 文件中指定了参数 configure-on-demand,则只会配置主项目以及执行 task 所需要的项目。
if (gradle.getStartParameter().isConfigureOnDemand()) {
this.projectConfigurer.configure(gradle.getRootProject());
} else {
// 2、如果没有指定在 gradle.properties 文件中指定参数 configure-on-demand,则会调用 ProjectConfigurer 接口的实现类 TaskPathProjectEvaluator 的 configureHierarchy 方法去配置所有项目。
this.projectConfigurer.configureHierarchy(gradle.getRootProject());
(new ProjectsEvaluatedNotifier(this.buildOperationExecutor)).notify(gradle);
}

this.modelConfigurationListener.onConfigure(gradle);
}

在注释1处,如果在 gradle.properties 文件中指定了参数 configure-on-demand,则只会配置主项目以及执行 task 所需要的项目。我们这里只看默认没有指定的情况。否则,在注释2处,则会调用 ProjectConfigurer 接口的实现类 TaskPathProjectEvaluator 的 configureHierarchy 方法去配置所有项目。

我们接着继续看到 configureHierarchy 方法,如下所示:

public void configureHierarchy(ProjectInternal project) {
this.configure(project);
Iterator var2 = project.getSubprojects().iterator();

while(var2.hasNext()) {
Project sub = (Project)var2.next();
this.configure((ProjectInternal)sub);
}
}

可以看到在 configureHierarchy 方法中使用了 Iterator 遍历并配置了所有 Project。而 configure 方法最终会调用到 EvaluateProject 类的 run 方法,如下所示:

public void run(final BuildOperationContext context) {
this.project.getMutationState().withMutableState(new Runnable() {
public void run() {
try {

EvaluateProject.this.state.toBeforeEvaluate();

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

实战系列

话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示


本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

2466697104)]
[外链图片转存中…(img-qDiMkoLG-1712466697104)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值