产物转换 (Transforms)
源:Gradle 官方文档 - Artifact Transforms
Artifact Transform 允许你在依赖被使用前对其进行转换,实现自动化的产物处理。
Artifact Transform 概念
什么是 Artifact Transform
Artifact Transform:在依赖从仓库下载后、被任务使用前,对其进行转换的机制。
使用场景:
- 解压 JAR 文件
- 处理 AAR 中的资源
- Desugaring(脱糖)
- 代码混淆
- 资源压缩
为什么需要 Transform
传统方式问题:
kotlin
// ❌ 无法处理第三方依赖
tasks.register("processLibs") {
// 只能处理项目内部文件
// 无法处理 dependencies 中的 AAR/JAR
}Transform 优势:
- ✅ 自动处理所有依赖
- ✅ 按需执行
- ✅ 完美缓存支持
- ✅ 并行处理
Transform API
TransformAction 接口
kotlin
import org.gradle.api.artifacts.transform.TransformAction
import org.gradle.api.artifacts.transform.TransformOutputs
import org.gradle.api.artifacts.transform.TransformParameters
import org.gradle.api.artifacts.transform.InputArtifact
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Provider
abstract class UnzipTransform : TransformAction<TransformParameters.None> {
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val input = inputArtifact.get().asFile
val outputDir = outputs.dir(input.nameWithoutExtension)
// 解压逻辑
project.copy {
from(project.zipTree(input))
into(outputDir)
}
}
}注册 Transform
kotlin
dependencies {
registerTransform(UnzipTransform::class) {
from.attribute(Attribute.of("artifactType", String::class.java), "jar")
to.attribute(Attribute.of("artifactType", String::class.java), "unzipped-jar")
}
}参数配置
带参数的 Transform
kotlin
interface CompressionParameters : TransformParameters {
@get:Input
val compressionLevel: Property<Int>
}
abstract class CompressTransform : TransformAction<CompressionParameters> {
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val level = parameters.compressionLevel.get()
val input = inputArtifact.get().asFile
val output = outputs.file("${input.nameWithoutExtension}.compressed")
// 使用指定压缩级别
compress(input, output, level)
}
}注册:
kotlin
dependencies {
registerTransform(CompressTransform::class) {
from.attribute(ArtifactType.ARTIFACT_TYPE, "jar")
to.attribute(ArtifactType.ARTIFACT_TYPE, "compressed-jar")
parameters {
compressionLevel.set(9)
}
}
}实战案例
案例1:解压 JAR
kotlin
abstract class UnzipJarTransform : TransformAction<TransformParameters.None> {
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val jarFile = inputArtifact.get().asFile
val outputDir = outputs.dir(jarFile.nameWithoutExtension)
// 解压
ZipFile(jarFile).use { zip ->
zip.entries().asSequence().forEach { entry ->
if (!entry.isDirectory) {
val outputFile = File(outputDir, entry.name)
outputFile.parentFile?.mkdirs()
zip.getInputStream(entry).use { input ->
outputFile.outputStream().use { output ->
input.copyTo(output)
}
}
}
}
}
}
}
// 注册
dependencies {
registerTransform(UnzipJarTransform::class) {
from.attribute(ArtifactType.ARTIFACT_TYPE, "jar")
to.attribute(ArtifactType.ARTIFACT_TYPE, "classes")
}
}案例2:处理 AAR 资源
kotlin
interface ResourceTransformParameters : TransformParameters {
@get:Input
val optimize: Property<Boolean>
}
abstract class AarResourceTransform : TransformAction<ResourceTransformParameters> {
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val aar = inputArtifact.get().asFile
val outputDir = outputs.dir(aar.nameWithoutExtension)
// 解压 AAR
ZipFile(aar).use { zip ->
zip.entries().asSequence()
.filter { it.name.startsWith("res/") }
.forEach { entry ->
val file = File(outputDir, entry.name)
file.parentFile?.mkdirs()
zip.getInputStream(entry).use { input ->
if (parameters.optimize.get() && isImage(entry.name)) {
// 优化图片
optimizeImage(input, file)
} else {
file.outputStream().use { output ->
input.copyTo(output)
}
}
}
}
}
}
private fun isImage(name: String) =
name.endsWith(".png") || name.endsWith(".jpg")
private fun optimizeImage(input: InputStream, output: File) {
// 图片优化逻辑
}
}案例3:Minify Transform
kotlin
abstract class MinifyTransform : TransformAction<TransformParameters.None> {
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
@get:Classpath
abstract val classpath: ConfigurableFileCollection
override fun transform(outputs: TransformOutputs) {
val input = inputArtifact.get().asFile
val output = outputs.file("${input.nameWithoutExtension}-min.jar")
// 使用 ProGuard 或 R8
minifyJar(input, output, classpath.files)
}
private fun minifyJar(input: File, output: File, classpath: Set<File>) {
// Minify 逻辑
}
}缓存机制
Transform 缓存
自动缓存:Transform 的输出自动缓存。
缓存键:
- 输入文件内容
- Transform 参数
- Transform 实现类
示例:
kotlin
// 第一次执行
Transform: input.jar -> output/
Cache: MISS
// 第二次执行(相同输入)
Transform: input.jar -> output/
Cache: HIT禁用缓存
kotlin
@CacheableTransform // 默认可缓存
abstract class MyTransform : TransformAction<...>
// 如果不希望缓存
// 不添加 @CacheableTransform 注解Android 专用场景
Desugaring
AGP 内部使用:
kotlin
// AGP 自动注册的 Transform
// 将 Java 8+ 特性转换为 Java 7
abstract class DesugarTransform : TransformAction<...> {
override fun transform(outputs: TransformOutputs) {
// 脱糖逻辑
desugar(inputArtifact.get().asFile, outputs)
}
}D8/R8 Transform
kotlin
// 将 .class 转换为 .dex
abstract class DexTransform : TransformAction<...> {
override fun transform(outputs: TransformOutputs) {
val classes = inputArtifact.get().asFile
val dex = outputs.file("classes.dex")
// D8/R8
runD8(classes, dex)
}
}最佳实践
明确输入输出类型:
kotlin
from.attribute(ArtifactType.ARTIFACT_TYPE, "jar")
to.attribute(ArtifactType.ARTIFACT_TYPE, "classes")使用参数传递配置:
kotlin
interface MyParameters : TransformParameters {
val config: Property<String>
}缓存友好:
kotlin
@CacheableTransform
abstract class MyTransform : TransformAction<...>错误处理:
kotlin
override fun transform(outputs: TransformOutputs) {
try {
// Transform 逻辑
} catch (e: Exception) {
logger.error("Transform failed", e)
throw e
}
}并行处理:
kotlin
// Transform 自动并行执行
// 无需手动配置