【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. java内存区域及溢出异常

    内存划分: java虚拟机在执行java程序过程中会把内存分为以下区域进行管理 线程私有的 虚拟机栈 局部变量表 基本数据类型 long和double占用两个slot 对象引用 返回地址 操作数栈 动 ...

  2. KVM虚拟化简介及安装

    kvm是基于图形化的linux操作的 安装图形化界面的知识点: 磁盘空间有两个词: 精简置备:我先在我系统里面去声明我要一个50G的空间,但是呢,我不会把50G都分给你,你用多少,我分给你多少,但是做 ...

  3. mariadb读写分离

    mycat maraidb主从架构,是主负责写,从负责读,但前端如果没有调度器的话,是无法实现读写分离的.这就涉及到了中间站,它就是mycat.一定要在主从架构的基础之上实现读写分离. 配置三台的主从 ...

  4. PAT Basic 1029 旧键盘 (20 分)

    旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现.现在给出应该输入的一段文字.以及实际被输入的文字,请你列出肯定坏掉的那些键. 输入格式: 输入在 2 行中分别给出应该输入的文字.以及 ...

  5. (转)JAVA socket 进行十六进制报文交互测试

    import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io. ...

  6. Codeforces 975 前缀和二分算存活人数 思维离直线速度相同判平行

    A /* Huyyt */ #include <bits/stdc++.h> using namespace std; typedef long long ll; ]; ]; map< ...

  7. vue2.0 笔记(杂记)

    一.vue class.style表达式的类型: 字符串.对象和数组1.字符串 <div class="static" v-bind:class="class-a& ...

  8. 微信小程序购物车功能

    <view class='shop-mana'> <text class='management'>管理您的购物车</text> <text class='g ...

  9. Iconfont 阿里图库使用(小程序和H5)

    前言 现在前端发展的太快,前端优化也是,图片也是被近2年来比较火的就是阿里图库取代了,不管小程序还是H5 都在用 好了,那么就介绍下如何使用吧 阿里图库 当然需要你到阿里官网 http://www.i ...

  10. windows窗口启动redis

    必须先得配置好环境变量,才能在窗口开启 启动服务端:redis-server 启动客户端:redis-cli