Android AOP你了解多少
什么是AOP
AOP(Aspect Oriented Programming)意为面向切面编程,指通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
其广泛的应用在代码的后期修改与维护之中,它对原代码侵入性少,容易扩展辅助功能,可以使原执行逻辑与改变执行逻辑解耦。
举个例子.jpeg现在有一个智能门,以前的开门的逻辑是:输入密码 -> 拎动把手 -> 开门,现在呢业主觉得密码有可能被盗,不够安全,希望加上指纹验证。这时候我们只需要将 验证指纹 这一步插入到 开门 之前就完成了,现在的逻辑是:输入密码 -> 拎动把手 -> 指纹验证 -> 开门。这种思维就是一种面向切面的思维。
思维导图.png什么是AspectJ
要知道AOP只是一种编程思想,那么,在android中,我们该通过何种工具来实现这种思想呢,没错,就是AspectJ。要掌握AspectJ首先要明确下面的几大概念:
AspectJ.pngAdvice(通知):定义需要被注入到.class字节码文件中的代码,通俗点儿来说就是告诉编织器哪里是你需要插入的代码。
- @Pointcut :定义切点、标记方法以便于重用。
- @Before :前置通知,其内容在切点之前执行。
- @Around :环绕通知,其内容贯穿整个切点前后。
- @After:后置通知,其内容在切点之后执行。
- @AfterReturning:返回通知,其内容在切点返回结果后再执行。
- @AfterThrowing:异常通知,其内容在切点抛出异常时执行。
JoinPoint(连接点):即允许你插入代码的地方。
Pointcut(切入点):是对连接点的筛选与定义的一种表达式。
- 如下图所示,"execution(* android.view.View.OnClickListener.onClick(..))"是一个完整的切点表达式,它是由execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)组成的,其中<修饰符模式>和<异常模式>可以省略。
Aspect(切面):切面是通知和切入点的结合。
Weaving(织入):这个很好理解就是把我们定义好的切面注入到目标对象中去的过程。
基本用法
插件配置
//在根build.gradle下配置:
dependencies {
...
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
}
//在app/build.gradle下配置
apply plugin: 'com.hujiang.android-aspectjx'
aspectjx {
//关闭AspectJX功能
enabled true
//织入遍历符合条件的库
//includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library'
//排除包含‘universal-image-loader’的库
//excludeJarFilter 'universal-image-loader'
}
定义切面
创建一个类,并通过@Aspect定义为一个切面。
@Aspect
public class AspectTest {
......
}
创建通知、添加切点表达式
在该类中添加需要编织的方法,并通过通知和切点表达式来定义它。Ok,一个简单的防抖的OnClick判断就切入到你的程序中去了。相信其他业务你也能很好的利用AspectJ来处理了。
@Aspect
public class AspectTest {
private static final String TAG = "AspectTest";
@Around("execution(* android.view.View.OnClickListener.onClick(..))")
public void onClickListener(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
Log.d(TAG, "onClick");
if (!NoDoubleClickUtils.isDoubleClick()){
proceedingJoinPoint.proceed(); //切回到切点并执行后续代码
}
}
}
补充:第三方库兼容
有时候我们在使用AspectJ的时候可能会遇到引入了一些其他的三方库的情况,而我们又需要对其内的连接点进行编织、切入。如上述点击事件防抖的例子,可能用到了Butterknife来注解@onClick(),这时你会发现上述切入表达式不起作用了,为什么呢,我们看一下Butterknife中的@OnClick定义就知道了。
@Target(METHOD)
@Retention(RUNTIME)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
@IdRes int[] value() default { View.NO_ID };
}
我们可以发现。其通过Annotation Processor对@OnClick进行扫描,并将android.view.View.OnClickListener.onClick方法替换为butterknife.internal.DebouncingOnClickListener.doClick方法。因此为了兼容ButterKnife上述切入点表达式应该改为如下方式,及对butterknife的OnClick方法进行切入。
@Around("execution(* android.view.View.OnClickListener.onClick(..)) || execution(@butterknife.OnClick * *(..))")
public void onClickListener(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
Log.d(TAG, "onClick");
if (!NoDoubleClickUtils.isDoubleClick()){
proceedingJoinPoint.proceed();
}
}