AOP即Aspect Oriented Programming的縮寫,習慣稱為切面編程;與OOP(面向對象編程)萬物模塊化的思想不同,AOP則是將涉及到眾多模塊的某一類問題進行統(tǒng)一管理,AOP的優(yōu)點是將業(yè)務邏輯與系統(tǒng)化功能高度解耦,讓我們在開發(fā)過程中可以只專注于業(yè)務邏輯,其他一些系統(tǒng)化功能(如路由、日志、權限控制、攔截器、埋點、事件防抖等)則由AOP統(tǒng)一處理;
AspectJ簡介
AOP是一種編程思想,或者說方法論,AspectJ則是專為AOP設計的一種語言,它支持原生的JAVA,可用于在java中處理AOP的相關問題。
下面非常簡單的描述下AspectJ中幾個要點
-
@Aspect
表示這是一個切面類,放在類名上面,把當前類標識為一個切面供容器讀取
@Aspect
public class SingleClickAspect {
// 里面用AspectJ注解,實現(xiàn)相應方法
}
-
Join Points
AspectJ中的切點,是AspectJ作用到具體某個位置的說明,主要包括三類:1、函數(shù)(函數(shù)調(diào)用,函數(shù)執(zhí)行,構造函數(shù)等)
2、變量(變量get,變量set等)
3、 代碼塊(靜態(tài)代碼塊,for等) @Pointcuts
AspectJ中的切面(這種翻譯不一定正確),由點及面,用于說明你需要hook哪一類問題,比如我需要hook一個單擊事件SingleClick ,
@Retention(RetentionPolicy.RUNTIME) //注解保留至運行時
@Target(ElementType.METHOD) //聲明注解作用在方法上面
public @interface SingleClick {
/* 點擊間隔時間 */
long value() default 2000;
}
則:
@Aspect
public class SingleClickAspect {
/**
* 定義切點,標記切點為所有被@SingleClick注解的方法
* 注意:這里com.util.click.SingleClick是你自己項目中SingleClick這個類的全路徑
* 注意:這里的 * * 表示任意方法
* (..)表示任意參數(shù)
*/
@Pointcut("execution(@com.util.click.SingleClick * *(..))")
public void methodClick() {}// 該方法不會被執(zhí)行
}
-
advice
Join Points和Pointcuts用來說明需要hook哪些位置或者流程,advice則用于hook之后指定需要做什么,在切面類中需要定義切面方法用于響應響應的目標方法,切面方法即為通知方法,通知方法需要用注解標識,AspectJ 支持 5 種類型的通知注解:@Before: 前置通知, 在方法執(zhí)行之前執(zhí)行
@After: 后置通知, 在方法執(zhí)行之后執(zhí)行 。
@AfterRunning: 返回通知, 在方法返回結果之后執(zhí)行
@AfterThrowing: 異常通知, 在方法拋出異常之后
@Around: 環(huán)繞通知, 圍繞著方法執(zhí)行,around()用的會比較多,因為自由度高,其他的用around()都可以實現(xiàn)
@Aspect
public class SingleClickAspect {
@Pointcut("execution(@com.util.click.SingleClick * *(..))")
public void methodClick() {}// 該方法不會被執(zhí)行
@Before("methodClick()")
public void before(){
System.out.println("before................");
}
@After("methodClick()")
public void after(){
System.out.println("after.................");
}
@AfterReturning("methodClick()")
public void afterReturning(JoinPoint joinPoint) {
System.out.println("afterReturning.................");
}
@AfterThrowing("methodClick()")
public void afterThrowing(JoinPoint joinPoint) {
System.out.println("afterThrowing...................");
}
@Around("methodClick()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("around before............");
joinPoint.proceed(); //執(zhí)行完成目標方法
System.out.println("around after..............");
}
它們是按什么順序執(zhí)行的呢?創(chuàng)建點擊事件
TextView textView.setOnClickListener(new View.OnClickListener() {
@SingleClick(1500)// 添加點擊注釋
@Override
public void onClick(View v) {
if (flag) {
System.out.println("throw an exception................");
throw new RuntimeException();
}
System.out.println("執(zhí)行onClick................");
}
});
點擊后,執(zhí)行正常情況結果:
around before............
before................
執(zhí)行onClick................
around after..............
after.................
afterReturning.................
執(zhí)行異常情況結果:
around before............
before................
throw an exception................
around after..............
after.................
afterThrowing.................
對于@Around這個advice,不管它有沒有返回值,但是必須要在方法內(nèi)部,調(diào)用一下joinPoint.proceed();否則,OnClickListener中的onClick()將沒有機會被執(zhí)行,從而也導致了 @Before這個advice不會被觸發(fā)。
AOP處理android中的重復點擊
AOP用于處理某一類獨立的問題,非常契合屏蔽重復點擊的需求,我們只需要hook住原先的點擊事件(轉確的說是點擊事件后的處理流程),判斷是不是重復點擊,是則過濾掉不讓它執(zhí)行,否則就正常執(zhí)行;
集成
1.引入Aspectj
在Android中進行AspectJ的實現(xiàn),建議使用Hujiang大神的框架gradle_plugin_android_aspectjx,可以非常方便的集成和配置AspectJ在Android中的環(huán)境
- 在項目根目錄下的build.gradle中,添加依賴:
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
}
- 在app或其他任何用到該AOP功能的module目錄下的build.gradle中,都需添加:
apply plugin: 'android-aspectjx'
dependencies {
......
implementation 'org.aspectj:aspectjrt:1.8.9'
}
2.添加一個自定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
/* 點擊間隔時間 */
long value() default 1000;
}
- 添加自定義注解的原因是,方便管理哪些方法使用了重復點擊的AOP,同時可以在注解中傳入點擊時間間隔,更加靈活。
3.封裝一個重復點擊判斷工具類
public final class XClickUtil {
/**
* 最近一次點擊的時間
*/
private static long mLastClickTime;
/**
* 最近一次點擊的控件ID
*/
private static int mLastClickViewId;
/**
* 是否是快速點擊
*
* @param v 點擊的控件
* @param intervalMillis 時間間期(毫秒)
* @return true:是,false:不是
*/
public static boolean isFastDoubleClick(View v, long intervalMillis) {
int viewId = v.getId();
// long time = System.currentTimeMillis();
long time = SystemClock.elapsedRealtime();
long timeInterval = Math.abs(time - mLastClickTime);
if (timeInterval < intervalMillis && viewId == mLastClickViewId) {
Log.e("isFastDoubleClick", "true");
return true;
} else {
mLastClickTime = time;
mLastClickViewId = viewId;
Log.e("isFastDoubleClick", "false");
return false;
}
}
}
4.編寫Aspect AOP處理類
@Aspect
public class SingleClickAspect {
private static final long DEFAULT_TIME_INTERVAL = 5000;
/**
* 定義切點,標記切點為所有被@SingleClick注解的方法
* 注意:這里me.baron.test.annotation.SingleClick需要替換成
* 你自己項目中SingleClick這個類的全路徑哦
*/
@Pointcut("execution(@me.baron.test.annotation.SingleClick * *(..))")
public void methodAnnotated() {}
/**
* 定義一個切面方法,包裹切點方法
*/
@Around("methodAnnotated()")
public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
// 取出方法的參數(shù)
View view = null;
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof View) {
view = (View) arg;
break;
}
}
if (view == null) {
return;
}
// 取出方法的注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
if (!method.isAnnotationPresent(SingleClick.class)) {
return;
}
SingleClick singleClick = method.getAnnotation(SingleClick.class);
// 判斷是否快速點擊
if (!XClickUtil.isFastDoubleClick(view, singleClick.value())) {
// 不是快速點擊,執(zhí)行原方法
joinPoint.proceed();
}
}
}
使用方法
private void initView() {
btTest = findViewById(R.id.bt_test);
btTest.setOnClickListener(new View.OnClickListener() {
// 如果需要自定義點擊時間間隔,自行傳入毫秒值即可
// @SingleClick(2000)
@SingleClick
@Override
public void onClick(View v) {
// do something
}
});
}
遇到的坑
監(jiān)聽系統(tǒng)的onClick()方法時,有時會多次調(diào)用切點的方法,導致onClick()方法失效,
@Pointcut("execution(* android.view.View.OnClickListener.onClick(..))")
public void methodAnnotated() {}
原因:onClick()方法中又調(diào)用了onClick()方法,被判定為重復點擊,所以點擊事件沒有執(zhí)行,例如:
@Override
public void onClick(View v) {
super.onClick(v);// 重復調(diào)用
}
if (posListener != null) {
btnPositive.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
posListener.onClick(view);// 重復調(diào)用
}
});
參考文章:
Android-如何優(yōu)雅的處理重復點擊
AOP在Android中的應用-過濾重復點擊
Spring AOP @Before @Around @After 等 advice 的執(zhí)行順序