Standalone Transformer

In daily development, we may need to scan certain JAR files, class files, or artifacts from the Android Transform Pipeline outside of the Gradle environment to obtain some results. For this purpose, Booster provides a series of utility classes and extension methods to help developers improve efficiency:

Runtime Instrumentation

In a Java environment, you might need to modify certain Class at runtime. We can easily implement this using Transformer.

Custom ClassLoader

class TransformerClassLoader : URLClassLoader {

    private val transformer: Transformer

    constructor(
            delegate: URLClassLoader,
            factory: (ClassLoader) -> Transformer
    ) : super(delegate.urLs) {
        this.transformer = factory(this)
    }

    constructor(
            delegate: URLClassLoader,
            factory: (ClassLoader, Iterable<Transformer>) -> Transformer,
            vararg transformer: Transformer
    ) : super(delegate.urLs) {
        this.transformer = factory(this, transformer.asIterable())
    }

    private val classpath: Collection<File> by lazy {
        this.urLs.map { File(it.path) }
    }

    private val context: TransformContext by lazy {
        object : AbstractTransformContext(javaClass.name, javaClass.name, emptyList(), classpath, classpath) {}
    }

    override fun findClass(name: String): Class<*> {
        val bytecode = transformer.run {
            try {
                onPreTransform(context)
                getResourceAsStream("${name.replace('.', '/')}.class")?.use(InputStream::readBytes)?.let {
                    transform(context, it)
                } ?: throw IOException("Read class $name failed")
            } finally {
                onPostTransform(context)
            }
        }

        return defineClass(name, bytecode, 0, bytecode.size)
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

Using ASM

val delegate = Thread.currentThread().contextClassLoader as URLClassLoader
val tcl = TransformerClassLoader(delegate) {
    AsmTransformer(it)
}
Class.forName("io.johnsonlee.booster.SimpleClass", tcl)
1
2
3
4
5

Using Javassist

val delegate = Thread.currentThread().contextClassLoader as URLClassLoader
val tcl = TransformerClassLoader(delegate) {
    JavassistTransformer(it)
}
Class.forName("io.johnsonlee.booster.SimpleClass", tcl)
1
2
3
4
5

Analysing Intermediate Artifacts

Using TransformHelperopen in new window, we can easily scan the artifacts of the Android Transform Pipeline:

val variant = "debug"
val input = File("build").file("intermediates", "transforms", "booster", variant)
val output = File(System.getProperty("java.io.tmpdir"))

TransformHelper(input).transform(output, AsmTransformer(object : ClassTransformer {
    override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
        println(klass.name)
        return klass
    }
}))
1
2
3
4
5
6
7
8
9
10

Analysing JAR File

Using the extension methods provided above, we can easily scan class files in a JAR file:

File("some.jar").transform(File("out")) { bytecode ->
    val klass = bytecode.asClassNode()
    println(klass.name)
    bytecode
}
1
2
3
4
5

Or

JarFile("some.jar").use { jar ->
    jar.entries().iterator().forEach { entry ->
        jar.transform(entry.name) { klass ->
            println(klass.name)
        }
    }
}
1
2
3
4
5
6
7

Analysing Class File

val klass = File("Some.class").asClassNode()
println(klass.name)
1
2