Android 中使用 Aspectj 限制快速点击

在AspectJ 在 Android 中的使用中,介绍了 Aspectj 的基本知识及其在 Android 中的基本使用,在这篇将会介绍如何使用 Aspectj 在 Android 中限制快速点击

[原文原创]Android 中使用 Aspectj 限制快速点击

1. 配置依赖

建立 clicklimt 的 lib,添加对 Aspect 的依赖,之前我们要做很多的配置工作,沪江的开源库 gradle_plugin_android_aspectjx 已经帮我们弄了,省了很多工作。

在根项目的 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 {    //指定只对含有关键字'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 注解

我们会对整个项目中的点击事件做点击限制,如果不需要限制的方法,可以设置 value = 0 即可, 我们默认设置为 500 毫秒。

@Target({ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)public @interface ClickLimit {    int value() default 500;}

2. 选择 Pointcut

我们这里选择 View#setOnClickListener 作为切入点

// View#setOnClickListenerprivate 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 ,代表着不做限制,直接执行        // 如果是其他时间,则更新限制时间        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);        }        // 传进来的参数不是 View, 则直接执行        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, 说明是第一次点击,则立即执行;
如果有存储上次点击时间,则通过 canClick 方法配对时间,如果是在时间间隔之内,不执行。

3. 对 clicklimit 库的使用

在 app module 的 build.gradle 中添加 clicklimit 库的引用

implementation project(':clicklimit')

我们一个使用 View#setOnClickListener 方法,一个使用 ButterKnife 绑定的方式

@BindView(R.id.btn_click)Button mBtnClick;@Overrideprotected 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

Android 中使用 Aspectj 限制快速点击_第1张图片

第一次执行了, 第二在时间限制内,return 掉了

我们点击一下 butterknife 绑定的 button,看看 log

Android 中使用 Aspectj 限制快速点击_第2张图片

我们看到 butterknife 绑定的方法也被限制,但是我们的 Poincut 并没有对它做限制。

在 app/build/intermediates/transforms/ajx/debug 的路径下会生成 jar 包, ajx 这个路径就是使用了 android-aspectjx 生成

Android 中使用 Aspectj 限制快速点击_第3张图片

我们将 0.jar 文件放到软件 JD-GUI 上面可以看到里面的代码

Android 中使用 Aspectj 限制快速点击_第4张图片

其实是因为 ButterKnife 会生成一个 ViewBinding 的类,在里面调用了
View#setOnClickListener 方法

很多文章都需要对 butterknife 设置 Pointcut, 其实这完全是没有必要的.

更多相关文章

  1. Android高手进阶教程(二十)之---Android与JavaScript方法相互调
  2. android中的坐标系以及获取坐标的方法
  3. Android与JavaScript方法相互调用
  4. 创建Android库的方法及Android .aar文件用法小结
  5. Android各种资源引用的方法
  6. Android 性能优化的一些方法
  7. android 让一个控件按钮居于底部的几种方法

随机推荐

  1. MySQL MGR 有哪些优点
  2. mysql数据库中字符集乱码问题原因及解决
  3. MySql如何实现远程登录MySql数据库过程解
  4. MySql添加新用户及为用户创建数据库和给
  5. MySQL数据库高级查询和多表查询
  6. MySQL 详细单表增删改查crud语句
  7. MySQL使用聚合函数进行单表查询
  8. MySQL数据定义语言DDL的基础语句
  9. Mysql之组合索引方法详解
  10. mysql中 ${param}与#{param}使用区别