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 Point | illustrate | Pointcuts syntax |
---|---|---|
Method call | method is called | call(MethodPattern) |
Method execution | method execution | execution(MethodPattern) |
Constructor call | Constructor is called | call(ConstructorPattern) |
Constructor execution | Constructor execution | execution(ConstructorPattern) |
Field get | Read properties | get(FieldPattern) |
Field set | Write properties | set(FieldPattern) |
Pre-initialization | Related to constructor, rarely used | preinitialization(ConstructorPattern) |
Initialization | Related to constructor, rarely used | initialization(ConstructorPattern) |
Static initialization static | block initialization | staticinitialization(TypePattern) |
Handler | Exception handling | handler(TypePattern) |
Advice execution | All Advice execution | adviceexecution() |
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.
method | illustrate | describe |
---|---|---|
Before | Pre-notification | Execute before Join Point (such as method call) execution |
After | Notify after return | Executed after the Join Point has been executed normally. |
AfterReturning | Notify after an exception is thrown | After 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. |
AfterThrowing | final notice | Execute after an exception is thrown during method execution. That is, if an exception is thrown during method execution, this aspect method will be executed. |
Around | surround notification | It 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: