字节码处理流水线
Transform API
从 Android Gradle Plugin 1.5.0-beta1 开始,为了简化注入自定义 class 的操作,Android 提供了 Transform API在新窗口打开,允许第三方插件在 class 文件被转换成 dex 之前对其进行修改,在此之前,如果要实现同样的操作,只能通过 Hook Task 的方式才能做到,由于 Booster 仅支持 Android Gradle Plugin 3.0.0 及以上版本,所以,选择了采用 Transform API在新窗口打开 的方式,通过向 Android Extension 注册 BoosterTransform在新窗口打开 来处理 class 字节码。
Transform Pipeline
在 Android Gradle Plugin 中,通过 Transform API在新窗口打开 注册的 Transform
最终会以 Transform Pipeline 的形式来执行,而 Transform Pipeline 是由一系列 TransformStream
和 Transform
所组成,而 Transform
通过 TransformTask
来挂载到 Gradle 任务系统,并进行执行,以 Android Gradle Plugin 3.0.0+ 为例,Transform Pipeline 在整个构建流程中的位置如下图所示:
Transform Stream
每个 Transform Stream 都有特定的类型(Content Types)和作用域(Scopes)。
Content Types
内容类型 | 描述 |
---|---|
QualifiedContent.DefaultContentType.CLASSES | class 或者 JAR 文件 |
QualifiedContent.DefaultContentType.RESOURCES | Java 资源 |
ExtendedContentType.DEX | dex 文件 |
ExtendedContentType.NATIVE_LIBS | so 动态库 |
ExtendedContentType.CLASSES_ENHANCED | Instant Run 更新的 class |
ExtendedContentType.DATA_BINDING | data binding 产物 |
ExtendedContentType.DEX_ARCHIVE | dex 存档文件,每个 class 对应一个单独的 dex 文件 |
由于 Booster 只关注于字节码的处理,所以 BoosterTransform在新窗口打开 所接收的输入内容类型为 QualifiedContent.DefaultContentType.CLASSES
。
Scopes
作用域 | 描述 |
---|---|
QualifiedContent.Scope.PROJECT | 仅作用于当前工程 |
QualifiedContent.Scope.SUB_PROJECTS | 作用于所有子工程 |
QualifiedContent.Scope.EXTERNAL_LIBRARIES | 作用于外部依赖库 |
QualifiedContent.Scope.TESTED_CODE | 作用于当前 variant 的测试代码 |
QualifiedContent.Scope.PROVIDED_ONLY | 仅作用于 provided 的依赖 |
QualifiedContent.Scope.PROJECT_LOCAL_DEPS | 作用于工程的本地依赖包 |
QualifiedContent.Scope.SUB_PROJECTS_LOCAL_DEPS | 作用于所有子工程的本地依赖包 |
InternalScope.MAIN_SPLIT | 作用于 Instant Run 模式下主包 |
InternalScope.LOCAL_DEPS | 作用于本地依赖库 |
InternalScope.FEATURES | 作用于 Dynamic Feature 工程 |
TransformManager
中定义了一些常用的 Scopes:
作用域 | 描述 |
---|---|
TransformManager.SCOPE_FULL_PROJECT | 作用于整个工程,包括当前工程、所有子工程及外部依赖库 |
TransformManager.PROJECT_ONLY | 仅作用于当前工程,不包括子工程或外部依赖库 |
TransformManager.SCOPE_FULL_WITH_FEATURES | 作用于整个工程以及 Dynamic Feature 工程 |
TransformManager.SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS | 作用于当前工程以及本地依赖库 |
对于 APP 工程而言, BoosterTransform在新窗口打开 的作用域为 TransformManager.SCOPE_FULL_PROJECT
,不仅要处理当前工程自身的字节码,还要处理其依赖的本地库或者外部库等;对于 Library 工程而言,其作用域为 TransformManager.PROJECT_ONLY
;对于 dynamic-feature 工程而言,则为 TransformManager.SCOPE_FULL_WITH_FEATURES
Original Stream & Intermediate Stream
根据 Transform Stream 的起源,又将其分为原始流(Original Stream)和中间流(Intermediate Stream),顾名思义,原始流是由 Android Gradle Plugin 创建,然后给其它的 Transform
来消费其中的内容,而自定义的 Transform
消费后产生的 Transform Stream 则为中间流(非原始的)。
当我们通过自定义 Transform
来处理构建中间产物,可以从两个维度来关注:
- Content Types:即
Transform
要处理的内容是什么类型,可以是多种类型; - Scopes:即
Transform
要处理哪里的内容,可以是多个作用域;
当通过 Transform API 注册了自定义的 Transform
后,Transform Manager 就会根据 Transform
关注的 Content Types 和 Scopes 从现有的 Transform Stream 列表中选择对类型和作用域的流作为该 Transform
的输入,而该 Transform
的输出(Intermediate Stream)则作为下一个 Transform
的输入,每个 Transform
通过 Transform Stream 连接起来,这样就形成了管道,而 BoosterTransform在新窗口打开 则是 Transform Pipeline 中的一个节点,如下图所示:
Booster Transformer Pipeline
为了简化对字节码的操作以及提升模块的可复用性,Booster 内部设计了一套类似于 Android Transform Pipeline 的 Transformer Pipeline 框架来实现字节码流式操作, Transformer
作为字节码的处理单元,开发者可以根据个人喜好来选择 JavassistTransformer
、AsmTransformer
或者基于其它字节码操作框架自定义 Transformer
,在 JavassistTransformer
和 AsmTransformer
内部,还有一层 Class Transformer Pipeline,每个 ClassTransformer 作为 class 的处理单元,Transformer Pipeline 的结构如下图所示:
并行 Transformer Pipeline
为了最大限度的提升构建速度,Booster 为每一个输入(JarInput
或 DirectoryInput
)创建了一条独立的 Transformer Pipeline,所有 Transformer Pipeline 共享 Transformer
。
调整 Transformer 在 Pipeline 中的顺序
默认情况下,Booster 是不保证 Transformer
的执行顺序的,这样的好处是减少 Transformer
之间的依赖,降低 Transform Pipeline 的复杂度,但总有一些特殊情况存在,为了满足 Transformer
的顺序可调整,Booster 提供了通过在 Transformer
上标注 @Priority在新窗口打开 的方式为 Transformer
指定优先级(值越小优先级越高),例如:
@Priority(Int.MAX_VALUE)
@AutoService(ClassTransformer::class)
class HighestPriorityTransformer : ClassTransformer {
override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
// do something...
return super.transform(context, klass)
}
}
2
3
4
5
6
7
8
9
10
@Priority
同样适用于调整VariantProcessor
的执行顺序
上帝视角
至此,我们已经了解了 Transform Pipeline 的内部实现细节,如何自定义一个 Transform
只对当前 Transform Pipeline 中的内容进行观察而不修改呢?这个问题就留给本书的读者吧。