Migrating from the Deprecated Transform API to AsmClassVisitorFactory in Android Gradle Plugin 7.x
This article explains why the Transform API is deprecated in AGP 7.0, introduces the new AsmClassVisitorFactory‑based API, compares code examples, discusses performance benefits and drawbacks, and provides guidance for migrating Android Gradle plugins to the newer variant and artifact system.
Background
Our project currently uses AGP 4.0.2, which is five major versions behind the latest AGP 7.2.1. Gradle upgrades are inevitable, and although the AGP 7.0 upgrade was paused last year, we still need to learn the new features.
What is Transform
Transform API allows third‑party plugins to manipulate compiled class files before they are converted to dex. It abstracts away task generation and execution, letting developers focus on class processing.
Work timing : operates between the Class → Dex stage.
Processing objects : compiled class files, standard resources, local and remote JAR/AAR dependencies.
Transform task : each Transform corresponds to a Task with defined inputs and outputs.
Transform chain : TaskManager links TransformTasks so that the output of one becomes the input of the next.
Transform usage scenarios
Custom processing of compiled class files.
Reading compiled class files for analysis without modification.
Transform replacement API
The Transform API is deprecated because it is difficult to combine with other Gradle features, leading to performance issues. The replacement APIs live inside the androidComponents {} block and are provided from AGP 7.2 onward.
Example code
“Friendship tip: the following code is dense; readers with code‑intensity phobia can skip to comments.”
class ExamplePlugin : Plugin
{
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
// Add instrumentation per variant
println("variant.name--${variant.name}")
variant.instrumentation.transformClassesWith(
ExampleClassVisitorFactory::class.java,
InstrumentationScope.ALL
) {
it.writeToStdout.set(true)
}
variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
}
}
interface ExampleParams : InstrumentationParameters {
@get:Input
val writeToStdout: Property
}
abstract class ExampleClassVisitorFactory : AsmClassVisitorFactory
{
override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor {
return if (parameters.get().writeToStdout.get()) {
TraceClassVisitor(nextClassVisitor, PrintWriter(System.out))
} else {
TraceClassVisitor(nextClassVisitor, PrintWriter(File("trace_out")))
}
}
override fun isInstrumentable(classData: ClassData): Boolean {
return classData.className.startsWith("com.wuba.instrument")
}
}
}Officially, Transform Action is a possible alternative, but the demo shows that Google recommends AsmClassVisitorFactory.
AsmClassVisitorFactory
A factory to create class visitor objects to instrument classes.
The implementation must be an abstract class leaving parameters and instrumentationContext unimplemented, with an empty constructor.
Using AsmClassVisitorFactory can improve compilation speed by about 18% and reduce code size roughly fivefold.
interface AsmClassVisitorFactory
: Serializable {
@get:Nested
val parameters: Property
@get:Nested
val instrumentationContext: InstrumentationContext
fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor
fun isInstrumentable(classData: ClassData): Boolean
} interface ClassData {
/** Fully qualified name of the class. */
val className: String
/** List of the annotations the class has. */
val classAnnotations: List
/** List of all interfaces implemented by the class or its super‑classes. */
val interfaces: List
/** List of all super‑classes of the class. */
val superClasses: List
}ClassData is not part of ASM but provides enough information for most use‑cases.
New Extension
Since AGP 7.0, a new AndroidComponentsExtension replaces the old AppExtension . Custom transforms must now be registered on this extension, taking variant awareness into account.
TransformClassesWithAsmTask
This task maintains a list of AsmClassVisitorFactory instances and processes class files in a single I/O pass, delegating to a chain of ClassVisitor objects and finally writing with ClassWriter . This reduces I/O and explains the performance gain.
abstract val visitorsList: ListProperty
>
abstract val inputClassesDir: ConfigurableFileCollection
abstract val inputJarsDir: DirectoryProperty
abstract val inputJarsWithIdentity: JarsClasspathInputsWithIdentity
abstract val runtimeClasspath: ConfigurableFileCollection
abstract val bootClasspath: ConfigurableFileCollection
abstract val classesOutputDir: DirectoryProperty
abstract val jarsOutputDir: DirectoryPropertyCompared with the old Transform stream model, the new task eliminates the need for repeated I/O between transforms.
Advantages of AsmClassVisitorFactory
No need to write complex incremental build logic.
Significant build‑performance improvement.
Disadvantages of AsmClassVisitorFactory
Less flexible than the old Transform API; tied to ASM and cannot use Javassist or AspectJ.
Higher learning curve for ASM.
Stability of the new API
AsmClassVisitorFactory is stable in AGP 7.2.1 and can be used in production. AGP 8.0 will remove the old Transform API entirely.
AGP internal changes
From AGP 3.5.3 to 7.2.1, system transforms have gradually been replaced by tasks. By AGP 7.2.1, only custom transforms remain, and they are expected to disappear in AGP 8.0.
public void createPostCompilationTasks(@NonNull final VariantScope variantScope) {
// External transforms
List
customTransforms = extension.getTransforms();
// System transforms added here in older versions
// ...
}New Variant API
The new Variant and Artifact APIs expose a more stable way to interact with build artifacts. Example code shows how to register a task and wire its output to a variant’s artifact collection.
abstract class ToyPlugin : Plugin
{
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
val taskProvider = project.tasks.register(variant.name + "AddAsset", AddAssetTask::class.java) {
it.content.set("foo")
}
variant.artifacts.use(taskProvider).wireWith(AddAssetTask::outputDir).toAppendTo(MultipleArtifact.ASSETS)
}
}
}Industry plugin architecture
Frameworks like Booster and ByteX isolate the Transform API, making future AGP migrations easier. However, internal plugins still rely on the old API, and migration will be painful without a unified framework.
Personal thoughts
Should we develop our own plugin framework and define standards?
Should new plugins be built on ByteX or a custom framework that isolates Transform?
Is it necessary to consolidate existing internal plugins?
References
https://juejin.cn/post/7016147287889936397 https://juejin.cn/post/7056395437544046606 https://juejin.cn/post/7098752199575994405 https://blog.csdn.net/qq_33902817/article/details/122434538 https://developer.android.com/studio/build/extend-agp?hl=zh-cn https://juejin.cn/post/7105925343680135198 https://nebulae-pan.github.io/2021/12/25/Gradle7.0Transform%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88/
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.