Android 使用 Aspectj 限制快速點(diǎn)擊
在AspectJ 在 Android 中的使用中,介紹了 Aspectj 的基本知識(shí)及其在 Android 中的基本使用,在這篇將會(huì)介紹如何使用 Aspectj 在 Android 中限制快速點(diǎn)擊
[原創(chuàng)]原文鏈接Android 使用 Aspectj 限制快速點(diǎn)擊
1. 配置依賴
建立 clicklimt 的 lib,添加對(duì) Aspect 的依賴,之前我們要做很多的配置工作,滬江的開源庫 gradle_plugin_android_aspectjx 已經(jīng)幫我們弄了,省了很多工作。
在根項(xiàng)目的 build.gradle 配置
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
// 添加 hujiang.aspectjx
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0-rc2'
}
}
在 app 工程的 build.gradle 中使用 AspectJX 插件
apply plugin: 'com.android.application'
apply plugin: 'android-aspectjx' // 使用 AspectJX 插件
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.yxhuang.aspectjlimitclickdemo"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// Butterknife requires Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
aspectjx {
//指定只對(duì)含有關(guān)鍵字'universal-image-loader', 'AspectJX-Demo/library'的庫進(jìn)行織入掃描,忽略其他庫,提升編譯效率
// includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library'
// excludeJarFilter '.jar'
// ajcArgs '-Xlint:warning'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(':clicklimit')
implementation 'com.jakewharton:butterknife:9.0.0-rc1'
annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0-rc1'
}
在 clicklimt 庫的 build.gradle 中添加 aspectj 依賴
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api 'org.aspectj:aspectjrt:1.8.9'
}
2. 具體的處理
1. 建立 ClickLimit 注解
我們會(huì)對(duì)整個(gè)項(xiàng)目中的點(diǎn)擊事件做點(diǎn)擊限制,如果不需要限制的方法,可以設(shè)置 value = 0 即可, 我們默認(rèn)設(shè)置為 500 毫秒。
@Target({ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickLimit {
int value() default 500;
}
2. 選擇 Pointcut
我們這里選擇 View#setOnClickListener 作為切入點(diǎn)
// View#setOnClickListener
private static final String POINTCUT_ON_VIEW_CLICK =
"execution(* android.view.View.OnClickListener.onClick(..))";
對(duì) Joint 的處理
private void processJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
Log.d(TAG, "-----method is click--- ");
try {
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)){
Log.d(TAG, "method is no MethodSignature, so proceed it");
joinPoint.proceed();
return;
}
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
boolean isHasLimitAnnotation = method.isAnnotationPresent(ClickLimit.class);
String methodName = method.getName();
int intervalTime = CHECK_FOR_DEFAULT_TIME;
// 這里判斷是否使用了 ClickLimit 注解
// 如果用注解,并且修改了限制點(diǎn)擊的時(shí)間
// 如果時(shí)間 <= 0 ,代表著不做限制,直接執(zhí)行
// 如果是其他時(shí)間,則更新限制時(shí)間
if (isHasLimitAnnotation){
ClickLimit clickLimit = method.getAnnotation(ClickLimit.class);
int limitTime = clickLimit.value();
// not limit click
if (limitTime <= 0){
Log.d(TAG, "method: " + methodName + " limitTime is zero, so proceed it");
joinPoint.proceed();
return;
}
intervalTime = limitTime;
Log.d(TAG, "methodName " + methodName + " intervalTime is " + intervalTime);
}
// 傳進(jìn)來的參數(shù)不是 View, 則直接執(zhí)行
Object[] args = joinPoint.getArgs();
View view = getViewFromArgs(args);
if (view == null) {
Log.d(TAG, "view is null, proceed");
joinPoint.proceed();
return;
}
// 通過 viewTag 存儲(chǔ)上次點(diǎn)擊的時(shí)間
Object viewTimeTag = view.getTag(R.integer.yxhuang_click_limit_tag_view);
// first click viewTimeTag is null.
if (viewTimeTag == null){
Log.d(TAG, "lastClickTime is zero , proceed");
proceedAnSetTimeTag(joinPoint, view);
return;
}
long lastClickTime = (long) viewTimeTag;
if (lastClickTime <= 0){
Log.d(TAG, "lastClickTime is zero , proceed");
proceedAnSetTimeTag(joinPoint, view);
return;
}
// in limit time
if (!canClick(lastClickTime, intervalTime)){
Log.d(TAG, "is in limit time , return");
return;
}
proceedAnSetTimeTag(joinPoint, view);
Log.d(TAG, "view proceed.");
} catch (Throwable e) {
e.printStackTrace();
Log.d(TAG, e.getMessage());
joinPoint.proceed();
}
}
private void proceedAnSetTimeTag(ProceedingJoinPoint joinPoint, View view) throws Throwable {
view.setTag(R.integer.yxhuang_click_limit_tag_view, System.currentTimeMillis());
joinPoint.proceed();
}
通過 ViewTag 來存儲(chǔ)上次點(diǎn)擊的時(shí)間,如果上次的點(diǎn)擊時(shí)間為 0, 說明是第一次點(diǎn)擊,則立即執(zhí)行;
如果有存儲(chǔ)上次點(diǎn)擊時(shí)間,則通過 canClick 方法配對(duì)時(shí)間,如果是在時(shí)間間隔之內(nèi),不執(zhí)行。
3. 對(duì) clicklimit 庫的使用
在 app module 的 build.gradle 中添加 clicklimit 庫的引用
implementation project(':clicklimit')
我們一個(gè)使用 View#setOnClickListener 方法,一個(gè)使用 ButterKnife 綁定的方式
@BindView(R.id.btn_click)
Button mBtnClick;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
TextView tvSay = findViewById(R.id.tv_say);
tvSay.setOnClickListener(new View.OnClickListener() {
@ClickLimit(value = 1000)
@Override
public void onClick(View v) {
Log.i(TAG, "-----onClick----");
showToast();
}
});
}
private void showToast() {
Toast.makeText(MainActivity.this, "被點(diǎn)擊", Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.btn_click)
public void onViewClicked() {
Log.i(TAG, "-----butterknife method onClick execution----");
showToast();
}
我們對(duì) tvSay 快速點(diǎn)擊兩次,看到 log
=第一次執(zhí)行了, 第二在時(shí)間限制內(nèi),return 掉了
我們點(diǎn)擊一下 butterknife 綁定的 button,看看 log
我們看到 butterknife 綁定的方法也被限制,但是我們的 Poincut 并沒有對(duì)它做限制。
在 app/build/intermediates/transforms/ajx/debug 的路徑下會(huì)生成 jar 包, ajx 這個(gè)路徑就是使用了 android-aspectjx 生成
我們將 0.jar 文件放到軟件 JD-GUI 上面可以看到里面的代碼
其實(shí)是因?yàn)?ButterKnife 會(huì)生成一個(gè) ViewBinding 的類,在里面調(diào)用了
View#setOnClickListener 方法
很多文章都需要對(duì) butterknife 設(shè)置 Pointcut, 其實(shí)這完全是沒有必要的.
2019年10月03日更新
之前的項(xiàng)目有很多不完善的地方,趁著國(guó)慶假期,把整個(gè)項(xiàng)目完善了。
以前項(xiàng)目是默認(rèn)所有的點(diǎn)擊事件都是限制的,現(xiàn)在改為只有加 @ClickLimit 注解才會(huì)進(jìn)行限制
源代碼
private static final String POINTCUT_ON_ANNOTATION =
"execution(@com.yxhuang.clicklimit.annotation.ClickLimit * *(..))";
@Pointcut(POINTCUT_ON_ANNOTATION)
public void onAnnotationClick(){}
使用方法
在項(xiàng)目的根 build.gradle 中添加 aspectjx
dependencies {
...
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
..
}
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
在項(xiàng)目工程的 build.gradle 中添加 的庫的依賴
apply plugin: 'android-aspectjx'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
...
implementation 'com.github.yxhuangCH:AndroidClickLimit:1.0.1'
}
添加上面的依賴,就可使很方便的使用注解限制點(diǎn)擊事件
例子
@ClickLimit(value = 1000)
@OnClick(R.id.btn_click)
public void onViewClicked(View view) { // 必須要傳入一個(gè) View
Log.i(TAG, "-----butterknife method onClick execution----");
showToast();
}
注意,如果是使用了Butterknife 注解,必須要傳入一個(gè) View, 否則限制是不起作用的。
另外如果使用淘寶的 sdk ,需要進(jìn)行排除,在項(xiàng)目中的 build.gradle 中添加
aspectjx {
// 排除淘寶的 sdk
exclude 'com.taobao'
}
具體的使用可以查看我在 github 上的例子 https://github.com/yxhuangCH/AndroidClickLimit