Using AspectJ for Android AOP is too troublesome. Are there many problems? Try BytecodeUtil

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 .

  1. Download the required code package and unzip it in the root directory of your Gradle project

  2. 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 }
        	... ...
        }
    }
    
  3. 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
        }
    }
    

Tags: Android AOP

Posted by sgboise on Fri, 01 Oct 2021 03:02:15 +0530