AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
@Pointcut @Befor @Around @After @AfterReturning @AfterThrowing 需要在切面类使用,即:@Aspect类中
- AspectJ: 一个 JavaTM 语言的面向切面编程的无缝扩展(适用Android)。
- Javassist for Android: 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版。
- DexMaker: Dalvik 虚拟机上,在编译期或者运行时生成代码的 Java API。
- ASMDEX: 一个类似 ASM 的字节码操作库,运行在Android平台,操作Dex字节码。
本文主要讲解的就是aspectJ在Android中的使用。
AOP这种编程思想有什么用呢?一般来说,主要用于不想侵入原有代码的场景中,例如:业务代码中插入一些日志埋点、性能监控、动态权限控制甚至代码调试等,会导致业务代码逻辑不清晰,业务不分离等问题。
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
//引用aspcetJ的依赖
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
然后在项目使用的具体module的build.gradle中添加相应的依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
//引用aspcetJ的jar
implementation 'org.aspectj:aspectjrt:1.8.9'
}
然后在module的build.gradle根路径添加解析代码块:
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
android.applicationVariants.all{ variant ->
JavaCompile javaCompile = variant.javaCompiler
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
}
}
}
}
重新rebuild,aspectJ就集成到我们的项目当中了。
代码如下:
@Aspect
public class LogTraceAspect {
private static final String TAG = "LOG";
@Before("execution(* android.app.Activity.on*(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore:" + key);
}
}
以上就是一个简单的切面类,具体的注解可以看1.3的说明,这个很简单,难点是切点表达式。搞懂切点表达式,AOP的东西就很简单了。
切点表达式的组成如下:
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
2.2中在切面类有一个表达式
“execution(* android.app.Activity.on*(…))”
除了返回类型模式、方法名模式和参数模式外,其他的都是可选的。
以2.2的表达式举例说明:execution 一般指定方法的执行,因为返回类型没有限制,这里就用 * 代替,Android.app.Activity.on * 代表函数的全路径,意思是所有包名Android.app.activity下以on开头的所有方法。后面的(…)表示方法的参数是任何值。
JPoint分类和方法说明
JPoint | 说明 | Pointcut语法说明 |
---|---|---|
method execution | 一般指定方法的执行 | execution(MethodSignnature) |
method call | 函数方法被调用 | call(MethodSignnature) |
constructor call | 构造函数被调用 | call(ConstructorSignature) |
constructor exxcution | 构造函数内部执行 | execution(ConstructorSignature) |
field get | 读变量 | get(FieldSignature) |
field set | 写变量 | set(FieldSignature) |
handler | 异常处理 | handler(TypeSignature)注意:只能和@Before()配合使用,不支持@After和@Around等 |
举例说明
@Before("call(* android.app.Activity.on*(..))")
public void callMethod(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "callMethodBefore" + key);
}
这段代码的意思是匹配所有的activity,在activity的on方法调用之前打印日志。
@Before("execution(* android.app.Activity.on*(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore:" + key);
}
这段代码的意思是匹配所有的activity,在activity的on方法中方法体执行之前打印日志。
execution 和call的区别在于call执行位置是在方法之前,execution执行的位置是方法内部第一行。
get和set方法说明
首先我们先定义一个对象类
public class TestUser {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
执行方法:
/**
* 获取到类的构造方法,并且给方法赋值
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("get(com.android.aspectj.TextUser.age)")
public String onGetAge(ProceedingJoinPoint joinPoint) throws Throwable{
// 执行原代码
Object obj = joinPoint.proceed();
String age = obj.toString();
Log.e(TAG, "age: " + age);
return "18";
}
获取到我们设置给testUser类的设置年龄的方法,并且将年龄设置成18岁返回给setAge()方法。get方法也是一样。
有时候我们并不知道具体的第三方名称,或者很多的方法都需要我们来记录,这时候注解就可以很好的解决这个需求。
先来自定义一段注解:
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface Debug {
}
然后在Aspect文件中引用监听的注解:
@Before("execution(@com.android.aspectj.log.LogTraceAspectInterface.Debug * * (..))")
public void onActivityDebug(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "Debug:" + key);
}
要注意路径一定是我们自定义注解的全路径
最后在想要记录的地方直接引入注解即可
@LogTraceAspectInterface.Debug
public void click(){
Toast.makeText(getApplicationContext(),"哈哈",Toast.LENGTH_SHORT).show();
}
好啦,这期关于Aspect的简单介绍就到这里了,代码我已经上传到github
欢迎加我V 18201726027或者私聊交流探讨,一起学习,一起进步成长!
下期预告:AspectJ开发面向切片编程分module的实际运用。