介绍
Android AOP
一、AOP是什么
Android AOP(Aspect-Oriented Programming),Android 面向切面编程,是一种编程范式,用于将程序中的跨多个点的功能(称为切面或方面)模块化。在Android开发中,AOP 可以帮助开发者更好地组织和管理代码。
总的来说,Android AOP 是一种强大的编程范式,可以帮助开发者更好地组织和管理 Android 应用程序中的代码,提高代码的可维护性和可扩展性。
二、使用场景
1、记录日志
AOP可以在不修改原有业务代码的情况下,为应用程序添加日志记录功能。通过在特定的连接点插入日志记录通知,可以方便地追踪程序的执行过程,有助于故障排查和问题定位。
2、监控性能
AOP可用于监控方法运行时间,帮助开发者了解程序的性能瓶颈。通过记录方法的执行时间,可以对程序进行优化,提高运行效率。
3、权限控制
AOP可以实现细粒度的权限控制,确保只有具备相应权限的用户才能访问特定的资源或执行特定的操作。这有助于保护系统的安全性。
4、缓存优化
AOP可以优化缓存的使用,提高程序的响应速度。例如,第一次调用查询数据库时,将查询结果放入内存对象;第二次调用时,直接从内存对象返回结果,无需再次查询数据库。
5、事务管理
AOP可以方便地管理事务,确保数据的完整性和一致性。通过在方法调用前后开启和提交事务,可以简化事务处理的代码,降低出错的可能性。
三、优缺点
优点
- 降低耦合度:通过将横切关注点(cross-cutting concerns)抽象出来,并在核心业务逻辑之外进行处理,AOP可以降低业务逻辑各部分之间的耦合度,提高程序的可维护性和可扩展性。
- 提高开发效率:AOP允许开发者在不修改原有代码的情况下添加新功能或修改现有功能,从而提高了开发效率。
- 统一管理:AOP可以将分散在各个模块中的公共行为集中到一个统一的地方进行控制和管理,简化了代码结构,降低了维护成本。
缺点
- 增加复杂性:引入AOP可能会增加代码的复杂性,尤其是对于初学者或不熟悉AOP的开发人员来说,可能需要一定的学习和适应周期。
- 难以调试:由于AOP将代码分散到多个地方,导致跟踪和调试变得更加复杂。当出现问题时,很难确定是AOP代码还是核心业务逻辑引起的。
- 运行时性能开销:AOP通常在运行时通过代理或动态字节码生成来实现,这些机制可能引入一定的运行时性能开销,尤其是在系统需要处理大量方法拦截时。
语法
一、Join Points
Join Points(连接点)是指在程序执行过程中可以插入切面逻辑的点。在Android环境下,这些Join Points也可以理解为应用中各种可拦截的执行点,以下是详细介绍。
Join Point | 说明 | Pointcuts语法 |
---|---|---|
Method call | 方法被调用 | call(MethodPattern) |
Method execution | 方法执行 | execution(MethodPattern) |
Constructor call | 构造函数被调用 | call(ConstructorPattern) |
Constructor execution | 构造函数执行 | execution(ConstructorPattern) |
Field get | 读取属性 | get(FieldPattern) |
Field set | 写入属性 | set(FieldPattern) |
Pre-initialization | 与构造函数有关,很少用到 | preinitialization(ConstructorPattern) |
Initialization | 与构造函数有关,很少用到 | initialization(ConstructorPattern) |
Static initialization static | 块初始化 | staticinitialization(TypePattern) |
Handler | 异常处理 | handler(TypePattern) |
Advice execution | 所有 Advice 执行 | adviceexcution() |
二、Pointcuts
在 Android AspectJ中,Pointcut(切点)是一个非常重要的概念,它用于定义哪些 Join Points(连接点)应该被拦截和处理。Join Points是程序执行中的特定位置,如方法调用、异常抛出等,而Pointcut则是一个表达式,用于匹配这些 Join Points。
通过定义 Pointcut,AspectJ 能够精确地控制哪些代码应该被增强(即添加额外的行为)。在 Android 项目中,你可以使用 AspectJ 来拦截和修改各种 Join Points,如 Activity 的生命周期方法、网络请求、数据库操作等。
定义 Pointcut 时,你可以使用 AspectJ 提供的语法来指定匹配规则。这些规则可以基于方法签名、方法执行参数、执行者类型等多种因素。例如,你可以定义一个 Pointcut 来匹配所有 Activity 类的 onCreate 方法,或者匹配所有带有特定注解的方法。
下面是一个简单的 AspectJ Pointcut 示例,用于匹配所有 Activity 类的 onCreate 方法。
@Pointcut("execution(* com.example.myapp.activity.*.onCreate(..))")
public void onCreateMethod() {
// Pointcut定义,无需实现
}
三、Advice
在Android AspectJ中,Advice(通知)是 AOP(面向切面编程)的一个核心概念,它定义了在特定的 Join Points(连接点)应该执行的操作。通过 Advice,你可以在不修改原有业务代码的情况下,向这些 Join Points 添加额外的行为,具体方法如下所示。
方法 | 说明 | 描述 |
---|---|---|
Before | 前置通知 | 在Join Point(如方法调用)执行之前执行 |
After | 返回后通知 | 在Join Point正常执行完毕后执行 |
AfterReturning | 抛出异常后通知 | 在方法执行后,返回一个结果再执行,如果没结果,用此修辞符修辞是不会执行的 |
AfterThrowing | 最终通知 | 在方法执行过程中抛出异常后执行,也就是方法执行过程中,如果抛出异常后,才会执行此切面方法 |
Around | 环绕通知 | 它包围了Join Point,允许你在Join Point执行前后添加代码,甚至可以决定是否执行Join Point |
具体使用
一、集成 AspectJ
第一步,项目 build.gradle 添加依赖
这里我们采用原生方式进行依赖,如果需要集成插件,可以使用AspectJX,注意该插件已经很久未维护。
所以我们采用原生依赖方式,在项目的 build.gradle 添加 classpath 依赖。
buildscript {
dependencies {
classpath 'org.aspectj:aspectjtools:1.9.6'
}
}
plugins {
id 'com.android.application' version '8.2.2' apply false
}
第二步,App build.gradle 添加依赖
修改 app 的 build.gradle 文件,引入 AspectJ 编译支持的插件,并配置相应的任务来织入切面。
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 ->
// 注意这里控制debug下生效,可以自行控制是否生效
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
}
}
}
}
二、开始使用
编写测试代码,我们选择切入点是 Activity 的 onCreate 生命周期方法,并在方法执行前后进行打印执行日志。
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");
}
}
执行结果如下:
三、点击事件拦截
实际场景中我们有防抖的需求,这时我们可以使用如下这种方式进行处理。
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");
}
});
}
}
执行结果如下: