# 字节码处理流水线

# 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 是由一系列 TransformStreamTransform 所组成,而 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 来处理构建中间产物,可以从两个维度来关注:

  1. Content Types:即 Transform 要处理的内容是什么类型,可以是多种类型;
  2. Scopes:即 Transform 要处理哪里的内容,可以是多个作用域;

当通过 Transform API 注册了自定义的 Transform 后,Transform Manager 就会根据 Transform 关注的 Content TypesScopes 从现有的 Transform Stream 列表中选择对类型和作用域的流作为该 Transform 的输入,而该 Transform 的输出(Intermediate Stream)则作为下一个 Transform 的输入,每个 Transform 通过 Transform Stream 连接起来,这样就形成了管道,而 BoosterTransform 则是 Transform Pipeline 中的一个节点,如下图所示:

Transform Pipeline

# Booster Transformer Pipeline

为了简化对字节码的操作以及提升模块的可复用性,Booster 内部设计了一套类似于 Android Transform PipelineTransformer Pipeline 框架来实现字节码流式操作, Transformer 作为字节码的处理单元,开发者可以根据个人喜好来选择 JavassistTransformerAsmTransformer 或者基于其它字节码操作框架自定义 Transformer,在 JavassistTransformerAsmTransformer 内部,还有一层 Class Transformer Pipeline,每个 ClassTransformer 作为 class 的处理单元,Transformer Pipeline 的结构如下图所示:

Booster Transformer Pipeline

# 并行 Transformer Pipeline

为了最大限度的提升构建速度,Booster 为每一个输入(JarInputDirectoryInput)创建了一条独立的 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)
    }

}

@Priority 同样适用于调整 VariantProcessor 的执行顺序

# 上帝视角

至此,我们已经了解了 Transform Pipeline 的内部实现细节,如何自定义一个 Transform 只对当前 Transform Pipeline 中的内容进行观察而不修改呢?这个问题就留给本书的读者吧。