1.前言
- 最近随着项目迭代业务与使用的用户逐渐增多,陆续的出现了一些性能或者BUG的问题(例如:1.某些功能执行响应慢;2.按键复电;等等)。
- 根据上述部分性能问题,提出相对应的解决方案:
- 某些功能执行响应慢 ---> 性能监控
- 按键复点 ---> 按键防抖
- 上述2个解决方案的最终实现原理:就是在执行函数的前或者后台添加对应的逻辑处理。
- 选择实现方式:
1.直接在方法前后添加对应的逻辑。
2.使用静态/动态代理。
3.使用AOP。 - 1与2方式:当涉及修改的方法或者类较多时,实现比较复杂,而且侵入性大,更不方便后期扩展与维护。
- 第3方式:侵入性很少;实现简单;解耦性很大;易维护。而这种方式是如何使用?原理是什么?。
- 今天就为大家揭开
AOP
的面纱。 - 文章中实例 linhaojian的Github
2.介绍
2.1 AOP 是什么
-
AOP
为Aspect Oriented Programming的缩写。意为:面向切面编程。
2.2 AOP 作用
- 通过预编译方式和运行期动态代理,来改变原来执行结果的技术。
2.3 AOP 特点
- 使原执行逻辑与改变执行逻辑解耦。
- 对原代码侵入性少。
- 容易扩展辅助功能(例如:日志)。
2.4 Android 中应用
- 日志
- 持久化
- 性能监控
- 数据校验
- 缓存
- 按钮防抖
- 其他更多
2.5 AOP 术语
- 通知、增强处理(Advice):就是你想要的功能,也就是上面说的日志、耗时计算等。
- 连接点(JoinPoint):允许你通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点(spring只支持方法连接点)。AspectJ还可以让你在构造器或属性注入时都行,不过一般情况下不会这么做,只要记住,和方法有关的前前后后都是连接点。
- 切入点(Pointcut):上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
- 切面(Aspect):切面是通知和切入点的结合。现在发现了吧,没连接点什么事,连接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过before,after,around等AOP注解就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
- 织入(weaving): 把切面应用到目标对象来创建新的代理对象的过程。
2.6 AOP 注解
- @Aspect:声明切面,标记类
- @Pointcut(切点表达式):定义切点,标记方法
- @Before(切点表达式):前置通知,切点之前执行
- @Around(切点表达式):环绕通知,切点前后执行
- @After(切点表达式):后置通知,切点之后执行
- @AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
- @AfterThrowing(切点表达式):异常通知,切点抛出异常时执行
@Pointcut、@Before、@Around、@After、@AfterReturning、@AfterThrowing需要在切面类中使用,即在使用@Aspect的类中。
2.7 切点表达式
AspectJ语法1.pngAspectJ语法2.png
AspectJ语法3.png
- 示例:
// 返回类型:* 类名+函数名:com.lhj.test_apt..*ck(..)
// com.lhj.test_apt..*ck(..) :意思是com.lhj.test_apt包下所有类中以ck结尾的函数
@Pointcut("execution(* com.lhj.test_apt..*ck(..))")
3.使用
- 3.1 项目根目录(build.gradle)的dependencies中,添加如下代码:
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.13' // add
classpath 'org.aspectj:aspectjrt:1.8.13' // add
}
- 3.2 在主项目或者需要引用的library的build.gradle中,示例如下:
apply plugin: 'com.android.library'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 17
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// aop
implementation 'org.aspectj:aspectjrt:1.8.13'
}
project.android.libraryVariants.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)]
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
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:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
- 3.3 定义切面类(例如:在源码一些方法上添加额外日志打印):
/**
* 添加切面注解
*/
@Aspect
public class AopTest {
private static final String TAG = "linhaojian";
/**
* 定义切入点(定义那些类或者方法需要改变)
*/
@Pointcut("execution(* com.lhj.test_apt..*ck(..))")
public void pointcut1() {
}
/**
* 使用注解方式,定义注解
*/
@Pointcut("execution(@com.lhj.test_apt.DebugLog * *ck(..))")
public void pointcut() {
}
/**
* 前置通知,切点之前执行
* @param point
*/
@Before("pointcut()")
public void logBefore(JoinPoint point){
Log.e(TAG,"logBefore");
}
/**
* 环绕通知,切点前后执行
* @param joinPoint
* @throws Throwable
*/
@Around("pointcut()")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e(TAG,"logAround");
// 1.执行切点函数(如果不调用该方法,切点函数不被执行)
joinPoint.proceed();
}
/**
* 后置通知,切点之后执行
* @throws Throwable
*/
@After("pointcut()")
public void logAfter(JoinPoint point){
Log.e(TAG,"logAfter");
}
/**
* 返回通知,切点方法返回结果之后执行
* @throws Throwable
*/
@AfterReturning("pointcut()")
public void logAfterReturning(JoinPoint point, Object returnValue){
Log.e(TAG,"logAfterReturning ");
}
/**
* 异常通知,切点抛出异常时执行
* @throws Throwable
*/
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void logAfterThrowing(Throwable ex){
Log.e(TAG,"logAfterThrowing : "+ex.getMessage());
}
}
- 注意:注释1中,如果使用了@Around注释,记得调用joinPoint.proceed(),不然切点的函数是不会被调用的。
- 3.4 切点的使用方式:
- 无侵入式:@Pointcut定义不包含注解时,就能实现源代码零修改的改变。
- 低侵入式:@Pointcut定义包含注解时,就需要在源代码的函数中添加相应的注解,如下代码块。
public class AopActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aop);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
click();
}
});
}
@DebugLog
public void click(){
Log.e("linhaojian","onClick()");
}
}
- DebugLog注解代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DebugLog {
}
-
3.5 运行结果
执行结果.png
4.原理
- 在分析其原理前,我们先看看包含切点功能的类,编译之后的class文件内容:
public class AopActivity extends AppCompatActivity {
private Button button;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(layout.aop);
this.button = (Button)this.findViewById(id.button);
this.button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
AopActivity.this.click();
}
});
}
@DebugLog
public void click() {
// 1
JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
try {
try {
// 2
AopTest.aspectOf().logBefore(var1);
// 3
click_aroundBody1$advice(this, var1, AopTest.aspectOf(), (ProceedingJoinPoint)var1);
} catch (Throwable var4) {
AopTest.aspectOf().logAfter(var1);
throw var4;
}
AopTest.aspectOf().logAfter(var1);
AopTest.aspectOf().logAfterReturning(var1, (Object)null);
} catch (Throwable var5) {
AopTest.aspectOf().logAfterThrowing(var5);
throw var5;
}
}
}
@Aspect
public class AopTest {
//...
public static AopTest aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("com.lhj.test_apt.AopTest", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}
public static boolean hasAspect() {
return ajc$perSingletonInstance != null;
}
static {
try {
// 4
ajc$postClinit();
} catch (Throwable var1) {
ajc$initFailureCause = var1;
}
}
}
- 从上面编译后的class文件,可以看很清楚编译时改变了原方法的执行流程或结果,添加了切面类中定义的一些额外执行逻辑(例如:logBefore、logAfter);
- 注释1:通过工厂类,创建ProceedingJoinPoint的实例;
- 注释2:AopTest.aspectOf()代表获取AopTest的单例(单例时在注释4中通过static块初始化);
- 注释3:其实就是将原方法的执行内容封装至ProceedingJoinPoint,使它们之间关联。
5.总结
- 到此,
AOP
就结束完毕。 - 如果喜欢我的分享,可以点击 关注 或者 赞,你们支持是我分享的最大动力 。
- linhaojian的Github
欢迎关注linhaojian_CSDN博客或者linhaojian_简书!
不定期分享关于安卓开发的干货。
写技术文章初心
- 技术知识积累
- 技术知识巩固
- 技术知识分享
- 技术知识交流
网友评论