Android Gradle Transform 简介

回顾一下 Transform 相关的知识。

什么是 Transform

Android App构建流程

上图来自 Android Developers。不过上图缺失一些细节,我们稍作展开。

我们知道,Android应用是Java或Kotlin语言写的。简单起见,我们只考虑Java。以Java语言编写的 Source Code,经Java编译器编译成 Java字节码(.class文件)。Java编译器的编译产物是.class,那么上图的中dex文件是怎么来的呢?

Dex之于Android,类似于jar之于Java。dex中保存的也是字节码,不过需要说明的是这里的字节码并非标准的Java字节码,而是专门为Android平台设计的字节码

所以完整的编译流程是这样的:multiple .java files –> multiple .classes files –> a single .dex file

更多细节可以参考以下资料:

Transform 的作用

从Android App构建流程可以看到,构建过程中需要对代码、资源文件和AIDL做各种处理。于是,Android Gradle插件提供了 Transform 以及相应的API。

以下内容翻译自 Transform (Android Gradle API)

Transform 类代表构建过程中的一个转换操作,用于处理中间构建产物。对于每个新增的 transform,都会创建一个新的 task。添加新的 transform 时需要处理好task之间的依赖。transfrom 的输出由其他 transform 消费,而相关的 task 则自动关联到一起。

Transform 需要明确它作用于哪个 (content,scope),以及它会生成什么 (content)

Transform 接收一个 TransformInput 集合作为输入,它由 JarInputDirectoryInput 组成。两者都需要提供关于特定内容的 QualifiedContent.ScopeQualifiedContent.ContentType 信息。

Transform 的输出由 TransformOutputProvider 处理,它可以创建新的自包含的内容,这些内容有自己的 Scopes 和 Content Types。TransformInput/Output 处理的内容由 transform 系统管理,其路径不可配置。

It is best practice to write into as many outputs as Jar/Folder Inputs have been received by the transform. 将所有输入处理后放到同一个输出,会让下游的 transform 无法处理指定的 scopes。(Combining all the inputs into a single output prevents downstream transform from processing limited scopes.)

虽然可以通过文件名后缀来区分不同的 Content Types,但无法通过文件名后缀来区分 Scopes。因此如果一个 transform 要求某种 Scope,但 Output 中包含的 Scope 种类多于要求的,构建会失败。(While it’s possible to differentiate different Content Types by file extension, it’s not possible to do so for Scopes. Therefore if a transform request a Scope but the only available Output contains more than the requested Scope, the build will fail.)
If a transform request a single content type but the only available content includes more than the requested type, the input file/folder will contain all the files of all the types, but the transform should only read, process and output the type(s) it requested.

另外,transform 还可指定辅助的输入输出目录(secondary inputs/outputs)。上下游的 transform 不处理这个目录,目录中的内容也不局限为 transform 能处理的文件类型,它们可以是任意内容。由每个 transform 来管理这些文件,并且保证在 transform 被调用前生成文件内容. This is done through additional parameters when register the transform.
These secondary inputs/outputs allow a transform to read but not process any content. This can be achieved by having getScopes() return an empty list and use getReferencedScopes() to indicate what to read instead.

(太难翻译了!)

简单来说,

  • Transform API 是由 Android Gradle Plugin 插件提供的,而非 Gradle 官方提供的,
  • Transform API 是一套用来修改构建产物的标准API

Android App 打包过程中的代码混淆、desugar等过程,其实都是一个个的 Transform。

使用 Transform 的场景

  • 对编译产生的class文件做自定义的处理。
  • 读取编译产生的class文件,并不修改

示例一 - Instant run

示例二 - Network Profiler

TODO

Transform API 介绍

自定义的 transform 要继承自 Transform

1
2
3
4
5
6
7
8
9
public abstract class Transform {
public abstract String getName(); // 自定义 Transform的名字

public abstract Set<ContentType> getInputTypes(); // 自定义 Transform 的输入类型

public abstract Set<? super Scope> getScopes();

public abstract boolean isIncremental(); // 是否支持增量编译
}

输入类型

getInputTypes() 指定 transform 的输入类型。

  • DefaultContentType 包括 CLASSESRESOURCES 两种类型
  • ExtendedContentType 包括 DEXNATIVE_LIBSCLASSES_ENHANCEDDATA_BINDING 等几种类型。

输入范围

getScopes() 指定 transform 的输入范围。

1
2
3
4
5
6
7
8
9
10
11
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),

注意 getScopes()getReferencedScopes() 的区别。前者用于表示将要消费的范围,后者表示引用的范围(可查看内容、但并不消费)。所以,如果一个Transform不想处理任何输入,只是想查看输入的内容,那么只需在getScopes()返回一个空集合,在getReferencedScopes()返回想要接收的范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Returns the scope(s) of the Transform. This indicates which scopes the transform consumes.
*/
@NonNull
public abstract Set<? super Scope> getScopes();

/**
* Returns the referenced scope(s) for the Transform. These scopes are not consumed by
* the Transform. They are provided as inputs, but are still available as inputs for
* other Transforms to consume.
*
* <p>The default implementation returns an empty Set.
*/
@NonNull
public Set<? super Scope> getReferencedScopes() {
return ImmutableSet.of();
}

如何自定义 Transform

时序图如下:
-w809

类图如下:
-w824

FAQ

  • Q: 有了ASM等字节码框架,为什么还需要 Transform?
  • A: ASM并不能直接运行于Android系统。ASM主要用于Android应用构建流程中修改Java字节码(Transform)

参考