Implementing AOP using BytecodeUtil
I believe students with several years of development experience know that AOP has natural advantages over OOP in some scenarios, such as centralized login, preventing fast clicks and method call logs.
However, the current popular AspectJ has poor compatibility with Android projects, and it is troublesome to troubleshoot various problems
In order to improve the development efficiency of the company's projects, BytecodeUtil, a development platform for modifying bytecode at compile time, is developed based on Transform + ASM.
Transform and ASM don't understand. Click here
Project source code click here
performance
BytecodeUtil uses multithreading to process IO concurrently, and provides a filter interface to filter the files in the jar you don't want to process
At present, 238 Jar packets need to be processed in the company's project, with a total occupation of 74.3MB. As shown in the figure:
16s is required without filtering the full amount of treatment
After reasonably configuring the processing range required by the project, it is shortened to 3s
How to use
rely on
Because it's a little troublesome to publish to Maven central, we'll start it for now Click here to download .
-
Download the required code package and unzip it in the root directory of your Gradle project
-
Configure build.gradle under the root directory to use the jar package in the decompression
// Top-level build file buildscript { // Unzipped warehouse address ext.repos = uri('./repos') repositories { // Using jar s from the repository maven { url repos } ... ... } dependencies { ... ... classpath "io.github.ysj001:bytecodeutil-plugin:1.0.4" } } allprojects { repositories { // Using jar s from the repository maven { url repos } ... ... } }
-
build.gradle configuration dependency of application module
apply plugin: 'com.android.application' // Using the bytecodeutil plug-in apply plugin: 'bytecodeutil-plugin' // Plug in extension bytecodeUtil { // Set plug-in log level loggerLevel = 1 // Mount the modifiers you need modifiers = [ // For example, mount the AOP modifier Class.forName("com.ysj.lib.bytecodeutil.plugin.core.modifier.aspect.AspectModifier") ] // File filters in jar packages that do not need to be processed. Reasonable configuration can greatly improve the compilation speed notNeedJar = { entryName -> // Here is a more general example (entryName.startsWith("kotlin") || entryName.startsWith("java") || entryName.startsWith("org/intellij/") || entryName.startsWith("org/jetbrains/") || entryName.startsWith("org/junit/") || entryName.startsWith("org/hamcrest/") || entryName.startsWith("com/squareup/") || entryName.startsWith("android") || entryName.startsWith("com/google/android/")) } } ... ... dependencies { ... ... implementation "io.github.ysj001:bytecodeutil-api:1.0.4" }
Confusion configuration
-keepclassmembers class * { @com.ysj.lib.bytecodeutil.api.util.BCUKeep <methods>; }
AOP
You need to mount the modifier: com.ysj.lib.bytecodeutil.plugin.core.modifier.aspect.AspectModifier
introduce
Aspect Oriented Programming. Aspect oriented programming, its application refers to a programming method of dynamically adding functions to the program without modifying the source code, and uniformly cutting this function to one place for unified processing, which can fully reflect the programming idea of high cohesion and low coupling.
Case (DEMO)
-
Demonstrate how to insert the following log printing method at the beginning of the onCreate function body of MainActivity
// Use this annotation to identify the class as a faceted class @Aspect object AopTest { // Use this annotation to identify that the method is a pointcut method that will be woven into the specified @Pointcut( target = "class:.*MainActivity", funName = "onCreate", funDesc = "\\(Landroid/os/Bundle;\\)V", position = POSITION_START, // Sets the weaving to the beginning of the target method ) fun log(jp: JoinPoint /* You can get the parameters of the woven target method, etc */) { Log.i(TAG, "Capture: ${jp.target} args:${jp.args}") } }
-
The demo agent uses the IntervalTrigger annotation arbitrarily to realize interval trigger
// Customize an interval triggered annotation @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class IntervalTrigger( val intervalMS: Long = 1000 ) @Aspect object AopTest { var oldTriggerTime = 0L @Pointcut( target = "annotation:L.*IntervalTrigger;", position = POSITION_CALL, // Sets the proxy source method at the call location ) fun log(callingPoint: CallingPoint) { val trigger = callingPoint.annotation(IntervalTrigger::class.java) ?: return val currentTimeMillis = System.currentTimeMillis() if (currentTimeMillis - oldTriggerTime < trigger.intervalMS) { Log.i(TAG, "log5: Disable trigger") return } oldTriggerTime = currentTimeMillis Log.i(TAG, "log5: Successfully triggered") callingPoint.call() } } // As follows: class MainActivity : AppCompatActivity() { @LogPositionReturn override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<View>(R.id.test).setOnClickListener { test3() } } @IntervalTrigger(500) private fun test3() { // todo something } }