引言
早期,AGP引入了Transform API,它在很长一段时间内是实现字节码操作等编译期修改的主要手段。然而,随着Gradle自身的发展和对构建性能、缓存效率要求的提高,Gradle核心引入了更现代、更强大的Artifact Transforms API,其中的核心实现就是TransformAction。
很多开发者可能对这两个API感到困惑:它们是做什么的?有什么区别?我应该用哪个?本文将深入探讨这两个API,理清它们的概念、工作方式、优劣势,并最终给出明确的建议。
核心目标: 理解Transform和TransformAction的作用、差异,并了解为何TransformAction是未来的方向。
“老前辈”:AGP的Transform API (com.android.build.api.transform.Transform)
Transform API是早期由Android Gradle插件(AGP)引入的,专门用于在Android构建流程中处理字节码和资源。
核心理念:
- 集中处理:Transform被设计为在特定的构建阶段(通常是所有项目和库的类编译完成后,但在Dexing之前)运行。它会接收到 所有 作用域(Scopes)内的输入(类文件、资源文件),进行处理,然后输出结果。
- AGP特定:这是AGP提供的API,与Gradle核心的依赖管理和任务系统耦合相对较松。
工作机制:
- 注册:通过project.android.registerTransform(yourTransform)注册自定义的Transform实现。
- 输入定义:
- getInputTypes(): 定义你想处理的内容类型,如CLASSES(.class文件)、RESOURCES。
- getScopes(): 定义你想处理的范围,如PROJECT(当前项目)、SUB_PROJECTS(子项目)、EXTERNAL_LIBRARIES(外部依赖库)等。
- getReferencedScopes(): 定义只读的引用范围,这些范围的内容不会被转换,但可以用来做分析。
- 处理逻辑:
- transform(TransformInvocation invocation): 这是核心方法。
- TransformInvocation 提供了输入 (getInputs()) 和输出 (getOutputProvider())。
- getInputs() 返回 Collection<TransformInput>,每个TransformInput包含该范围内的目录输入 (getDirectoryInputs()) 和JAR包输入 (getJarInputs())。
- 你需要遍历所有输入,对.class文件或资源进行处理,然后将处理后的结果写入TransformOutputProvider指定的输出位置。
- 增量构建:isIncremental() 返回 true 时,TransformInvocation 会提供每个输入的变更状态(ADDED, CHANGED, REMOVED, NOTCHANGED),允许你只处理发生变化的文件,以提高构建速度。
示例:使用Transform复制JAR文件
假设我们想实现一个简单的Transform,它接收所有项目和库的类文件(通常是JAR包形式),然后将这些JAR包复制到指定的输出目录,并打印日志。
- 实现 MyLegacyTransform:
- 在 app/build.gradle 中注册:
示例分析:
- 注册:直接在android闭包中使用registerTransform。
- 输入范围:通过getInputTypes和getScopes定义了广泛的输入范围(所有项目的类)。
- 处理逻辑:transform方法接收一个TransformInvocation,包含了 所有 符合范围的输入。你需要手动遍历inputs、directoryInputs、jarInputs,并使用outputProvider来获取对应的输出位置。
- 粒度:处理是“批发式”的,一次性拿到所有输入进行处理。
Transform API的局限性:
- 性能瓶颈(“全量”倾向):虽然支持增量,但Transform的设计倾向于一次性接收 所有 相关输入。当依赖众多或项目庞大时,即使只有少量文件变动,管理和调度这些输入也可能成为瓶颈。其增量是基于文件的,但作用域仍然很大。
- 并行度有限:由于Transform通常作为一个大的、集中的步骤运行,其内部并行处理逻辑需要开发者自己精细管理,Gradle难以在Transform之间或Transform内部进行有效的任务级并行调度。
- 配置缓存(Configuration Cache)兼容性差:Transform API的设计年代早于Gradle的配置缓存特性,其API设计和实现方式(如直接访问Project对象、闭包配置等)使得它很难与配置缓存完全兼容,影响了后续构建的启动速度。
- 与Gradle依赖管理解耦:Transform是AGP层面的概念,它在依赖图解析完成后才介入,与Gradle强大的依赖项转换(Artifact Transforms)机制脱节。
- API已标记弃用:随着AGP版本的迭代(尤其是AGP 7.0之后),Transform API已被官方标记为弃用,推荐使用新的API。
“新势力”:Gradle的Artifact Transforms API (org.gradle.api.artifacts.transform.TransformAction)
为了克服Transform的局限性,并提供更通用、更高效、与Gradle核心机制结合更紧密的解决方案,Gradle引入了Artifact Transforms API。TransformAction是这个API的核心接口。
核心理念:
- 精细化处理:Artifact Transforms作用于依赖关系图中的 单个构件(Artifact)。当某个任务需要一个特定类型的构件,但依赖图中只存在另一种类型的构件时,Gradle可以自动查找并应用一个注册的TransformAction来完成转换。
- Gradle原生:这是Gradle核心提供的API,深度集成到依赖管理和任务执行引擎中。
- 按需转换:转换只在需要时发生,并且针对单个输入构件进行。
工作机制:
- 定义TransformAction:
- 创建一个实现TransformAction<T extends TransformParameters>的类。T是可选的参数类型,用于传递配置信息。
- 使用@InputArtifact注解标记输入构件(通常是Provider<FileSystemLocation>类型,代表一个文件或目录)。
- 使用@OutputDirectory或@OutputFile注解标记输出。
- 实现transform(TransformOutputs outputs)方法,在该方法中:
- 获取输入文件/目录(通过@InputArtifact注解的字段)。
- 执行处理逻辑(如字节码操作)。
- 将结果写入outputs提供的输出位置(如outputs.dir(outputDir)或outputs.file(outputFile))。
- 注册TransformAction:
- 通过project.dependencies.registerTransform(YourTransformAction.class, spec -> { ... })来注册。
- 在spec中配置:
- from.attribute(...): 定义输入构件必须满足的属性(例如,artifactType = "jar")。
- to.attribute(...): 定义转换后输出构件的属性(例如,artifactType = "processed-jar")。
- parameters { ... }: 如果TransformAction定义了参数类型T,在这里配置参数。
- 消费转换后的构件:
- 在需要使用转换后构件的任务(或其他地方)中,声明对具有 目标属性(to属性)的依赖项的需求。例如,创建一个Configuration,其属性要求artifactType = "processed-jar"。
- Gradle的依赖管理引擎会自动发现这个需求,找到匹配的TransformAction,并为依赖图中的每个原始构件(满足from属性)安排执行转换。
示例:使用TransformAction复制JAR文件
现在,我们用TransformAction来实现同样的目标:复制JAR文件并打印日志。
- 定义属性(可选但推荐):为了区分原始JAR和处理后的JAR,我们可以定义或使用属性。这里我们简单地改变artifactType。
- 实现 MyNewJarTransformAction:
- 在 app/build.gradle (或插件) 中注册:
示例分析:
- 注册:在dependencies块中使用registerTransform。注册时核心是定义from和to的属性(Attributes)。Gradle会根据这些属性将Action“挂载”到依赖图的边上。
- 输入范围:Action本身不定义全局范围。它只关心传递给它的单个输入构件 (@InputArtifact)。哪个构件会被处理,取决于依赖关系和请求方(消费者)声明的属性。
- 处理逻辑:transform方法接收一个TransformOutputs对象。输入通过@InputArtifact注解的字段获取。输出通过调用outputs.file()或outputs.dir()来声明和获取位置。
- 粒度:处理是“精细化”的,针对单个构件执行。Gradle可以并行调度许多这样的独立Action。
TransformAction的优势:
- 高性能与高并行度:
- 细粒度:转换针对单个构件,任务更小、更独立。
- 按需执行:只有当需要特定类型的输出时才执行转换。
- 天然并行:Gradle可以轻松地并行执行不同构件的转换任务。
- 出色的缓存支持:
- 构建缓存(Build Cache):由于输入输出明确且任务独立,转换结果极易被缓存和复用。
- 配置缓存(Configuration Cache):TransformAction及其注册方式被设计为与配置缓存兼容,显著提升后续构建的配置阶段速度。
- 与依赖管理深度集成:转换成为依赖解析的一部分,逻辑更清晰,配置更灵活。你可以基于任意属性(Attribute)进行转换匹配。
- Gradle核心API,更稳定:作为Gradle核心功能,API更稳定,且会持续获得改进和支持。
- 解耦:将转换逻辑与特定的插件(如AGP)解耦,使其更通用。
Transform vs. TransformAction:关键差异对比
特性 | Transform (示例: MyLegacyTransform) | TransformAction (示例: MyNewJarTransformAction) |
触发方式 | 由AGP在固定阶段调用,处理所有符合条件的输入。 | 由Gradle依赖解析引擎根据属性匹配按需触发,针对单个构件。 |
输入处理 | transform方法接收所有输入的集合 (TransformInvocation)。 | transform方法处理由@InputArtifact注入的单个输入。 |
输出管理 | 使用TransformOutputProvider获取输出位置,需手动管理命名冲突。 | 使用TransformOutputs注册输出,Gradle管理路径和生命周期。 |
注册位置 | android { registerTransform(...) } | dependencies { registerTransform(...) } |
核心机制 | AGP特定的构建阶段插入点。 | Gradle核心的基于属性的构件转换机制。 |
并行与缓存 | 并行性有限,对缓存支持不如新API友好。 | 天然支持细粒度并行,与Gradle缓存(构建/配置)高度兼容。 |
关注点 | 关注“在某个阶段对一批文件做什么”。 | 关注“如何将具有A属性的构件转换为具有B属性的构件”。 |
总结
Gradle的Transform API曾是Android构建过程中实现字节码插桩等功能的功臣,但其设计限制了构建性能和对现代Gradle特性的兼容性。Artifact Transforms API及其核心TransformAction代表了更现代、更高效、更灵活的构件处理方式。它通过细粒度、按需执行、深度集成依赖管理和对缓存的良好支持,显著提升了Gradle构建的性能和可维护性。
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/80b59db3-88e3-4b2f-94d1-517c771a2caa
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts