日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

限制按钮点击_Android | 使用 AspectJ 限制按钮快速点击

發(fā)布時(shí)間:2023/12/10 Android 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 限制按钮点击_Android | 使用 AspectJ 限制按钮快速点击 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

  • 在Android開(kāi)發(fā)中,限制按鈕快速點(diǎn)擊(按鈕防抖)是一個(gè)常見(jiàn)的需求;
  • 在這篇文章里,我將介紹一種使用AspectJ的方法,基于注解處理器 & 運(yùn)行時(shí)注解反射的原理。如果能幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要。

系列文章

  • 《Android | 一文帶你全面了解 AspectJ 框架》
  • 《Android | 使用 AspectJ 限制按鈕快速點(diǎn)擊》

延伸文章

  • 關(guān)于 反射,請(qǐng)閱讀:《Java | 反射:在運(yùn)行時(shí)訪問(wèn)類型信息(含 Kotlin)》
  • 關(guān)于 注解,請(qǐng)閱讀:《Java | 這是一篇全面的注解使用攻略(含 Kotlin)》
  • 關(guān)于 注解處理器(APT),請(qǐng)閱讀:《Java | 注解處理器(APT)原理解析 & 實(shí)踐》

目錄


1. 定義需求

在開(kāi)始講解之前,我們先 定義需求,具體描述如下:

  • 限制快速點(diǎn)擊需求 示意圖:


2. 常規(guī)處理方法

目前比較常見(jiàn)的限制快速點(diǎn)擊的處理方法有以下兩種,具體如下:

2.1 封裝代理類

封裝一個(gè)代理類處理點(diǎn)擊事件,代理類通過(guò)判斷點(diǎn)擊間隔決定是否攔截點(diǎn)擊事件,具體代碼如下:

// 代理類 public abstract class FastClickListener implements View.OnClickListener {private long mLastClickTime;private long interval = 1000L;public FastClickListener() {}public FastClickListener(long interval) {this.interval = interval;}@Overridepublic void onClick(View v) {long currentTime = System.currentTimeMillis();if (currentTime - mLastClickTime > interval) {// 經(jīng)過(guò)了足夠長(zhǎng)的時(shí)間,允許點(diǎn)擊onClick();mLastClickTime = nowTime;} }protected abstract void onClick(); }

在需要限制快速點(diǎn)擊的地方使用該代理類,具體如下:

tv.setOnClickListener(new FastClickListener() {@Overrideprotected void onClick() {// 處理點(diǎn)擊邏輯} });

2.2 RxAndroid 過(guò)濾表達(dá)式

使用RxJava的過(guò)濾表達(dá)式throttleFirst也可以限制快速點(diǎn)擊,具體如下:

RxView.clicks(view).throttleFirst(1, TimeUnit.SECONDS).subscribe(new Consumer<Object>() {@Overridepublic void accept(Object o) throws Exception {// 處理點(diǎn)擊邏輯}});

2.3 小結(jié)

代理類和RxAndroid過(guò)濾表達(dá)式這兩種處理方法都存在兩個(gè)缺點(diǎn): - 1. 侵入核心業(yè)務(wù)邏輯,需要將代碼替換到需要限制點(diǎn)擊的地方; - 2. 修改工作量大,每一個(gè)增加限制點(diǎn)擊的地方都要修改代碼。

我們需要一種方案能夠規(guī)避這兩個(gè)缺點(diǎn) —— AspectJ。 AspectJ是一個(gè)流行的Java AOP(aspect-oriented programming)編程擴(kuò)展框架,若還不了解,請(qǐng)務(wù)必查看文章:《Android | 一文帶你全面了解 AspectJ 框架》


3. 詳細(xì)步驟

在下面的內(nèi)容里,我們將使用AspectJ框架,把限制快速點(diǎn)擊的邏輯作為核心關(guān)注點(diǎn)從業(yè)務(wù)邏輯中抽離出來(lái),單獨(dú)維護(hù)。具體步驟如下:

步驟1:添加AspectJ依賴

  • 1、依賴滬江的AspectJXGradle插件 —— 在項(xiàng)目build.gradle中添加插件依賴:
// 項(xiàng)目級(jí)build.gradle dependencies {classpath 'com.android.tools.build:gradle:3.5.3'classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8' }

如果插件下載速度過(guò)慢,可以直接依賴插件 jar文件,將插件下載到項(xiàng)目根目錄(如/plugins),然后在項(xiàng)目build.gradle中添加插件依賴:

// 項(xiàng)目級(jí)build.gradle dependencies {classpath 'com.android.tools.build:gradle:3.5.3'classpath fileTree(dir:'plugins', include:['*.jar']) }
  • 2、應(yīng)用插件 —— 在App Module的build.gradle中應(yīng)用插件:
// App Module的build.gradle apply plugin: 'android-aspectjx' ...
  • 3、依賴AspectJ框架 —— 在包含AspectJ代碼的Module的build.gradle文件中添加依賴:
// Module級(jí)build.gradle dependencies {...api 'org.aspectj:aspectjrt:1.8.9'... }

步驟2:實(shí)現(xiàn)判斷快速點(diǎn)擊的工具類

  • 我們先實(shí)現(xiàn)一個(gè)判斷View是否快速點(diǎn)擊的工具類;
  • 實(shí)現(xiàn)原理是使用View的tag屬性存儲(chǔ)最近一次的點(diǎn)擊時(shí)間,每次點(diǎn)擊時(shí)判斷當(dāng)前時(shí)間距離存儲(chǔ)的時(shí)間是否已經(jīng)經(jīng)過(guò)了足夠長(zhǎng)的時(shí)間;
  • 為了避免調(diào)用View#setTag(int key,Object tag)時(shí)傳入的key與其他地方傳入的key沖突而造成覆蓋,務(wù)必使用在資源文件中定義的 id,資源文件中的 id 能夠有效保證全局唯一性,具體如下:
// ids.xml <resources><item type="id" name="view_click_time" /> </resources> public class FastClickCheckUtil {/*** 判斷是否屬于快速點(diǎn)擊** @param view 點(diǎn)擊的View* @param interval 快速點(diǎn)擊的閾值* @return true:快速點(diǎn)擊*/public static boolean isFastClick(@NonNull View view, long interval) {int key = R.id.view_click_time;// 最近的點(diǎn)擊時(shí)間long currentClickTime = System.currentTimeMillis();if(null == view.getTag(key)){// 1. 第一次點(diǎn)擊// 保存最近點(diǎn)擊時(shí)間view.setTag(key, currentClickTime);return false;}// 2. 非第一次點(diǎn)擊// 上次點(diǎn)擊時(shí)間long lastClickTime = (long) view.getTag(key);if(currentClickTime - lastClickTime < interval){// 未超過(guò)時(shí)間間隔,視為快速點(diǎn)擊return true;}else{// 保存最近點(diǎn)擊時(shí)間view.setTag(key, currentClickTime);return false;}} }

步驟3:定義Aspect切面

使用@Aspect注解定義一個(gè)切面,使用該注解修飾的類會(huì)被AspectJ編譯器識(shí)別為切面類:

@Aspect public class FastClickCheckerAspect {// 隨后填充 }

步驟4:定義PointCut切入點(diǎn)

使用@Pointcut注解定義一個(gè)切入點(diǎn),編譯期AspectJ編譯器將搜索所有匹配的JoinPoint,執(zhí)行織入:

@Aspect public class FastClickAspect {// 定義一個(gè)切入點(diǎn):View.OnClickListener#onClick()方法@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")public void methodViewOnClick() {}// 隨后填充 Advice }

步驟5:定義Advice增強(qiáng)

增強(qiáng)的方式有很多種,在這里我們使用@Around注解定義環(huán)繞增強(qiáng),它將包裝PointCut,在PointCut前后增加橫切邏輯,具體如下:

@Aspect public class FastClickAspect {// 定義切入點(diǎn):View.OnClickListener#onClick()方法@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")public void methodViewOnClick() {}// 定義環(huán)繞增強(qiáng),包裝methodViewOnClick()切入點(diǎn)@Around("methodViewOnClick()")public void aroundViewOnClick(ProceedingJoinPoint joinPoint) throws Throwable {// 取出目標(biāo)對(duì)象View target = (View) joinPoint.getArgs()[0];// 根據(jù)點(diǎn)擊間隔是否超過(guò)2000,判斷是否為快速點(diǎn)擊if (!FastClickCheckUtil.isFastClick(target, 2000)) {joinPoint.proceed();}} }

步驟6:實(shí)現(xiàn)View.OnClickListener

在這一步我們?yōu)閂iew設(shè)置OnClickListener,可以看到我們并沒(méi)有添加限制快速點(diǎn)擊的相關(guān)代碼,增強(qiáng)的邏輯對(duì)原有邏輯沒(méi)有侵入,具體代碼如下:

// 源碼: public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.i("AspectJ","click");}});} }

編譯代碼,隨后反編譯AspectJ編譯器執(zhí)行織入后的.class文件。還不了解如何查找編譯后的.class文件,請(qǐng)務(wù)必查看文章:《Android | 一文帶你全面了解 AspectJ 框架》

public class MainActivity extends AppCompatActivity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(2131361820);findViewById(2131165349).setOnClickListener(new View.OnClickListener() {private static final JoinPoint.StaticPart ajc$tjp_0;// View.OnClickListener#onClick()public void onClick(View v) {View view = v;// 重構(gòu)JoinPoint,執(zhí)行環(huán)繞增強(qiáng),也執(zhí)行@Around修飾的方法JoinPoint joinPoint = Factory.makeJP(ajc$tjp_0, this, this, view);onClick_aroundBody1$advice(this, view, joinPoint, FastClickAspect.aspectOf(), (ProceedingJoinPoint)joinPoint);}static {ajc$preClinit();}private static void ajc$preClinit() {Factory factory = new Factory("MainActivity.java", null.class);ajc$tjp_0 = factory.makeSJP("method-execution", (Signature)factory.makeMethodSig("1", "onClick", "com.have.a.good.time.aspectj.MainActivity$1", "android.view.View", "v", "", "void"), 25);}// 原來(lái)在View.OnClickListener#onClick()中的代碼,相當(dāng)于核心業(yè)務(wù)邏輯private static final void onClick_aroundBody0(null ajc$this, View v, JoinPoint param1JoinPoint) {Log.i("AspectJ", "click");}// @Around方法中的代碼,即源碼中的aroundViewOnClick(),相當(dāng)于Adviceprivate static final void onClick_aroundBody1$advice(null ajc$this, View v, JoinPoint thisJoinPoint, FastClickAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint) {View target = (View)joinPoint.getArgs()[0];if (!FastClickCheckUtil.isFastClick(target, 2000)) {// 非快速點(diǎn)擊,執(zhí)行點(diǎn)擊邏輯ProceedingJoinPoint proceedingJoinPoint = joinPoint;onClick_aroundBody0(ajc$this, v, (JoinPoint)proceedingJoinPoint);null;} }});} }

小結(jié)

到這里,我們就講解完使用AspectJ框架限制按鈕快速點(diǎn)擊的詳細(xì),總結(jié)如下: - 使用@Aspect注解描述一個(gè)切面,使用該注解修飾的類會(huì)被AspectJ編譯器識(shí)別為切面類; - 使用@Pointcut注解定義一個(gè)切入點(diǎn),編譯期AspectJ編譯器將搜索所有匹配的JoinPoint,執(zhí)行織入; - 使用@Around注解定義一個(gè)增強(qiáng),增強(qiáng)會(huì)被織入匹配的JoinPoint


4. 演進(jìn)

現(xiàn)在,我們回歸文章開(kāi)頭定義的需求,總共有4點(diǎn)。其中前兩點(diǎn)使用目前的方案中已經(jīng)能夠?qū)崿F(xiàn),現(xiàn)在我們關(guān)注后面兩點(diǎn),即允許定制時(shí)間間隔與覆蓋盡可能多的點(diǎn)擊場(chǎng)景。

  • 需求回歸 示意圖:

4.1 定制時(shí)間間隔

在實(shí)際項(xiàng)目不同場(chǎng)景中的按鈕,往往需要限制不同的點(diǎn)擊時(shí)間間隔,因此我們需要有一種簡(jiǎn)便的方式用于定制不同場(chǎng)景的時(shí)間間隔,或者對(duì)于一些不需要限制快速點(diǎn)擊的地方,有辦法跳過(guò)快速點(diǎn)擊判斷,具體方法如下: - 定義注解

/*** 在需要定制時(shí)間間隔地方添加@FastClick注解*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface FastClick {long interval() default FastClickAspect.FAST_CLICK_INTERVAL_GLOBAL; }
  • 修改切面類的Advice
@Aspect public class SingleClickAspect {public static final long FAST_CLICK_INTERVAL_GLOBAL = 1000L;@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")public void methodViewOnClick() {}@Around("methodViewOnClick()")public void aroundViewOnClick(ProceedingJoinPoint joinPoint) throws Throwable {// 取出JoinPoint的簽名MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();// 取出JoinPoint的方法Method method = methodSignature.getMethod();// 1. 全局統(tǒng)一的時(shí)間間隔long interval = FAST_CLICK_INTERVAL_GLOBAL;if (method.isAnnotationPresent(FastClick.class)) {// 2. 如果方法使用了@FastClick修飾,取出定制的時(shí)間間隔FastClick singleClick = method.getAnnotation(FastClick.class);interval = singleClick.interval();}// 取出目標(biāo)對(duì)象View target = (View) joinPoint.getArgs()[0];// 3. 根據(jù)點(diǎn)擊間隔是否超過(guò)interval,判斷是否為快速點(diǎn)擊if (!FastClickCheckUtil.isFastClick(target, interval)) {joinPoint.proceed();}} }
  • 使用注解
findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {@FastClick(interval = 5000L)@Overridepublic void onClick(View v) {Log.i("AspectJ","click");} });

4.2 完整場(chǎng)景覆蓋

ButterKnife @OnClick android:onClick OK RecyclerView / ListView Java Lambda NO Kotlin Lambda OK DataBinding OK

Editting...


推薦閱讀

密碼學(xué) | Base64 是加密算法嗎??juejin.im算法面試題 | 回溯算法解題框架?juejin.im算法面試題 | 鏈表問(wèn)題總結(jié)?juejin.imJava | 帶你理解 ServiceLoader 的原理與設(shè)計(jì)思想?juejin.im計(jì)算機(jī)網(wǎng)絡(luò) | 圖解 DNS & HTTPDNS 原理?juejin.imAndroid | 說(shuō)說(shuō)從 android:text 到 TextView 的過(guò)程?juejin.imAndroid | 面試必問(wèn)的 Handler,你確定不看看??www.jianshu.comAndroid | 帶你探究 LayoutInflater 布局解析原理?juejin.imAndroid | View & Fragment & Window 的 getContext() 一定返回 Activity 嗎??juejin.im

感謝喜歡!你的點(diǎn)贊是對(duì)我最大的鼓勵(lì)!歡迎關(guān)注彭旭銳的GitHub!

總結(jié)

以上是生活随笔為你收集整理的限制按钮点击_Android | 使用 AspectJ 限制按钮快速点击的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。