introduce

Android AOP

1. What is AOP?

Android AOP (Aspect-Oriented Programming), Android aspect-oriented programming, is a programming paradigm used to modularize functionality across multiple points in a program (called aspects or aspects). In Android development, AOP can help developers better organize and manage code.

In general, Android AOP is a powerful programming paradigm that can help developers better organize and manage the code in Android applications and improve the maintainability and scalability of the code.

2. Usage scenarios

1. Record logs

AOP can add logging functionality to applications without modifying the original business code. By inserting logging notifications at specific connection points, you can easily track the execution process of the program, which is helpful for troubleshooting and problem location.

2. Monitor performance

AOP can be used to monitor method running time and help developers understand the performance bottlenecks of the program. By recording the execution time of the method, the program can be optimized and operating efficiency improved.

3. Permission control

AOP can implement fine-grained permission control to ensure that only users with corresponding permissions can access specific resources or perform specific operations. This helps protect the security of your system.

4. Cache optimization

AOP can optimize the use of cache and improve the response speed of the program. For example, the first time you call to query the database, the query results are put into the memory object; the second time you call it, the results are returned directly from the memory object without querying the database again.

5. Affairs management

AOP can easily manage transactions and ensure data integrity and consistency. By opening and committing transactions before and after method calls, you can simplify transaction processing code and reduce the possibility of errors.

3. Advantages and Disadvantages

advantage

  • Reduce coupling: By abstracting cross-cutting concerns and processing them outside the core business logic, AOP can reduce the coupling between various parts of the business logic and improve the maintainability and reliability of the program. Scalability.
  • Improve development efficiency: AOP allows developers to add new functions or modify existing functions without modifying the original code, thereby improving development efficiency.
  • Unified management: AOP can centralize public behaviors scattered in various modules into a unified place for control and management, simplifying the code structure and reducing maintenance costs.

shortcoming

  • Increased complexity: The introduction of AOP may increase the complexity of the code, especially for beginners or developers who are not familiar with AOP, which may require a certain learning and adaptation cycle.
  • Difficult to debug: Because AOP spreads code into multiple places, tracing and debugging becomes more complicated. When a problem occurs, it can be difficult to determine whether it’s the AOP code or core business logic that’s causing it.
  • Runtime performance overhead: AOP is usually implemented at runtime through proxies or dynamic bytecode generation. These mechanisms may introduce a certain runtime performance overhead, especially when the system needs to handle a large number of method interceptions.

grammar

1. Join Points

Join Points refer to points where aspect logic can be inserted during program execution. In the Android environment, these Join Points can also be understood as various interceptable execution points in the application. The following is a detailed introduction.

Join PointillustratePointcuts syntax
Method callmethod is calledcall(MethodPattern)
Method executionmethod executionexecution(MethodPattern)
Constructor callConstructor is calledcall(ConstructorPattern)
Constructor executionConstructor executionexecution(ConstructorPattern)
Field getRead propertiesget(FieldPattern)
Field setWrite propertiesset(FieldPattern)
Pre-initializationRelated to constructor, rarely usedpreinitialization(ConstructorPattern)
InitializationRelated to constructor, rarely usedinitialization(ConstructorPattern)
Static initialization staticblock initializationstaticinitialization(TypePattern)
HandlerException handlinghandler(TypePattern)
Advice executionAll Advice executionadviceexecution()

2. Pointcuts

In Android AspectJ, Pointcut is a very important concept, which is used to define which Join Points (connection points) should be intercepted and processed. Join Points are specific locations in program execution, such as method calls, exceptions thrown, etc., while Pointcut is an expression used to match these Join Points.

By defining Pointcut, AspectJ can control exactly which code should be enhanced (that is, add additional behavior). In Android projects, you can use AspectJ to intercept and modify various Join Points, such as Activity life cycle methods, network requests, database operations, etc.

When defining a Pointcut, you can use the syntax provided by AspectJ to specify matching rules. These rules can be based on various factors such as method signature, method execution parameters, executor type, etc. For example, you can define a Pointcut to match the onCreate method of all Activity classes, or to match all methods with a specific annotation.

Below is a simple AspectJ Pointcut example that matches the onCreate method of all Activity classes.

@Pointcut("execution(* com.example.myapp.activity.*.onCreate(..))")  
public void onCreateMethod() {  
    // 
}

3. Advice

In Android AspectJ, Advice is a core concept of AOP (Aspect-Oriented Programming), which defines the operations that should be performed at specific Join Points. Through Advice, you can add additional behaviors to these Join Points without modifying the original business code. The specific method is as follows.

methodillustratedescribe
BeforePre-notificationExecute before Join Point (such as method call) execution
AfterNotify after returnExecuted after the Join Point has been executed normally.
AfterReturningNotify after an exception is thrownAfter the method is executed, a result is returned and then executed. If there is no result, the rhetoric using this rhetorical symbol will not be executed.
AfterThrowingfinal noticeExecute after an exception is thrown during method execution. That is, if an exception is thrown during method execution, this aspect method will be executed.
Aroundsurround notificationIt surrounds the Join Point, allowing you to add code before and after the Join Point is executed, and even decide whether to execute the Join Point

Specific use

1. Integrate AspectJ

The first step is to add dependencies to the project build.gradle

Here we use the native method to make dependencies. If you need to integrate a plug-in, you can use AspectJX . Note that this plug-in has not been maintained for a long time.

So we use the native dependency method and add classpath dependencies to the project’s build.gradle.

buildscript {
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.9.6'
    }
}
plugins {
id 'com.android.application' version '8.2.2' apply false
}

The second step is to add dependencies to App build.gradle

Modify the app’s build.gradle file, introduce the plug-in supported by AspectJ compilation, and configure the corresponding tasks to weave in aspects.

dependencies {

    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    implementation 'org.aspectj:aspectjrt:1.9.6'
}

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->
    // 
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return
    }

    JavaCompile javaCompile = variant.javaCompileProvider.get()
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true)
        new Main().run(args, handler)
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break
            }
        }
    }
}

2. Start using

When writing test code, we choose the entry point to be the onCreate life cycle method of Activity, and print the execution log before and after the method is executed.

package com.example.myapplication;

import android.util.Log;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AspectUtil {
    private static final String TAG = AspectUtil.class.getSimpleName();
    @Before("execution(* android.app.Activity+.onCreate(..))")
    public void beforeCreate(JoinPoint joinPoint) {
        Log.d(TAG, "activity create before");
    }

    @After("execution(* android.app.Activity+.onCreate(..))")
    public void afterCreate() {
        Log.d(TAG, "activity create after");
    }
}

The execution results are as follows:

3. Click event interception

In actual scenarios, we have the need for anti-shake. In this case, we can use the following method to handle it.

package com.example.myapplication;

import android.util.Log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class InterruptClickUtil {
    private static final String TAG = "InterruptClick";
    private static final Long MAX = 500L;
    private Long mLastTime = 0L;

    @Around("execution(* android.view.View.OnClickListener.onClick(..))")
    public void clickEvent(ProceedingJoinPoint joinPoint) throws Throwable {
        if (System.currentTimeMillis() - mLastTime >= MAX) {
            mLastTime = System.currentTimeMillis();
            joinPoint.proceed();
        } else {
            Log.i(TAG, "repeated clicks");
        }
    }
}

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "InterruptClick";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.tv);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick");
            }
        });
    }
}

The execution results are as follows:

Leave a Reply

Your email address will not be published. Required fields are marked *