适用场景
- 统计埋点
- 日志打印/打点
- 数据校验
- 行为拦截
- 性能监控
- 动态权限控制
AOP 是什么,一图胜前言
相对于我们的流程图都是纵向的,现在我们来介绍一种横向的编程模型
show me the code
工程 build.gradle 中顶部新增
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.3"
classpath 'org.aspectj:aspectjtools:1.9.6'
}
}
新建一个java library 名称为annotation 新建一个Android Library 名称为aspect build.gradle
dependencies {
implementation project(":annotation")
api "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
//注意:如果是 Library Module 下面这个行代码中的 applicationVariants 应该替换为 libraryVariants
final def variants = project.android.libraryVariants
//在构建工程时,执行编织
variants.all { variant ->if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return
}
JavaCompile javaCompile = variant.javaCompile
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
}
}
}
app build.gradle
dependencies {
implementation 'org.aspectj:aspectjrt:1.9.6'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.navigation:navigation-fragment:2.4.1'
implementation 'androidx.navigation:navigation-ui:2.4.1'
implementation project(":annotation")
api project(":aspect")
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
//注意:如果是 Library Module 下面这个行代码中的 applicationVariants 应该替换为 libraryVariants
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.javaCompile
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
}
}
}
定义一个注解(可选,后面介绍)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnceClick {
/**
* 快速点击的间隔
*/
long value() default 1000;
}
定义切入面
@Aspect
public class ClickOnceAspect {
private static final String TAG = "sansui";
private static final String POINTCUT_METHOD =
"execution(@com.example.annotation.ClickOnce * *(..))";
/**
* 最近一次点击的时间
*/
private long mLastTime;
/**
* 最近一次点击的控件ID
*/
private int mLastId;
@Pointcut(POINTCUT_METHOD)
public void clickOnce() {}
@Around("clickOnce() && @annotation(clickOnce)")
public void aroundClickOnce(ProceedingJoinPoint joinPoint, ClickOnce clickOnce)
throws Throwable {
View v = null;
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof View) {
v = (View) arg;
}
}
if (v != null) {
long currentTime = System.currentTimeMillis();
if ( v.getId() == mLastId && currentTime - mLastTime < clickOnce.value()) {
Log.w(TAG, "忽略多余的点击事件");
return;
}
mLastId = v.getId();
mLastTime = currentTime;
// 正常执行
joinPoint.proceed();
} else {
Log.e(TAG, "这是不可能的");
}
}
}
使用方法
@ClickOnce
private void btnClick(View v) {
Log.i(TAG, "clicked");
}
运行原理
- @Aspect 用它声明一个类,表示一个需要执行的切面。
- @Pointcut 声明一个切点:类似sql语句,负责查询
- @Before/@After/@Around/...(统称为Advice类型) 声明在切点前、后、中执行切面代码。
切点表达式:
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
还有哪些方式
- APT
- Javassist
- ASM