限制按钮点击_Android | 使用 AspectJ 限制按钮快速点击
前言
- 在Android開發中,限制按鈕快速點擊(按鈕防抖)是一個常見的需求;
- 在這篇文章里,我將介紹一種使用AspectJ的方法,基于注解處理器 & 運行時注解反射的原理。如果能幫上忙,請務必點贊加關注,這真的對我非常重要。
系列文章
- 《Android | 一文帶你全面了解 AspectJ 框架》
- 《Android | 使用 AspectJ 限制按鈕快速點擊》
延伸文章
- 關于 反射,請閱讀:《Java | 反射:在運行時訪問類型信息(含 Kotlin)》
- 關于 注解,請閱讀:《Java | 這是一篇全面的注解使用攻略(含 Kotlin)》
- 關于 注解處理器(APT),請閱讀:《Java | 注解處理器(APT)原理解析 & 實踐》
目錄
1. 定義需求
在開始講解之前,我們先 定義需求,具體描述如下:
- 限制快速點擊需求 示意圖:
2. 常規處理方法
目前比較常見的限制快速點擊的處理方法有以下兩種,具體如下:
2.1 封裝代理類
封裝一個代理類處理點擊事件,代理類通過判斷點擊間隔決定是否攔截點擊事件,具體代碼如下:
// 代理類 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) {// 經過了足夠長的時間,允許點擊onClick();mLastClickTime = nowTime;} }protected abstract void onClick(); }在需要限制快速點擊的地方使用該代理類,具體如下:
tv.setOnClickListener(new FastClickListener() {@Overrideprotected void onClick() {// 處理點擊邏輯} });2.2 RxAndroid 過濾表達式
使用RxJava的過濾表達式throttleFirst也可以限制快速點擊,具體如下:
RxView.clicks(view).throttleFirst(1, TimeUnit.SECONDS).subscribe(new Consumer<Object>() {@Overridepublic void accept(Object o) throws Exception {// 處理點擊邏輯}});2.3 小結
代理類和RxAndroid過濾表達式這兩種處理方法都存在兩個缺點: - 1. 侵入核心業務邏輯,需要將代碼替換到需要限制點擊的地方; - 2. 修改工作量大,每一個增加限制點擊的地方都要修改代碼。
我們需要一種方案能夠規避這兩個缺點 —— AspectJ。 AspectJ是一個流行的Java AOP(aspect-oriented programming)編程擴展框架,若還不了解,請務必查看文章:《Android | 一文帶你全面了解 AspectJ 框架》
3. 詳細步驟
在下面的內容里,我們將使用AspectJ框架,把限制快速點擊的邏輯作為核心關注點從業務邏輯中抽離出來,單獨維護。具體步驟如下:
步驟1:添加AspectJ依賴
- 1、依賴滬江的AspectJXGradle插件 —— 在項目build.gradle中添加插件依賴:
如果插件下載速度過慢,可以直接依賴插件 jar文件,將插件下載到項目根目錄(如/plugins),然后在項目build.gradle中添加插件依賴:
// 項目級build.gradle dependencies {classpath 'com.android.tools.build:gradle:3.5.3'classpath fileTree(dir:'plugins', include:['*.jar']) }- 2、應用插件 —— 在App Module的build.gradle中應用插件:
- 3、依賴AspectJ框架 —— 在包含AspectJ代碼的Module的build.gradle文件中添加依賴:
步驟2:實現判斷快速點擊的工具類
- 我們先實現一個判斷View是否快速點擊的工具類;
- 實現原理是使用View的tag屬性存儲最近一次的點擊時間,每次點擊時判斷當前時間距離存儲的時間是否已經經過了足夠長的時間;
- 為了避免調用View#setTag(int key,Object tag)時傳入的key與其他地方傳入的key沖突而造成覆蓋,務必使用在資源文件中定義的 id,資源文件中的 id 能夠有效保證全局唯一性,具體如下:
步驟3:定義Aspect切面
使用@Aspect注解定義一個切面,使用該注解修飾的類會被AspectJ編譯器識別為切面類:
@Aspect public class FastClickCheckerAspect {// 隨后填充 }步驟4:定義PointCut切入點
使用@Pointcut注解定義一個切入點,編譯期AspectJ編譯器將搜索所有匹配的JoinPoint,執行織入:
@Aspect public class FastClickAspect {// 定義一個切入點:View.OnClickListener#onClick()方法@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")public void methodViewOnClick() {}// 隨后填充 Advice }步驟5:定義Advice增強
增強的方式有很多種,在這里我們使用@Around注解定義環繞增強,它將包裝PointCut,在PointCut前后增加橫切邏輯,具體如下:
@Aspect public class FastClickAspect {// 定義切入點:View.OnClickListener#onClick()方法@Pointcut("execution(void android.view.View.OnClickListener.onClick(..))")public void methodViewOnClick() {}// 定義環繞增強,包裝methodViewOnClick()切入點@Around("methodViewOnClick()")public void aroundViewOnClick(ProceedingJoinPoint joinPoint) throws Throwable {// 取出目標對象View target = (View) joinPoint.getArgs()[0];// 根據點擊間隔是否超過2000,判斷是否為快速點擊if (!FastClickCheckUtil.isFastClick(target, 2000)) {joinPoint.proceed();}} }步驟6:實現View.OnClickListener
在這一步我們為View設置OnClickListener,可以看到我們并沒有添加限制快速點擊的相關代碼,增強的邏輯對原有邏輯沒有侵入,具體代碼如下:
// 源碼: 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編譯器執行織入后的.class文件。還不了解如何查找編譯后的.class文件,請務必查看文章:《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;// 重構JoinPoint,執行環繞增強,也執行@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);}// 原來在View.OnClickListener#onClick()中的代碼,相當于核心業務邏輯private static final void onClick_aroundBody0(null ajc$this, View v, JoinPoint param1JoinPoint) {Log.i("AspectJ", "click");}// @Around方法中的代碼,即源碼中的aroundViewOnClick(),相當于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)) {// 非快速點擊,執行點擊邏輯ProceedingJoinPoint proceedingJoinPoint = joinPoint;onClick_aroundBody0(ajc$this, v, (JoinPoint)proceedingJoinPoint);null;} }});} }小結
到這里,我們就講解完使用AspectJ框架限制按鈕快速點擊的詳細,總結如下: - 使用@Aspect注解描述一個切面,使用該注解修飾的類會被AspectJ編譯器識別為切面類; - 使用@Pointcut注解定義一個切入點,編譯期AspectJ編譯器將搜索所有匹配的JoinPoint,執行織入; - 使用@Around注解定義一個增強,增強會被織入匹配的JoinPoint
4. 演進
現在,我們回歸文章開頭定義的需求,總共有4點。其中前兩點使用目前的方案中已經能夠實現,現在我們關注后面兩點,即允許定制時間間隔與覆蓋盡可能多的點擊場景。
- 需求回歸 示意圖:
4.1 定制時間間隔
在實際項目不同場景中的按鈕,往往需要限制不同的點擊時間間隔,因此我們需要有一種簡便的方式用于定制不同場景的時間間隔,或者對于一些不需要限制快速點擊的地方,有辦法跳過快速點擊判斷,具體方法如下: - 定義注解
/*** 在需要定制時間間隔地方添加@FastClick注解*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface FastClick {long interval() default FastClickAspect.FAST_CLICK_INTERVAL_GLOBAL; }- 修改切面類的Advice
- 使用注解
4.2 完整場景覆蓋
ButterKnife @OnClick android:onClick OK RecyclerView / ListView Java Lambda NO Kotlin Lambda OK DataBinding OK
Editting...
推薦閱讀
密碼學 | Base64 是加密算法嗎??juejin.im算法面試題 | 回溯算法解題框架?juejin.im算法面試題 | 鏈表問題總結?juejin.imJava | 帶你理解 ServiceLoader 的原理與設計思想?juejin.im計算機網絡 | 圖解 DNS & HTTPDNS 原理?juejin.imAndroid | 說說從 android:text 到 TextView 的過程?juejin.imAndroid | 面試必問的 Handler,你確定不看看??www.jianshu.comAndroid | 帶你探究 LayoutInflater 布局解析原理?juejin.imAndroid | View & Fragment & Window 的 getContext() 一定返回 Activity 嗎??juejin.im感謝喜歡!你的點贊是對我最大的鼓勵!歡迎關注彭旭銳的GitHub!
總結
以上是生活随笔為你收集整理的限制按钮点击_Android | 使用 AspectJ 限制按钮快速点击的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Pycharm 输出中文或打印中文乱码现
- 下一篇: 从一个Android码农视角回顾2018