Replacing Xposed with a Gradle Plugin for Automated Android Privacy Checks
This article compares the traditional Xposed‑based dynamic privacy checker with a new Gradle‑plugin instrumentation approach, explains the design and implementation of the Transform API‑driven solution, shows performance metrics, provides usage instructions, and outlines automated testing and future improvements for Android apps.
Background
Privacy protection is critical in mobile app development. The original solution relied on the Xposed framework to intercept method calls at runtime, but it required specific phone models, complex installation steps, and could not be integrated into automated test pipelines.
Comparison of Two Solutions
2.1 Xposed Implementation
High device‑model dependency; only works on phones that support Xposed.
Complex setup involving multiple apps and configurations.
Incompatible with automated testing environments.
2.2 Instrumentation (Gradle Plugin) Implementation
Device‑agnostic; works on any Android phone.
Simple integration—no extra software installation required.
Can be incorporated into CI/CD pipelines for automated testing.
Solution Implementation
3.1 Overview
The new approach uses the Gradle TransformApi to scan all class files, locate privacy‑related methods with the ASM library, and inject code that records stack traces. When the user consents to the privacy policy, the collected data is written to a result.json file for further analysis.
3.2 Design Details
The implementation consists of two modules: gradle_plugin: a custom Gradle plugin that processes class files during the build. privacy_check: defines the code to be injected.
Key classes include:
PrivacyPlugin : registers the PrivacyClassVisitorFactory for each variant.
val androidComponents = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
val extension = project.extensions.create("privacyCheck", PrivacyExtension::class.java)
androidComponents.onVariants { variant ->
if (!extension.enable) {
println("privacyCheck disable")
return@onVariants
}
variant.instrumentation.transformClassesWith(
PrivacyClassVisitorFactory::class.java,
InstrumentationScope.ALL
) {}
variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
}PrivacyClassVisitorFactory : extends AsmClassVisitorFactory, creates a PrivacyClassVisitor for each class and skips irrelevant classes.
override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor {
return PrivacyClassVisotor(instrumentationContext.apiVersion.get(), nextClassVisitor)
}
override fun isInstrumentable(classData: ClassData): Boolean {
return !PrivacyPluginUtil.ignoreClass(classData.className)
}PrivacyClassVisitor : visits each method, uses PrivacyMethodVisitor (which extends ASM AdviceAdapter) to insert calls to PrivacyCollectUtil.appendData whenever a privacy method is detected.
override fun visitMethodInsn(opcodeAndSource: Int, owner: String?, nameInsn: String?, descriptor: String?, isInterface: Boolean) {
privacyMethodList.forEach { classMethod ->
if (owner == classMethod.className && nameInsn == classMethod.methodName) {
mv.visitLdcInsn("${className}#${name}")
mv.visitLdcInsn("${owner.replace("/", ".")}#${nameInsn}")
mv.visitMethodInsn(INVOKESTATIC, "com/xx/privacycheck/PrivacyCollectUtil", "appendData",
"(Ljava/lang/String;Ljava/lang/String;)V", false)
}
}
super.visitMethodInsn(opcodeAndSource, owner, nameInsn, descriptor, isInterface)
}3.3 Plugin Performance
Using the plugin, a full compilation of the sample project takes about 5 seconds. After modifying a single class and layout, incremental compilation drops to 2.2 seconds, demonstrating the low overhead of the instrumentation.
Plugin Usage
To enable the plugin, add the following to app/build.gradle.kts and set enable = true. The plugin should only be activated in test builds to avoid affecting end‑users.
plugins {
id("com.xx.privacycheck")
}
android {
privacyCheck {
enable = true
}
}During runtime, the privacy check runs only in debug builds:
if (BuildConfig.DEBUG) {
PrivacyCollectUtil.stopCollect(this@PreProcessingActivity)
}Automated Testing
A Python script using uiautomator2 drives the device: it installs the APK, clicks the consent button, and extracts the generated JSON file. The script also uploads the data to the APM backend for further analysis.
if __name__ == '__main__':
# parse arguments, install APK, start check, load JSON
installApk()
startCheck()
loadJson()Result Processing
By examining the stack traces in the JSON output, developers can locate privacy‑method calls and move them behind the user‑consent point, reducing unnecessary access before permission is granted.
Conclusion and Outlook
The presented Gradle‑plugin solution provides a flexible, device‑independent way to perform dynamic privacy checks, outperforming the previous Xposed method in compatibility and extensibility. Future work includes adding a whitelist to skip certain modules, further speeding up instrumentation, and expanding support for additional privacy‑related APIs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
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.
