由于簡書近期對平臺內(nèi)容進(jìn)行審核,導(dǎo)致之前的這篇文章無法訪問,所以重新發(fā)一次
最近項目進(jìn)入緊鑼密鼓測試階段,昨天測試提了一個issue,app中按鈕都沒有做快速點(diǎn)擊校驗挫望。
這就涉及到aop面向切面編程了!后端開發(fā)Spring對aop應(yīng)該很熟悉,android開發(fā)中可能用到aop的情況沒有后端那么多,但是aop對android開發(fā)也是至關(guān)重要的!
哪些情況用到aop?
- 比如針對某一功能進(jìn)行埋點(diǎn)
- 全局日志處理
- 全局異常處理
- 全局動畫處理等
java aop大致有三種方式
1.jdk動態(tài)代理
2.cglib動態(tài)代理
3.aspectj
. | jdk動態(tài)代理 | cglib | aspectj |
---|---|---|---|
作用對象的限制 | 只能操作實現(xiàn)了接口的類 | 不能操作被final修飾的類,因為cglib是針對類實現(xiàn)代理,主要是對指定的類生成一個子類白华,覆蓋其中的方法來實現(xiàn)代理. | 貌似沒什么限制 |
基本原理 | 利用攔截器(攔截器必須實現(xiàn)InvocationHanlder)加上反射機(jī)制生成一個實現(xiàn)代理接口的匿名類走越,在調(diào)用具體方法前調(diào)用InvokeHandler來處理椭豫。 | 利用ASM開源包,對代理對象類的class文件加載進(jìn)來旨指,通過修改其字節(jié)碼生成子類來處理捻悯。 | 采用基于jvm的ajc(編譯器)和weaver(織入器),在class字節(jié)碼中織入aspectj的代碼 |
項目中我使用的是https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx 它是在aspectj基礎(chǔ)上做了些修改,支持AS的instant run,集成比aspectj更加方便
首先在project下的gradle文件中加入aspectjx插件
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/jcenter/' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
google()
jcenter()
maven { url 'https://jitpack.io' }
maven { url 'https://dl.bintray.com/umsdk/release' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-beta02"
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/jcenter/' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
google()
jcenter()
maven { url 'https://jitpack.io' }
maven { url 'https://dl.bintray.com/umsdk/release' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
ext {
compileSdkVersion = 28
minSdkVersion = 17
targetSdkVersion = 27
versionCode=5
versionName="1.4.6"
testRunner="1.1.1"
espresso="3.1.1"
junit="4.12"
appcompat="1.1.0-alpha01"
supportLibVersion = "28.0.0"
}
module下的gradle文件添加
apply plugin: 'android-aspectjx'
接下來就可以開始編寫被@AspectJ 修飾的切面類了
AspectHandler.java 用來對點(diǎn)擊事件相關(guān)的攔截處理
package com.mjt.pad.common.aspect;
import android.util.Log;
import com.mjt.common.utils.UIUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* Copyright:mjt_pad_android
* Author: liyang <br>
* Date:2019-05-05 15:41<br>
* Desc: <br>
*/
@Aspect
public class AspectHandler {
private static final String TAG = AspectHandler.class.getSimpleName();
@Before("execution(void android.view.View.OnClickListener.onClick(..))")
public void beforePoint(JoinPoint joinPoint) {
Log.e(TAG, "before: " + joinPoint);
}
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void dealWithNormal() {
}
@Around("dealWithNormal()")
public void onViewClicked(ProceedingJoinPoint proceedingJoinPoint) {
boolean isFastClickPassed = !UIUtils.isFastClickOnlyInAspect();
Log.e(TAG, "onViewClicked: 捕獲到了,isFastClick=" + !isFastClickPassed);
if (isFastClickPassed) {
Log.e(TAG, "onViewClicked: " + proceedingJoinPoint);
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
Log.e(TAG, "onViewClicked: ", throwable);
}
}
}
}
簡單介紹下這個類里面的@Before @Pointcut @Around幾個注解吧,不然完全沒接觸過aspectj的同學(xué)會看的一頭霧水
首先說pointcut
@Pointcut相當(dāng)于你要攔截的某些執(zhí)行點(diǎn)或者調(diào)用點(diǎn),pointcut可以有call,execution,target,this,within,withincode等等操作符,這些操作符可以結(jié)合java的||,&&,!使用
call捕獲的joinpoint是簽名方法的調(diào)用點(diǎn),而execution捕獲的則是執(zhí)行點(diǎn)淤毛。
call和execution的語法
within()的參數(shù)是一個類,比如我們可以通過within(A.class)或者!within(A.class)來過濾想要攔截的點(diǎn)
withincode()和within()相似,只不過withincode()接收的參數(shù)是方法的signature
target()判斷目標(biāo)對象是否是某種類型,this()判斷當(dāng)前執(zhí)行對象是否是某種類型
call和execution的語法結(jié)構(gòu):
execution/call([注解] [修飾符] 返回值類型 [類型聲明]方法名(參數(shù)列表)[ 異常列表]),被[]括住的是非必須項.
舉例:
execution (* com.mjt..*.*(..))
1今缚、execution(): 表達(dá)式主體。
2低淡、第一個*號:表示返回類型姓言,*號表示所有的類型。
3蔗蹋、包名:表示需要攔截的包名何荚,后面的兩個句點(diǎn)表示當(dāng)前包和當(dāng)前包的所有子包,com.mjtl包猪杭、子孫包下所有類的方法餐塘。
4、第二個*號:表示類名皂吮,*號表示所有的類戒傻。
5税手、*(..):最后這個星號表示方法名,*號表示所有的方法需纳,后面括弧里面表示方法的參數(shù)芦倒,兩個句點(diǎn)表示任何參數(shù)。
@Before在攔截點(diǎn)或者調(diào)用點(diǎn)之前調(diào)用
@After是在攔截點(diǎn)或者調(diào)用點(diǎn)之后調(diào)用
被@Around注解的方法,會被織入到攔截方法調(diào)用點(diǎn)或這行點(diǎn)之前,
接著我運(yùn)行項目,編譯通過后,快速的點(diǎn)擊了一個按鈕
log日志顯示
2019-05-08 11:43:41.717 14103-14103/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.adapter.ProductAdapter.1.onClick(View))
2019-05-08 11:43:41.718 14103-14103/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕獲到了,isFastClick=false
2019-05-08 11:43:41.718 14103-14103/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.adapter.ProductAdapter.1.onClick(View))
2019-05-08 11:43:41.883 14103-14103/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.adapter.ProductAdapter.1.onClick(View))
2019-05-08 11:43:41.883 14103-14103/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕獲到了,isFastClick=true
執(zhí)行了,before方法,然后進(jìn)入被@Around修飾的方法,看到第一次點(diǎn)擊判斷不是快速點(diǎn)擊
放過攔截,proceedingJoinPoint.proceed();原方法得到執(zhí)行!
第二次點(diǎn)擊,判斷是快速點(diǎn)擊,proceedingJoinPoint.proceed()沒有執(zhí)行,也就是原方法被攔截掉了!
但是這時候我發(fā)現(xiàn)如果點(diǎn)擊事件是使用lambda表達(dá)式是無法攔截的,因為這里的pointcut的execution是這樣
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void dealWithNormal() {
}
這個正則的大致意思是,攔截返回類型為void, android.view.View.OnClickListener.onClick()方法,參數(shù)(..)表示參數(shù)可以是任意數(shù)量任意類型
那么接著寫pointcut攔截lambda表達(dá)式的點(diǎn)擊事件
于是AspectHandler 切面類被我改成了這樣
package com.mjt.pad.common.aspect;
import android.util.Log;
import com.mjt.common.utils.UIUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* Copyright:mjt_pad_android
* Author: liyang <br>
* Date:2019-05-05 15:41<br>
* Desc: <br>
*/
@Aspect
public class AspectHandler {
private static final String TAG = AspectHandler.class.getSimpleName();
@Before("dealWithNormal()||dealWithLambda()")
public void beforePoint(JoinPoint joinPoint) {
Log.e(TAG, "before: " + joinPoint);
}
@Pointcut("execution(void com.mjt..lambda*(android.view.View))")
public void dealWithLambda() {
}
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void dealWithNormal() {
}
@Around("dealWithNormal()||dealWithLambda()")
public void onViewClicked(ProceedingJoinPoint proceedingJoinPoint) {
boolean isFastClickPassed = !UIUtils.isFastClickOnlyInAspect();
Log.e(TAG, "onViewClicked: 捕獲到了,isFastClick=" + !isFastClickPassed);
if (isFastClickPassed) {
Log.e(TAG, "onViewClicked: " + proceedingJoinPoint);
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
Log.e(TAG, "onViewClicked: ", throwable);
}
}
}
}
增加的pointcut對點(diǎn)擊事件采用lambda表達(dá)式的攔截
然后我快速的點(diǎn)擊了一個采用lambda表達(dá)式方式實現(xiàn)的點(diǎn)擊事件log日志如下
2019-05-08 11:55:05.506 15052-15052/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.fragment.print.PrintManagerFragment.lambda$initViews$1(View))
2019-05-08 11:55:05.506 15052-15052/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕獲到了,isFastClick=false
2019-05-08 11:55:05.506 15052-15052/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.fragment.print.PrintManagerFragment.lambda$initViews$1(View))
2019-05-08 11:55:05.755 15052-15052/com.mjt.pad.test E/AspectHandler: before: execution(void com.mjt.pad.ui.fragment.print.PrintManagerFragment.lambda$initViews$1(View))
2019-05-08 11:55:05.755 15052-15052/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕獲到了,isFastClick=true
嗯,lambda方式實現(xiàn)的點(diǎn)擊事件也被攔截到了
接下來,如果某個小伙伴或我這個按鈕不要攔截快速點(diǎn)擊,那怎么辦呢?
嗯,采用自定義注解,如果某個onClick方法請求放過快速點(diǎn)擊攔截,加上這個注解就好了
接著我們寫一個自定義注解就叫Ignore
作用于CLASS,修飾的目標(biāo)為方法和構(gòu)造函數(shù)
package com.mjt.pad.common.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Copyright:mjt_pad_android
* Author: liyang <br>
* Date:2019-05-05 16:35<br>
* Desc: <br>
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface Ignore {
}
接著改動AspectHandler切面類
package com.mjt.pad.common.aspect;
import android.util.Log;
import com.mjt.common.utils.UIUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* Copyright:mjt_pad_android
* Author: liyang <br>
* Date:2019-05-05 15:41<br>
* Desc: <br>
*/
@Aspect
public class AspectHandler {
private static final String TAG = AspectHandler.class.getSimpleName();
private volatile boolean isIgnored = false;
@Before("execution(@com.mjt.pad.common.aspect.Ignore void com.mjt..*.onClick(..))")
public void checkIgnore(JoinPoint joinPoint) {
isIgnored = true;
Log.e(TAG, "checkIgnore: " + joinPoint);
}
@Pointcut("execution(void com.mjt..lambda*(android.view.View))")
public void dealWithLambda() {
}
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")
public void dealWithNormal() {
}
@Around("dealWithNormal()||dealWithLambda()")
public void onViewClicked(ProceedingJoinPoint proceedingJoinPoint) {
boolean isFastClickPassed = !UIUtils.isFastClickOnlyInAspect();
Log.e(TAG, "onViewClicked: 捕獲到了,isFastClick=" + !isFastClickPassed+",isIgnored="+isIgnored);
if (isIgnored||isFastClickPassed) {
Log.e(TAG, "onViewClicked: " + proceedingJoinPoint);
try {
proceedingJoinPoint.proceed();
isIgnored=false;
} catch (Throwable throwable) {
throwable.printStackTrace();
Log.e(TAG, "onViewClicked: ", throwable);
}
}
}
}
對@Before方法進(jìn)行了修改
@Before("execution(@com.mjt.pad.common.aspect.Ignore void com.mjt..*.onClick(..))")
public void checkIgnore(JoinPoint joinPoint) {
isIgnored = true;
Log.e(TAG, "checkIgnore: " + joinPoint);
}
這"execution(@com.mjt.pad.common.aspect.Ignore void com.mjt..*.onClick(..))"
匹配的是被我們自定義注解@Ignore修飾的 com.mjt包及其子包下的所有onClick方法
然后我們找一個onClick方法加上@Ignore注解看看起作用沒
@Ignore
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.llyPart:
...
接著找到這個被Ignore修飾的點(diǎn)擊事件,快速點(diǎn)擊兩下
log日志如下
2019-05-08 12:19:06.925 16912-16912/com.mjt.pad.test E/AspectHandler: checkIgnore: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))
2019-05-08 12:19:06.925 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕獲到了,isFastClick=false,isIgnored=true
2019-05-08 12:19:06.926 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))
2019-05-08 12:19:07.086 16912-16912/com.mjt.pad.test E/AspectHandler: checkIgnore: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))
2019-05-08 12:19:07.087 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: 捕獲到了,isFastClick=true,isIgnored=true
2019-05-08 12:19:07.087 16912-16912/com.mjt.pad.test E/AspectHandler: onViewClicked: execution(void com.mjt.pad.ui.fragment.RemarkFragment.onClick(View))
可以看到,是快速點(diǎn)擊,但是原方法也得到了執(zhí)行
嗯 對項目的全局處理點(diǎn)擊事件大致就是這樣,aspectj相關(guān)的東西有很多