【Android 修炼手册】Gradle 篇 -- Android Gradle Plugin 主要流程分析

预备知识

  1. 理解 gradle 的基本开发
  2. 了解 gradle task 和 plugin 使用及开发
  3. 了解 android gradle plugin 的使用

看完本文可以达到什么程度

  1. 了解 android gradle plugin 的构建流程
  2. 了解 android gradle plugin 的主要 task 的实现
  3. 学会 hook android 构建流程,添加自己想要的功能

阅读前准备工作

  1. 项目添加 android gradle plugin 依赖
compile 'com.android.tools.build:gradle:3.0.1'

通过这种方式,可以直接依赖 plugin 的源码,读起来比较方便。
2. 官方对照源码地址 android gradle plugin 源码地址

大家可以直接 clone EasyGradle 项目,把 android-gradle-plugin-source/build.gradle 里的 implementation 'com.android.tools.build:gradle:3.0.1' 注释打开就可以了。

com.android.application 主要有下面几个流程:

一、插件启动的准备工作

在前面讲解自定义插件的时候说到过,要定义一个 xxx.properties 文件,里面声明插件的入口类,而 xxx 就是 apply plugin 时候使用的 id,这里要知道 android gradle plugin 的入口类,看 com.android.application.properties 文件就可以,内容如下:
implementation-class=com.android.build.gradle.AppPlugin

这里定义了入口是 AppPlugin,AppPlugin 继承自 BasePlugin。
AppPlugin 里没有做太多的操作,主要是重写了 createTaskManager 和 createExtension,剩下的大部分工作还是在 BasePlugin 里做的。
插件准备工作中主要做的事情:

1.检查插件版本

// method: BasePlugin.apply()
checkPluginVersion();

2.检查 module 是否重名

// method: BasePlugin.apply()
// 方法中会遍历所有子项目,判断是否有重复的 id
this.checkModulesForErrors();

3.初始化插件信息

// method: BasePlugin.apply()
PluginInitializer.initialize(project, this.projectOptions);
// 创建 Profiler 文件
ProfilerInitializer.init(project, this.projectOptions);
// profiler 信息中写入 plugin 版本
ProcessProfileWriter.getProject(project.getPath()).setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION).setAndroidPlugin(this.getAnalyticsPluginType()).setPluginGeneration(PluginGeneration.FIRST);

二、配置项目

配置项目这一阶段主要做的事情:

1.检查 gradle 版本是否匹配

// method: BasePlugin.configureProject()
this.checkGradleVersion();

2.创建 AndroidBuilder和 DataBindingBuilder

3.引入 java plugin 和 jacoco plugin

this.project.getPlugins().apply(JavaBasePlugin.class);
this.project.getPlugins().apply(JacocoPlugin.class);

4.设置构建完成以后的混存清理工作 添加了 BuildListener,在 buildFinished 回调里做缓存清理工作

三、配置 Extension

实现在 BasePlugin.configureExtension()
这一阶段主要做了下面几个事情:

1.创建 AppExtension,也就是 build.gradle 里用到的 android {} dsl

this.extension = this.createExtension(...);

// AppPlugin 中实现了 createExtension,创建了 android {} dsl
protected BaseExtension createExtension(...) {
return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
}

2.创建依赖管理,ndk管理,任务管理,variant管理

3.注册新增配置的回调函数,包括 signingConfig,buildType,productFlavor

// BasePlugin.java createExtension()
// map the whenObjectAdded callbacks on the containers.
signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);
buildTypeContainer.whenObjectAdded(
buildType -> {
SigningConfig signingConfig =
signingConfigContainer.findByName(BuilderConstants.DEBUG);
buildType.init(signingConfig);
// addBuildType,会检查命名是否合法,然后创建 BuildTypeData
variantManager.addBuildType(buildType);
});
// addProductFlavor 会检查命名是否合法,然后创建 ProductFlavorData
productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);
// VariantManager.java
// addSigningConfig 就是在 signingConfigs 里新增一个配置
public void addSigningConfig(SigningConfig signingConfig) {
this.signingConfigs.put(signingConfig.getName(), signingConfig);
}
// VariantManager.java
public void addProductFlavor(CoreProductFlavor productFlavor) {
String name = productFlavor.getName();
// checkName 会检查
checkName(name, "ProductFlavor");
if(this.buildTypes.containsKey(name)) {
throw new RuntimeException("ProductFlavor names cannot collide with BuildType names");
} else {
// 获取源码位置
DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet)this.extension.getSourceSets().maybeCreate(productFlavor.getName());
DefaultAndroidSourceSet androidTestSourceSet = null;
DefaultAndroidSourceSet unitTestSourceSet = null;
if(this.variantFactory.hasTestScope()) {
// 获取单测源码位置
androidTestSourceSet = (DefaultAndroidSourceSet)this.extension.getSourceSets().maybeCreate(computeSourceSetName(productFlavor.getName(), VariantType.ANDROID_TEST));
unitTestSourceSet = (DefaultAndroidSourceSet)this.extension.getSourceSets().maybeCreate(computeSourceSetName(productFlavor.getName(), VariantType.UNIT_TEST));
} // 创建 productFlavorData 对象
ProductFlavorData<CoreProductFlavor> productFlavorData = new ProductFlavorData(productFlavor, mainSourceSet, androidTestSourceSet, unitTestSourceSet, this.project);
this.productFlavors.put(productFlavor.getName(), productFlavorData);
}
}

4.创建默认的 debug 签名,创建 debug 和 release 两个 buildType

variantFactory.createDefaultComponents(
buildTypeContainer, productFlavorContainer, signingConfigContainer);
// ApplicationVariantFactory.java
public void createDefaultComponents(...) {
signingConfigs.create("debug");
buildTypes.create("debug");
buildTypes.create("release");
}

四、创建不依赖 flavor 的 task

上述准备,配置阶段完成以后,就开始创建构建需要的 Task 了,是在 BasePlugin.createTasks() 里实现的,主要有两步,创建不依赖 flavor 的 task 和创建构建 task
先看不依赖 flavor 的 task,其实现在 TaskManager.createTasksBeforeEvaluate()。
这里主要创建了几个 task,包括 uninstallAll,deviceCheck,connectedCheck,preBuild,extractProguardFiles,sourceSets,assembleAndroidTest,compileLint,lint,lintChecks,cleanBuildCacheresolveConfigAttr,consumeConfigAttr。
这些 task 都是不需要依赖 flavor 数据的公共 task。

五、创建构建 task

在介绍下面的流程之前,先明确几个概念,flavor,dimension,variant。
在 android gradle plugin 3.x 之后,每个 flavor 必须对应一个 dimension,可以理解为 flavor 的分组,然后不同 dimension 里的 flavor 组合成一个 variant。
举个例子:
flavorDimensions "size", "color"

productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
blue {
dimension "color"
}
red {
dimension "color"
}
}

上面配置对应生成的 variant 就是 bigBlue,bigRed,smallBlue,smallRed,在这个基础上,再加上 buildTypes,就是 bigBlueDebug,bigRedDebug,smallBlueDebug,smallRedDebug,bigBlueRelease,bigRedRelease,smallBlueRelease,smallRedRelease。

createAndroidTasks 的调用时机和上面不一样,是在 project.afterEvaluate 里调用的,还记得之前文章里说道的 afterEvaluate 回调么?这个时候所有模块配置已经完成了。所以在这个阶段可以获取到对应的 flavor 以及其他配置了。
在 BasePlugin.createAndroidTasks 里,是调用 VariantManager.createAndroidTasks 完成工作的。
创建 task 的时候,会先通过 populateVariantDataList 生成 flavor 相关的数据结构,然后调用 createTasksForVariantData 创建 flavor 对应的 task。
分别看下这两个方法做的事情
1.populateVariantDataList
在方法里,会先根据 flavor 和 dimension 创建对应的组合,存放在 flavorComboList 里,之后调用 createVariantDataForProductFlavors 创建对应的 VariantData。
其中重要的几个方法:

// 创建 flavor 和 dimension 的组合
List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =
ProductFlavorCombo.createCombinations(
flavorDimensionList,
flavorDsl);
// 为每个组合创建 VariantData
for (ProductFlavorCombo<CoreProductFlavor> flavorCombo : flavorComboList) {
//noinspection unchecked
createVariantDataForProductFlavors(
(List<ProductFlavor>) (List) flavorCombo.getFlavorList());
}

创建出来的 VariantData 都是 BaseVariantData 的子类,里面保存了一些 Task,可以看一下 BaseVariantData 里的一些重要的结构,对 BaseVariantData 有个大概的了解。

public abstract class BaseVariantData implements TaskContainer {
private final GradleVariantConfiguration variantConfiguration;
private VariantDependencies variantDependency;
private final VariantScope scope;
public Task preBuildTask;
public Task sourceGenTask;
public Task resourceGenTask; // 资源处理
public Task assetGenTask;
public CheckManifest checkManifestTask; // 检测manifest
public AndroidTask<PackageSplitRes> packageSplitResourcesTask; // 打包资源
public AndroidTask<PackageSplitAbi> packageSplitAbiTask;
public RenderscriptCompile renderscriptCompileTask;
public MergeResources mergeResourcesTask; // 合并资源
public ManifestProcessorTask processManifest; // 处理 manifest
public MergeSourceSetFolders mergeAssetsTask; // 合并 assets
public GenerateBuildConfig generateBuildConfigTask; // 生成 BuildConfig
public GenerateResValues generateResValuesTask;
public Sync processJavaResourcesTask;
public NdkCompile ndkCompileTask; // ndk 编译
public JavaCompile javacTask;
public Task compileTask;
public Task javaCompilerTask; // java 文件编译
// ...
}

VariantData 里保存了很多 task,下一步就要创建这些 task。

2.createTasksForVariantData
创建完 variant 数据,就要给 每个 variantData 创建对应的 task,对应的 task 有 assembleXXXTask,prebuildXXX,generateXXXSource,generateXXXResources,generateXXXAssets,processXXXManifest 等等,重点关注几个方法:

VariantManager.createAssembleTaskForVariantData() // 创建 assembleXXXTask
TaskManager.createTasksForVariantScope() // 是一个抽象类,具体实现在 ApplicationTaskManager.createTasksForVariantScope()
TaskManager.createPostCompilationTasks() // 创建 .class to dex 的 task, 创建 transformTask,我们创建的 transform 就是这个阶段添加进来的,是在 addCompileTask 里调用的 // createTasksForVariantScope 是一个抽象方法,具体实现在子类中,可以看一下 ApplicationTaskManager.createTasksForVariantScope()
// createTasksForVariantScope 里的实现,如果在业务中有需要查看相关 task 源码时,可以来这里找
void createTasksForVariantScope() {
this.createCheckManifestTask(tasks, variantScope); // 检测 manifest
this.handleMicroApp(tasks, variantScope);
this.createDependencyStreams(tasks, variantScope);
this.createApplicationIdWriterTask(tasks, variantScope); // application id
this.createMergeApkManifestsTask(tasks, variantScope); // 合并 manifest
this.createGenerateResValuesTask(tasks, variantScope);
this.createRenderscriptTask(tasks, variantScope);
this.createMergeResourcesTask(tasks, variantScope, true); // 合并资源文件
this.createMergeAssetsTask(tasks, variantScope, (BiConsumer)null); // 合并 assets
this.createBuildConfigTask(tasks, variantScope); // 生成 BuildConfig
this.createApkProcessResTask(tasks, variantScope); // 处理资源
this.createProcessJavaResTask(tasks, variantScope);
this.createAidlTask(tasks, variantScope); // 处理 aidl
this.createShaderTask(tasks, variantScope);
this.createNdkTasks(tasks, variantScope); // 处理 ndk
this.createExternalNativeBuildJsonGenerators(variantScope);
this.createExternalNativeBuildTasks(tasks, variantScope);
this.createMergeJniLibFoldersTasks(tasks, variantScope); // 合并 jni
this.createDataBindingTasksIfNecessary(tasks, variantScope); // 处理 databinding
this.addCompileTask(tasks, variantScope);
createStripNativeLibraryTask(tasks, variantScope);
this.createSplitTasks(tasks, variantScope);
this.createPackagingTask(tasks, variantScope, buildInfoWriterTask); // 打包 apk
this.createLintTasks(tasks, variantScope); // lint
} // createPostCompilationTasks 实现:
// 处理 Android Transform
void createPostCompilationTasks() {
for (int i = 0, count = customTransforms.size(); i < count; i++) {
Transform transform = customTransforms.get(i);
// TransformManager.addTransform 实际上是为 transform 创建了一个 Task
transformManager
.addTransform(tasks, variantScope, transform)
.ifPresent(t -> {
if (!deps.isEmpty()) {
t.dependsOn(tasks, deps);
}
// if the task is a no-op then we make assemble task depend on it.
if (transform.getScopes().isEmpty()) {
variantScope.getAssembleTask().dependsOn(tasks, t);
}
});
}
}

六、总结

android gradle plugin 的主要流程基本上就结束了,主要流程图如下所示

这里总结几个要点:

  1. com.android.application 入口类是 AppPlugin,但大部分工作都是在 BasePlugin 里完成的
  2. build.gradle 里见到的 android {} dsl 是在 BasePlugin.configureExtension() 里声明的
  3. 主要的 task 是在 BasePlugin.createAndroidTasks() 里生成的
  4. 主要 task 的实现可以在 TaskManager 中找到
  5. transform 会转化成 TransformTask
 
 
 

