Android 使用 Aspectj 限制快速點(diǎn)擊

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

=
aspectj_7.png

第一次執(zhí)行了, 第二在時(shí)間限制內(nèi),return 掉了

我們點(diǎn)擊一下 butterknife 綁定的 button,看看 log

aspectj_8.png

我們看到 butterknife 綁定的方法也被限制,但是我們的 Poincut 并沒有對(duì)它做限制。

在 app/build/intermediates/transforms/ajx/debug 的路徑下會(huì)生成 jar 包, ajx 這個(gè)路徑就是使用了 android-aspectjx 生成

aspectj_9.png

我們將 0.jar 文件放到軟件 JD-GUI 上面可以看到里面的代碼

aspectj_11.png

其實(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評(píng)論 2 379