一、概念介绍
我们要实现项目中点击事件的防重复点击功能,AOP思想可以很好的实践。业务代码和其它代码可以完全解耦。 使用 aspectJ 来完成这一次的实践。
二、实践
2.1 环境配置
2.1.1 Gradle配置(复杂版)
项目根目录build.gradle进行配置,添加mavenCentral()仓库,并配置 aspectjtools 和 aspectjweaver 的版本
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'org.aspectj:aspectjtools:1.9.1'
classpath 'org.aspectj:aspectjweaver:1.9.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
再将aspectJ的配置拷贝到根目录build.gradle,在这里暴露一个模块结构类型的入参
/**
* AspectJ 的配置
*/
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
def aop(variants) {
def log = project.logger
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
}
}
}
}
}
再前往需要的模块去配置,主模块的build.gradle:
//aspectJ 配置
rootProject.aop(project.android.applicationVariants)
同时 dependencies里添加 aspectjrt版本 'org.aspectj:aspectjrt:1.9.1'
//aspectJ 配置
implementation 'org.aspectj:aspectjrt:1.9.1'
如果要在其它Moudle的 build.gradle 里进行配置,如下:
rootProject.aop(project.android.libraryVariants)
然后在dependencies里再添加一遍。
2.1.1 Gradle配置(精简版)
如果使用AspectJ,需要在项目的build里面进行一大丢配置,这里为了方便快捷,推荐使用沪江的gradle_plugin_android_aspectjx。(原理是:通过Gradle Plugin的方式 将公共的功能可以抽取出来成为插件,可以供多个 Module (build.gradle)使用,增加复用性。 其中包括 aspectJ 复杂的gradle配置,都集成在里边了)
1、按照github使用指南,添加依赖。
根目录的build.gradle
buildscript {
...
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
//添加依赖,如果studio是3.0以上版本,建议使用v1.1.1
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.1.1'
//注意不能少了aspectjtools的依赖,否则会报错
//同样aspectjtools可以不写在这里,写在app主module的dependencies下面。
}
}
allprojects {
...
}
app 主目录中的build.gradle
apply plugin: 'com.hujiang.android-aspectjx'
android {
...
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
...
//aspectjrt的依赖
implementation 'org.aspectj:aspectjrt:1.8.13'
}
2.2 业务实现
2.2.1 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
long value() default 200;
}
2.2.2 定义切面
@Aspect
public class SingleClickAspect {
int lastViewId = -1;
long lastClickTimestamp = 0;
//切入点:规则
@Pointcut("execution(@com.jxf.aspectjdemo1.annotation.SingleClick * *(..))")
public void clickFun(){ }
// 连接点
@Around("clickFun()")
public void handleClick(ProceedingJoinPoint joinPoint) throws Throwable{
//取click方法中的参数
if(joinPoint.getArgs() != null
&& joinPoint.getArgs().length > 0
&& joinPoint.getArgs()[0] instanceof View){
View target = (View) joinPoint.getArgs()[0];
//签名信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(SingleClick.class)){
SingleClick singleClickAnnotation = method.getAnnotation(SingleClick.class);
long nowTime = System.currentTimeMillis();
if(nowTime - lastClickTimestamp <= singleClickAnnotation.value()
&& target.getId() == lastViewId){
Log.i("AOP", "you click is too fast!");
}else{
// 记住上一次点击的时间戳和View的ID
lastViewId = target.getId();
lastClickTimestamp = nowTime;
//执行click方法
joinPoint.proceed();
}
}
}
}
}
2.2.3 调用
public class MainActivity extends AppCompatActivity {
int number = 1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@SingleClick
public void txtClick(View v){
Log.d("AOP", "----------------"+number++);
}
}
2.2.4 结果
![](https://img.haomeiwen.com/i18224982/bce402b364d60244.png)
2.2.4 其它
我们看一下 编译过后的 MainActivity.class 的内容
public class MainActivity extends AppCompatActivity {
int number = 1;
public MainActivity() {
}
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131361820);
}
@SingleClick
public void txtClick(View v) {
JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, v);
txtClick_aroundBody1$advice(this, v, var3, SingleClickAspect.aspectOf(), (ProceedingJoinPoint)var3);
}
static {
ajc$preClinit();
}
}
生成的class文件可以看出,编译生成字节码文件时,可以通过aspectJ 的编译器生成符合java字节码规范的class文件,从而可以达到灵活插入部分代码(Aspect)的目的。
三、其它
其它实现:
方法调用性能检测 : hugo
Android 权限申请 :android_permission_aspectjx
参考:
https://www.jianshu.com/p/f577aec99e17
https://www.jianshu.com/p/16c5e8cdcf08
AspectJ官网
AspectJ Programming Guide
AspectJ Development Environment Guide
AspectJ NoteBook
网友评论