一文学会Android Gradle Transform基础使用

2021-01-06  本文已影响0人  Android阿南

概述

最近在做一个在 Android 工程编译期间动态插入一些随机代码的需求,我选择的是 Gradle Transform 技术,想起好久没有写过博客了,就记录一下这方面的一些基本使用。

一般来说,在 Android 工程的编译期间可以通过一些技术来动态插入一些代码逻辑甚至生成一些新的 Class 类,具体技术有:

这里还可以看看 AOP 和 IOC 的一些概念,参考 AOP-IOC概述。在使用 Transform 之前需要先了解一下 Gradle 自定义插件的方式,可以选择最简单的方式实现,参考 自定义gradle插件

Android Gradle 工具从 1.5.0-beta1 版本开始提供了 Transform API 工具,它可以在将 .class 文件转换为 dex 文件之前对其进行操作。可以通过自定义 Gradle 插件来注册自定义的 Transform,注册后 Transform 会包装成一个 Gradle Task 任务,这个 Task 在 compile task 执行完毕后运行。

依赖如下:

implementation 'com.android.tools.build:gradle:4.1.1'
复制代码

当在buildSrc中开发插件时,其build.gradle脚本内容如下:

apply plugin: 'groovy'
apply plugin: 'maven'

repositories {
    google()
    jcenter()
    mavenCentral()
}

dependencies {
    implementation gradleApi()
    implementation localGroovy()
    implementation 'com.android.tools.build:gradle:4.1.1'
}
复制代码

Transform处理流程如下图(图片来于网络):

Transform

先看看Transform类,这是一个abstract类,实现自定义 Transform task 需要重写它,一般需要重写的方法有:

class InjectTransform extends Transform {

    @Override
    String getName() {
        return null
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return null
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return null
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)
    }
}
复制代码

getName

指明 Transform 的名字,也对应了该 Transform 所代表的 Task 名称,例如当返回值为 InjectTransform 时,编译后可以看到名为transformClassesWithInjectTransformForxxx 的 task。

getInputTypes

指明 Transform 处理的输入类型,在 TransformManager 中定义了很多类型:

public static final Set<ScopeType> EMPTY_SCOPES = ImmutableSet.of();

// 代表 javac 编译成的 class 文件,常用
public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES);
// 这里的 resources 单指 java 的资源
public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
public static final Set<ContentType> CONTENT_NATIVE_LIBS = ImmutableSet.of(NATIVE_LIBS);
public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX);
public static final Set<ContentType> CONTENT_DEX_WITH_RESOURCES = ImmutableSet.of(ExtendedContentType.DEX, RESOURCES);
复制代码

其中,很多类型是不允许自定义 Transform 来处理的,我们常使用 CONTENT_CLASS 来操作 Class 文件。

getScopes

指明 Transform 输入文件所属的范围, 因为 gradle 是支持多工程编译的。总共有以下几种:

enum Scope implements ScopeType {
    /** Only the project (module) content */
    PROJECT(0x01),
    /** Only the sub-projects (other modules) */
    SUB_PROJECTS(0x04),
    /** Only the external libraries */
    EXTERNAL_LIBRARIES(0x10),
    /** Code that is being tested by the current variant, including dependencies */
    TESTED_CODE(0x20),
    /** Local or remote dependencies that are provided-only */
    PROVIDED_ONLY(0x40),

    @Deprecated
    PROJECT_LOCAL_DEPS(0x02),
    @Deprecated
    SUB_PROJECTS_LOCAL_DEPS(0x08);

    private final int value;

    Scope(int value) {
        this.value = value;
    }

    @Override
    public int getValue() {
        return value;
    }
}
复制代码

在 TransformManager 类中定义了几种范围:

public static final Set<ScopeType> PROJECT_ONLY = ImmutableSet.of(Scope.PROJECT);
public static final Set<ScopeType> SCOPE_FULL_PROJECT = ImmutableSet.of(Scope.PROJECT, Scope.SUB_PROJECTS, Scope.EXTERNAL_LIBRARIES);
public static final Set<ScopeType> SCOPE_FULL_WITH_FEATURES = new ImmutableSet.Builder<ScopeType>().addAll(SCOPE_FULL_PROJECT).add(InternalScope.FEATURES).build();
public static final Set<ScopeType> SCOPE_FEATURES = ImmutableSet.of(InternalScope.FEATURES);
public static final Set<ScopeType> SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS = ImmutableSet.of(Scope.PROJECT, InternalScope.LOCAL_DEPS);
public static final Set<ScopeType> SCOPE_FULL_PROJECT_WITH_LOCAL_JARS = new ImmutableSet.Builder<ScopeType>().addAll(SCOPE_FULL_PROJECT).add(InternalScope.LOCAL_DEPS).build();
复制代码

常用的是SCOPE_FULL_PROJECT,代表所有Project。

确定了ContentType和Scope后就确定了该自定义Transform需要处理的资源流。比如CONTENT_CLASS和SCOPE_FULL_PROJECT表示了所有项目中java编译成的class组成的资源流。

isIncremental

指明该 Transform 是否支持增量编译。有时即使返回 true, 在某些情况下它还是会当作 false 返回。

transform

transform是一个空实现,input的内容将会打包成一个 TransformInvocation 对象。

TransformInvocation

看一下这个接口的定义:

public interface TransformInvocation {

    // 上下文
    @NonNull
    Context getContext();

    // transform 的输入/输出
    @NonNull
    Collection<TransformInput> getInputs();

     // 返回不被这个 transformation 消费的 input
    @NonNull Collection<TransformInput> getReferencedInputs();

    /**
     * Returns the list of secondary file changes since last. Only secondary files that this
     * transform can handle incrementally will be part of this change set.
     */
    @NonNull Collection<SecondaryInput> getSecondaryInputs();

    // 返回允许创建内容的 output provider
    @Nullable
    TransformOutputProvider getOutputProvider();

    boolean isIncremental();
}
复制代码

TransformInput

public interface TransformInput {
    // 表示 Jar 包
    @NonNull
    Collection<JarInput> getJarInputs();

    // 表示目录,包含 class 文件
    @NonNull
    Collection<DirectoryInput> getDirectoryInputs();
}
复制代码

TransformOutputProvider

public interface TransformOutputProvider {

    void deleteAll() throws IOException;

    // 根据 name、ContentType、QualifiedContent.Scope 返回对应的文件(jar / directory)
    @NonNull
    File getContentLocation(
            @NonNull String name,
            @NonNull Set<QualifiedContent.ContentType> types,
            @NonNull Set<? super QualifiedContent.Scope> scopes,
            @NonNull Format format);
}
复制代码

示例:注入代码

1.首先创建一个普通的Android工程。

2.自定义Gradle插件,示例采用buildSrc方式。

关于自定义 Gradle 插件的三种方式可以参考 自定义gradle插件

3.引用插件。

apply plugin: com.hearing.plugin.TransformPlugin
复制代码

在工程模块中引入插件后,在编译时可以看到相关日志,查看相关 class 文件,可以看到插入后的代码。

文中内容如有错误欢迎指出,共同进步!觉得不错的留个赞再走哈~

上一篇 下一篇

猜你喜欢

热点阅读