// Option 1.
// Add `mavenCentral` to `pluginManagement{}` on `settings.gradle.kts` (or the root `build.gradle.kts`),
// and then the caliper plugin id.
pluginManagement {
repositories {
...
mavenCentral()
}
plugins {
...
id("com.google.devtools.ksp") version "1.7.22-1.0.8" apply false
id("me.2bab.caliper") version "0.2.2" apply false
}
}
// Option 2.
// Using classic `buildscript{}` block in root build.gradle.kts.
buildscript {
repositories {
...
mavenCentral()
}
dependencies {
...
classpath("com.google.devtools.ksp:symbol-processing-gradle-plugin:1.7.22-1.0.8")
classpath("me.2bab:caliper-gradle-plugin:0.2.2")
}
}plugins {
id("com.android.application")
...
id("me.2bab.caliper")
}
caliper {
// Main feature flags (Mandatory).
// Can not be lazily set, it's valid only if you call it before "afterEvaluate{}".
enableByVariant { variant ->
// With below snippet, only "FullDebug" variant will be interacted with Caliper.
// variant.buildType == "debug" && variant.flavorName == "full"
true
}
}Now imagine you have a class in your library module, and you want to intercept the
method commonMethodReturnsString().
// ./sample/library/.../LibrarySampleClass.kt
class LibrarySampleClass {
fun commonMethodReturnsString(): String {
return "commonMethodReturnsString"
}
}0x1. Create a new Android Library module to host our proxy rules.
// ./sample/custom-proxy/build.gradle.kts
plugins {
id("com.android.library")
kotlin("android")
id("com.google.devtools.ksp") // Apply the KSP plugin ahead of Caliper
id("me.2bab.caliper")
}
android {...}
dependencies {
ksp("me.2bab:caliper-annotation-processor:$latestVersion")
implementation(project(":library"))
} 0x2. Create a new class named CustomProxy.kt in the src/main/kotlin folder.
package me.xx2bab.caliper.sample.customproxy
import me.xx2bab.caliper.anno.ASMOpcodes
import me.xx2bab.caliper.anno.CaliperMethodProxy
import me.xx2bab.caliper.sample.library.LibrarySampleClass
object CustomProxy { // ①
@CaliperMethodProxy( // ②
className = "me/xx2bab/caliper/sample/library/LibrarySampleClass",
methodName = "commonMethodReturnsString",
opcode = ASMOpcodes.INVOKEVIRTUAL
)
@JvmStatic // ③
fun commonMethodReturnsString(lib: LibrarySampleClass) = "CustomProxy" // ④
}- ① The class must be an
objectclass when writing in Kotlin. - ② The annotation
@CaliperMethodProxyis used to mark the method as a proxy.classNameis the full name of the class to be intercepted whose package name is separated by slash.methodNameis the name of the method to be intercepted.opcodeis the operation-code of the method to be intercepted, for example:INVOKEVIRTUALfor non-static method.INVOKESTATICfor static method.GETSTATICfor static field.- ... and so on. More details can be found in ASM Opcodes.
- Caplier supports 3 types of proxy methods:
@CaliperMethodProxyfor method interception.@CaliperFieldProxyfor field interception.@CaliperClassProxyfor class interception.
- ③ The method must be
@JvmStaticclass when writing in Kotlin. - ④ The method must have
- the same signature as the original method when it's a static method.
- the same signature as the original method, except the first parameter will be the instance of
the class, when it's a non-static method. For example, if the original method
is
fun commonMethodReturnsString(): String, then the proxy method should befun commonMethodReturnsString(lib: LibrarySampleClass): String.
0x3. Now go back to the app module, and add the custom-proxy module as a dependency
with caliper configuration.
caliper(project(":custom-proxy"))After that, you can run the app and see the result. The toast message below should be CustomProxy.
Toast.makeText(
this,
LibrarySampleClass().commonMethodReturnsString(),
Toast.LENGTH_SHORT
).show()The step 3 is a bit tedious(however empowering everyone to custom their own proxies), so we provide some pre-packaged proxies for you to use.
caliper("me.2bab:caliper-runtime-privacy:$latestVersion")
caliper("me.2bab:caliper-runtime-battery-optim:$latestVersion")More details can be found in caliper-runtime-privacy and caliper-runtime-battery-optim.
You can also leverage the Caliper class to do some runtime operations. For example, print the signature of all proxied calling in the app.
Caliper.register(object : SignatureVisitor {
override fun visit(
className: String,
elementName: String,
parameterNames: Array<String>,
parameterValues: Array<Any>
) {
println("$className->$elementName")
}
})