Gradle之Android Gradle Plugin 主要流程分析(二)的更多相关文章

  1. Gradle之Android Gradle Plugin 主要 Task 分析(三)

    [Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要 Task 分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plu ...

  2. 【Gradle】Android Gradle 插件

    Android Gradle 插件 Android Gradle 插件简介 从Gradle角度来看,Android其实是Gradle的一个第三方插件,它是由Google的Android团队开发的.但从 ...

  3. 【Gradle】Android Gradle 多渠道构建

    Android Gradle 多渠道构建 多渠道构建的基本原理 在Android Gradle中,定义了一个叫Build Variant的概念,一个Build Variant=Build TYpe+P ...

  4. 【Gradle】Android Gradle 多项目构建

    Android Gradle 多项目构建 Android 项目区别 Android项目一般分为库项目,应用项目,测试项目,Android Gradle 根据这些项目分别对应3种插件:com.andro ...

  5. Android 4.4KitKat AudioRecord 流程分析

    Android是架构分为三层: 底层      Linux Kernel 中间层  主要由C++实现 (Android 60%源码都是C++实现) 应用层  主要由JAVA开发的应用程序 应用程序执行 ...

  6. Android 4.4 音量调节流程分析(二)

    之前在Android 4.4 音量调节流程分析(一)里已经有简单的分析音量控制的流程,今天想接着继续分析下音量大小计算的方法.对于任一播放文件而言其本身都有着固定大小的音量Volume_Max,而在A ...

  7. Cocos2d-x3.3RC0的Android编译Activity启动流程分析

    本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2 ...

  8. Android 7.1 WindowManagerService 屏幕旋转流程分析 (二)

    一.概述 从上篇[Android 7.1 屏幕旋转流程分析]知道实际的旋转由WindowManagerService来完成,这里接着上面具体详细展开. 调了三个函数完成了三件事,即首先调用update ...

  9. 【Gradle】Android Gradle 高级自定义

    Android Gradle 高级自定义 使用共享库 Android的包,如android.app,android.content,android.view,android.widget等,是默认包含 ...

随机推荐

  1. windows下查看C语言字符数组(俗称:字符串)在内存中地址信息的操作过程

      #include <stdio.h> #pragma warning(disable:4996) int power10(int n) { ) { ; } ; ; i < n; ...

  2. 【LuoguP5289】[十二省联考2019] 皮配

    题目链接 题目描述 略 Sol 一道背包问题 首先暴力做法设 \(dp[i][j][k]\) 表示前 \(i\) 个城市的学校被分到第一阵营 \(j\) 人 第一门派 \(k\) 人的方案数. 中间一 ...

  3. rsync快速部署记录

    rsync快速部署记录 安装rsync和使用环境:客户端:10.192.30.59 fudao_db_cluster_002 (将本地文件备份到服务端)服务端:10.192.30.60 fudao_d ...

  4. 输出到Excel

    HSSFWorkbook oBook = new HSSFWorkbook(); NPOI.SS.UserModel.ISheet oSheet = oBook.CreateSheet(); #reg ...

  5. PHP 字符串相关常识

    0x00 前言 第一次遇见字符串这个概念是在学 C 语言的时候,那时候觉得字符串也没有什么难的,不就是一个以 \0 结尾的 char 数组而已咯.后来在学习 PHP 的过程中也同样保持这个观念,不过在 ...

  6. 关于:基于http协议大文件断点续传上传至web服务器

    关键部分 前端用file.slice()分块 前端用FileReader获取每一分块的md5值 后端用MultipartFile接受分块文件 后端用FileOutputStream拼装分块文件 话不多 ...

  7. Tire树模板-于是他错误的点名开始了

    题目背景 XS中学化学竞赛组教练是一个酷爱炉石的人. 他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛CON900). ...

  8. luogu P1181 数列分段Section I x

    P1181 数列分段Section I 题目描述 对于给定的一个长度为N的正整数数列A[i],现要将其分成连续的若干段,并且每段和不超过M(可以等于M),问最少能将其分成多少段使得满足要求. 输入输出 ...

  9. sh_07_函数的嵌套调用

    sh_07_函数的嵌套调用 def test1(): print("*" * 50) def test2(): print("-" * 50) # 函数的嵌套调 ...

  10. [BZO3572][HNOI2014]世界树:虚树+倍增

    分析 思维难度几乎为\(0\)的虚树码农(并不)题. 代码 #include <bits/stdc++.h> #define rin(i,a,b) for(register int i=( ...