天空一声巨响,徐某闪亮登场
今天和大家一起学习一下安卓面向AOP编程,什么是AOP?AOP全称为Aspect Oriented Programming,意为:面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
话不多说,首先引入环境
看,是这个文件!
buildscript {
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
plugins {
id 'com.android.application'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.xcy.myaopproject"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'org.aspectj:aspectjrt:1.9.6'
}
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 ->
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)
}
}
然后创建注解(Ps:让我们先来实现一个小功能,什么小功能呢?欲知后事如何。。。)
开个玩笑,emm~(看注释)
/**
* author:Xcy
* date:2022/5/5 20
* description:防止多次点击
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
long value() default 1000L;
}
让我们来创建切入类
/**
* author:Xcy
* date:2022/5/5 20
* description:防止多次点击切入类
**/
@Aspect
public class SingleClickAspect {
//最后一次点击的时间
private static long lastTime = 0;
//最后注入的tag
private static String lastTag = "";
//@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
@Pointcut("execution(@com.xcy.myaopproject.annotation.SingleClick * *(..))")
public void methodForSingleClick() {
}
@Around("methodForSingleClick() && @annotation(singleClick)")
public void aroundJoinPoint(ProceedingJoinPoint joinPoint, SingleClick singleClick) throws Throwable {
//获取被注解方法的签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取类名
String className = methodSignature.getDeclaringType().getName();
//获取方法名
String methodName = methodSignature.getName();
//创建TAG
StringBuilder builder = new StringBuilder(className + "." + methodName);
builder.append("(");
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (i == 0) {
builder.append(arg);
} else {
builder.append(",").append(arg);
}
String tag = builder.toString();
//当前点击时间
long currentTime = System.currentTimeMillis();
//如果当前时间减去最后一次点击的时间小于你通过注解设置的区间,并且当前的tag等于上次点击的tag时则return
if (currentTime - lastTime < singleClick.value() && tag.equals(lastTag)) {
//当前时间还处于冷静期(手动滑稽)
return;
}
//最后点击时间
lastTime = currentTime;
lastTag = tag;
//执行方法
joinPoint.proceed();
}
}
}
使用(Ps:当前注解@SingleClick(毫秒))
btn.setOnClickListener(new View.OnClickListener() {
@SingleClick(3000)
@Override
public void onClick(View view) {
Log.d("MainActivity", "click button");
}
});
避免在短时间内多次点击的小功能就完成了。
让我们趁热打铁,照葫芦画瓢写一个统计方法执行耗时的切入
首先写自定义注解
/**
* author:Xcy
* date:2022/5/5 20
* description:计算当前方法耗时
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FunDuration {
}
然后呢?然后写切入类啊!笨蛋,嗨害嗨...
/**
* author:Xcy
* date:2022/5/5 20
* description:统计方法执行耗时
**/
@Aspect
public class FunDurationAspect {
private final String TAG = FunDurationAspect.class.getSimpleName();
//任务开始,任务结束,耗时
private long startTime;
private long endTime;
private long duration;
@Pointcut("execution(@com.xcy.myaopproject.annotation.FunDuration * *(..))")
public void methodForDuration() {
}
@Around("methodForDuration() && @annotation(funDuration)")
public void aroundJoinPoint(ProceedingJoinPoint joinPoint, FunDuration funDuration) {
//获取被注解方法的签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取类名
String className = methodSignature.getDeclaringType().getName();
//获取方法名
String methodName = methodSignature.getName();
//方法开始执行
startTime = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
//函数结束执行
endTime = System.currentTimeMillis();
//执行总耗时
duration = endTime - startTime;
Log.d(TAG, className + "." + methodName + "()" + " 耗时:" + duration / 1000f + "s");
}
}
}
使用(方法上加注释,某个地方调用)
@FunDuration
public void test1() {
for (int i = 0; i < 10000; i++) {
Log.e("test", i + "");
}
}
拓展知识:(剩下的用法,靠你自己了,看千遍,不如自己写一遍,这样才能加深印象嘛)
AspectJ提供的注解
@Aspect:表明这是一个AspectJ文件,编译器在编译的时候,就会自动去解析,然后将代码注入到相应的JPonit中
@Pointcut:表示具体的切入点,可以确定具体织入代码的地方。可以通过通配、正则表达式等指定点
@Before:表示在调用点之前,调用该方法
@After:表示在调用点之后,再调用该方法
@Around:使用该方法代替该点的执行
Join Point 表示连接点,即 AOP 可值入代码的点:
Pointcuts是具体的切入点,可以确定具体值入代码的地方。可以通过通配、正则表达式等指定点,常用的有:
execution: 用于匹配方法执行的连接点,意思是说,直接在方法内部的前或者后添加。
call: 调用匹配到的方法时调用
[AOP demo链接]:(https://gitee.com/xcy_god/my-aop-project.git)
PS:项目运行后会有几秒钟白屏,这是因为我在MainActivity.java中调用的方法,目的是用来计算方法执行耗时,如果不需要可以自己注释掉
完结撒花~~~
徐某人不谈原理,只助你CV
等等!!!