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

栏目: IOS · Android · 发布时间: 5年前

内容简介:通过这种方式,可以直接依赖 plugin 的源码,读起来比较方便。2. 官方对照源码地址大家可以直接 clone
  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 主要有下面几个流程:

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

一、插件启动的准备工作

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

在前面讲解自定义插件的时候说到过,要定义一个 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();
复制代码
  1. 检查 module 是否重名
// method: BasePlugin.apply()
// 方法中会遍历所有子项目,判断是否有重复的 id
this.checkModulesForErrors();
复制代码
  1. 初始化插件信息
// 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);
复制代码

二、配置项目

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

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

  1. 检查 gradle 版本是否匹配
// method: BasePlugin.configureProject()
this.checkGradleVersion();
复制代码
  1. 创建 AndroidBuilder和 DataBindingBuilder
  2. 引入 java plugin 和 jacoco plugin
this.project.getPlugins().apply(JavaBasePlugin.class);
this.project.getPlugins().apply(JacocoPlugin.class);
复制代码
  1. 设置构建完成以后的混存清理工作 添加了 BuildListener,在 buildFinished 回调里做缓存清理工作

三、配置 Extension

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

实现在 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});
}
复制代码
  1. 创建依赖管理,ndk管理,任务管理,variant管理
  2. 注册新增配置的回调函数,包括 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);
    }
}
复制代码
  1. 创建默认的 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

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

上述准备,配置阶段完成以后,就开始创建构建需要的 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

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

在介绍下面的流程之前,先明确几个概念,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 的主要流程基本上就结束了,主要流程图如下所示

【Android 修炼手册】Gradle 篇 -- 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

【Android 修炼手册】系列内容 每周五更新

欢迎关注下面账号,获取更新:

微信搜索公众号: ZYLAB

Github

掘金

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

腾讯传

腾讯传

吴晓波 / 浙江大学出版社 / 2017-1-1 / 58.00元

腾讯官方唯一授权的权威传记 著名财经作家吴晓波倾力之作 当市值最高的中国互联网公司,遇上中国财经界最冷静的一双眼睛 读懂腾讯,读懂中国互联网 . 内容简介 本书全景式地记录了腾讯崛起的经历,并以互联网的视角重新诠释了中国在融入全球化进程中的曲折与独特性。 从1998年开始创业到成为世界级互联网巨头,腾讯以即时通信工具起步,逐渐进入社交网络、互动娱乐、网络媒......一起来看看 《腾讯传》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换