这个可能是作为 Android 开发想要做插件开发的时候最关心的事,我们今天看看如何去扩展 Android Gradle Plugin(以下简称 AGP)

我们扩展 AGP 的方式有两种:

  1. 利用 ApplicationVariant 和 LibraryVariant 两个类,去扩展我们的一些功能。
  2. 直接继承 AppPlugin 和 LibraryPlugin 来实现扩展功能。

其中,第一种方式是我们常见的方式,我们经常会使用 Gradle 的 DSL 获取到所有的变体。

applicationVariants.all { variant ->
    ... // Do something
}

applicationVariants 是 AppExtension 里的内容,如果你想配置 Library 工程的话,就使用libraryVariants,这两个 DSL 调用返回的是两种对象的集合,一个是 ApplicationVariant 这个类的集合,一个是 LibraryVariant 的集合,他们都继承于 BaseVariant,我们看下两个对象的简单声明:


public interface ApplicationVariant extends ApkVariant, TestedVariant {}

public interface LibraryVariant extends BaseVariant, TestedVariant {
    ...
}

他们统一继承于 BaseVariant(ApkVariant 继承自 BaseVariant),因此可以使用 BaseVariant 获得一些接口。
首先,我们目前使用的版本是 AGP 3.5.x,BaseVariant 提供了一些主要的 TaskProvider,我们能拿到相关 Variant 的 TaskProvider 做一些配置,比如我们关心 Java 编译任务的话,可以使用getJavaCompileProvider这个方法,拿到 TaskProvider<JavaCompile> 对象,然后使用.config方法对这个 Task 进行调用。一般来说,使用 BaseVariant 的方式能满足基本需求,

第二种方式的自定义能力更强大,同时接入会更加复杂,我们继承了 AppPlugin 就意味着需要子类化 TaskManager 等类,这时候,如果我们想子类化一些 AGP 提供的任务的话,用这种方式会好很多,如果我们只想在 AGP 的任务链调用中,插入一个新任务的话,用上第一种方案比较好。

准备

如果我们已经按照 上一个教程 构建好一个项目的话,我们可以直接按照这个项目开始。

增加自定义的 Java Resources

我们这一节的目的是在我们的 APK 中增加自己的任意。我们需要在 apply 的回调中写逻辑,那么这个处理起来很简单,看以下示例代码:

public class AppPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        project.afterEvaluate(p -> {
            AppExtension extension = (AppExtension)p.getExtensions().getByName("android");
            Set<? extends BaseVariant> variants = extension.getApplicationVariants();
            configureVariants(project, variants);
        });
    }

    private void configureVariants(Project project, Set<? extends BaseVariant> variants) {
        variants.forEach(it -> {
            TaskProvider<AbstractCopyTask> task = it.getProcessJavaResourcesProvider();
            configureProcessJavaResource(project, task);
        });
    }

    private void configureProcessJavaResource(Project project, TaskProvider<AbstractCopyTask> taskProvider) {
        taskProvider.configure(task -> {
            task.from(project.file("gemini.txt"));
        });
    }
}

AbstractCopyTask.from 的注释如下:

     /**
     * Specifies source files or directories for a copy. The given paths are evaluated as per {@link
     * org.gradle.api.Project#files(Object...)}.
     *
     * @param sourcePaths Paths to source files for the copy
     */
    CopySourceSpec from(Object... sourcePaths);

那么经过以上修改,我们就成功的往 JavaResources 任务里面新增了一个 Input 啦。赶紧使用

./gradlew clean install

把插件安装到本地,然后在 Android 工程中进行实践吧。
打开我们上次创建的 Android 工程,然后在app模块中新建刚刚需要的文件,比如我这里使用的是gemini.txt
需要打入包内的文件
最后,执行 Android 工程的任务:

./gradlew clean assembleDebug

完成后,我们查看下我们的 APK 文件:

APK

可以看见我们需要的文件已经打进去了。

那么对于其他 Task 我们依然可以如法炮制,在 Variants 迭代的过程中,我们能先拿到这些 TaskProvider,然后调用 configure 方法,在 Task 真正被创建的时候,会调用到这些方法,我们只要配置好正确的输入,它就会执行我们的输出了。

在 AGP 原有的 Task 中接入我们的 Task

我们在前面 Gradle Builds Everything —— Task 实例 说过,Task 之间的产物,可以使用 BuildableArtifactsHolder 这个对象连接起来。
因为它是一个产物收集器,在最后 Package 任务需要打包 zip 的过程中,都是通过这个类把所有已经打出的产物收集起来,最后变成一个 zip 包。

task.outputFile =  variantScope.getArtifacts().createArtifactFile(
                    InternalArtifactType.BUNDLE,
                    BuildArtifactsHolder.OperationType.INITIAL,
                    taskName,
                    bundleName)

我们目前只能通过继承的方式拿到 VariantScope,同时只能通过子类化 TaskManager 的方式重新编排 Configuration 过程中的顺序(任务执行顺序我们反而不用担心)。通过精心编排好 BuildableArtifactsHolder 的注册顺序,Task 就自动被串联起来了。

后续

后续可能还有关于 Task 输入和输出的高级用法,比如像前文提到的 Artifacts 之类的连接等等。不过到此为止,我们关于自定义 Gradle 插件的基础用法和主线就全讲完啦。

欢迎关注我的公众号「TalkWithMobile」
公众号


Gemini
7k 声望1.5k 粉丝

一个没有文化的诗人