Android 使用 Aspectj 限制快速點擊
在AspectJ 在 Android 中的使用中钧舌,介紹了 Aspectj 的基本知識及其在 Android 中的基本使用,在這篇將會介紹如何使用 Aspectj 在 Android 中限制快速點擊
[原創(chuàng)]原文鏈接Android 使用 Aspectj 限制快速點擊
1. 配置依賴
建立 clicklimt 的 lib豁陆,添加對 Aspect 的依賴扭吁,之前我們要做很多的配置工作端圈,滬江的開源庫 gradle_plugin_android_aspectjx 已經(jī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 {
//指定只對含有關(guān)鍵字'universal-image-loader', 'AspectJX-Demo/library'的庫進行織入掃描睬涧,忽略其他庫募胃,提升編譯效率
// 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 注解
我們會對整個項目中的點擊事件做點擊限制,如果不需要限制的方法畦浓,可以設(shè)置 value = 0 即可, 我們默認設(shè)置為 500 毫秒痹束。
@Target({ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickLimit {
int value() default 500;
}
2. 選擇 Pointcut
我們這里選擇 View#setOnClickListener 作為切入點
// View#setOnClickListener
private static final String POINTCUT_ON_VIEW_CLICK =
"execution(* android.view.View.OnClickListener.onClick(..))";
對 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 注解
// 如果用注解,并且修改了限制點擊的時間
// 如果時間 <= 0 讶请,代表著不做限制祷嘶,直接執(zhí)行
// 如果是其他時間,則更新限制時間
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);
}
// 傳進來的參數(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 存儲上次點擊的時間
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 來存儲上次點擊的時間夺溢,如果上次的點擊時間為 0论巍, 說明是第一次點擊,則立即執(zhí)行风响;
如果有存儲上次點擊時間嘉汰,則通過 canClick 方法配對時間,如果是在時間間隔之內(nèi)状勤,不執(zhí)行鞋怀。
3. 對 clicklimit 庫的使用
在 app module 的 build.gradle 中添加 clicklimit 庫的引用
implementation project(':clicklimit')
我們一個使用 View#setOnClickListener 方法,一個使用 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, "被點擊", Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.btn_click)
public void onViewClicked() {
Log.i(TAG, "-----butterknife method onClick execution----");
showToast();
}
我們對 tvSay 快速點擊兩次持搜,看到 log
=第一次執(zhí)行了密似, 第二在時間限制內(nèi),return 掉了
我們點擊一下 butterknife 綁定的 button,看看 log
我們看到 butterknife 綁定的方法也被限制葫盼,但是我們的 Poincut 并沒有對它做限制残腌。
在 app/build/intermediates/transforms/ajx/debug 的路徑下會生成 jar 包, ajx 這個路徑就是使用了 android-aspectjx 生成
我們將 0.jar 文件放到軟件 JD-GUI 上面可以看到里面的代碼
其實是因為 ButterKnife 會生成一個 ViewBinding 的類,在里面調(diào)用了
View#setOnClickListener 方法
很多文章都需要對 butterknife 設(shè)置 Pointcut, 其實這完全是沒有必要的.
2019年10月03日更新
之前的項目有很多不完善的地方废累,趁著國慶假期邓梅,把整個項目完善了。
以前項目是默認所有的點擊事件都是限制的邑滨,現(xiàn)在改為只有加 @ClickLimit 注解才會進行限制
源代碼
private static final String POINTCUT_ON_ANNOTATION =
"execution(@com.yxhuang.clicklimit.annotation.ClickLimit * *(..))";
@Pointcut(POINTCUT_ON_ANNOTATION)
public void onAnnotationClick(){}
使用方法
在項目的根 build.gradle 中添加 aspectjx
dependencies {
...
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
..
}
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
在項目工程的 build.gradle 中添加 的庫的依賴
apply plugin: 'android-aspectjx'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
...
implementation 'com.github.yxhuangCH:AndroidClickLimit:1.0.1'
}
添加上面的依賴日缨,就可使很方便的使用注解限制點擊事件
例子
@ClickLimit(value = 1000)
@OnClick(R.id.btn_click)
public void onViewClicked(View view) { // 必須要傳入一個 View
Log.i(TAG, "-----butterknife method onClick execution----");
showToast();
}
注意,如果是使用了Butterknife 注解掖看,必須要傳入一個 View, 否則限制是不起作用的匣距。
另外如果使用淘寶的 sdk ,需要進行排除,在項目中的 build.gradle 中添加
aspectjx {
// 排除淘寶的 sdk
exclude 'com.taobao'
}
具體的使用可以查看我在 github 上的例子 https://github.com/yxhuangCH/AndroidClickLimit