Skip to content

产物转换 (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 自动并行执行
// 无需手动配置