请点赞关注,你的支持对我意义重大。
🔥 Hi,我是小彭。本文已收录到 GitHub · AndroidFamily 中。这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] 带你建立核心竞争力。
前言
- 在
Android
开发中,限制按钮快速点击(按钮防抖)是一个常见的需求; - 在这篇文章里,我将介绍一种使用
AspectJ
的方法,基于注解处理器 & 运行时注解反射的原理。如果能帮上忙,请务必点赞加关注,这真的对我非常重要。
系列文章
延伸文章
- 关于 反射,请阅读:《Java | 反射:在运行时访问类型信息(含 Kotlin)》
- 关于 注解,请阅读:《Java | 这是一篇全面的注解使用攻略(含 Kotlin)》
- 关于 注解处理器(APT),请阅读:《Java | 注解处理器(APT)原理解析 & 实践》
目录
- 定义需求
在开始讲解之前,我们先 定义需求,具体描述如下:
- 限制快速点击需求 示意图:
- 常规处理方法
目前比较常见的限制快速点击的处理方法有以下两种,具体如下:
2.1 封装代理类
封装一个代理类
处理点击事件,代理类通过判断点击间隔决定是否拦截点击事件,具体代码如下:
1 | csharp复制代码// 代理类 |
在需要限制快速点击的地方使用该代理类,具体如下:
1 | less复制代码tv.setOnClickListener(new FastClickListener() { |
2.2 RxAndroid 过滤表达式
使用RxJava
的过滤表达式throttleFirst
也可以限制快速点击,具体如下:
1 | java复制代码RxView.clicks(view) |
2.3 小结
代理类
和RxAndroid过滤表达式
这两种处理方法都存在两个缺点:
- 1. 侵入核心业务逻辑,需要将代码替换到需要限制点击的地方;
- 2. 修改工作量大,每一个增加限制点击的地方都要修改代码。
我们需要一种方案能够规避这两个缺点 —— AspectJ
。 AspectJ
是一个流行的Java
AOP(aspect-oriented programming)
编程扩展框架,若还不了解,请务必查看文章:《Android | 一文带你全面了解 AspectJ 框架》
- 详细步骤
在下面的内容里,我们将使用AspectJ
框架,把限制快速点击的逻辑作为核心关注点
从业务逻辑中抽离出来,单独维护。具体步骤如下:
步骤1:添加AspectJ
依赖
- 依赖沪江的
AspectJX
Gradle插件 —— 在项目build.gradle
中添加插件依赖:
- 依赖沪江的
1 | arduino复制代码// 项目级build.gradle |
如果插件下载速度过慢,可以直接依赖插件 jar文件,将插件下载到项目根目录(如/plugins),然后在项目build.gradle
中添加插件依赖:
1 | php复制代码// 项目级build.gradle |
- 应用插件 —— 在
App Module
的build.gradle
中应用插件:
- 应用插件 —— 在
1 | arduino复制代码// App Module的build.gradle |
- 依赖AspectJ框架 —— 在包含
AspectJ
代码的Module
的build.gradle
文件中添加依赖:
- 依赖AspectJ框架 —— 在包含
1 | arduino复制代码// Module级build.gradle |
步骤2:实现判断快速点击的工具类
- 我们先实现一个判断
View
是否快速点击的工具类; - 实现原理是使用
View
的tag
属性存储最近一次的点击时间,每次点击时判断当前时间距离存储的时间是否已经经过了足够长的时间; - 为了避免调用
View#setTag(int key,Object tag)
时传入的key
与其他地方传入的key
冲突而造成覆盖,务必使用在资源文件中定义的 id,资源文件中的 id 能够有效保证全局唯一性,具体如下:
1 | xml复制代码// ids.xml |
1 | java复制代码public class FastClickCheckUtil { |
步骤3:定义Aspect
切面
使用@Aspect注解
定义一个切面
,使用该注解修饰的类会被AspectJ编译器
识别为切面类:
1 | kotlin复制代码@Aspect |
步骤4:定义PointCut
切入点
使用@Pointcut注解
定义一个切入点
,编译期AspectJ编译器
将搜索所有匹配的JoinPoint
,执行织入:
1 | java复制代码@Aspect |
步骤5:定义Advice
增强
增强的方式有很多种,在这里我们使用@Around注解
定义环绕增强
,它将包装PointCut
,在PointCut
前后增加横切逻辑,具体如下:
1 | less复制代码@Aspect |
步骤6:实现View.OnClickListener
在这一步我们为View
设置OnClickListener
,可以看到我们并没有添加限制快速点击的相关代码,增强的逻辑对原有逻辑没有侵入,具体代码如下:
1 | scala复制代码// 源码: |
编译代码,随后反编译AspectJ编译器
执行织入后的.class文件
。还不了解如何查找编译后的.class文件
,请务必查看文章:《Android | 一文带你全面了解 AspectJ 框架》
1 | java复制代码public class MainActivity extends AppCompatActivity { |
小结
到这里,我们就讲解完使用AspectJ框架
限制按钮快速点击的详细,总结如下:
- 使用
@Aspect注解
描述一个切面
,使用该注解修饰的类会被AspectJ编译器
识别为切面类; - 使用
@Pointcut注解
定义一个切入点
,编译期AspectJ编译器
将搜索所有匹配的JoinPoint
,执行织入; - 使用
@Around注解
定义一个增强
,增强会被织入匹配的JoinPoint
- 演进
现在,我们回归文章开头定义的需求,总共有4点。其中前两点使用目前的方案中已经能够实现,现在我们关注后面两点,即允许定制时间间隔与覆盖尽可能多的点击场景。
- 需求回归 示意图:
4.1 定制时间间隔
在实际项目不同场景中的按钮,往往需要限制不同的点击时间间隔,因此我们需要有一种简便的方式用于定制不同场景的时间间隔,或者对于一些不需要限制快速点击的地方,有办法跳过快速点击判断,具体方法如下:
- 定义注解
1 | less复制代码/** |
- 修改切面类的
Advice
1 | java复制代码@Aspect |
- 使用注解
1 | less复制代码findViewById(R.id.text).setOnClickListener(new View.OnClickListener() { |
4.2 完整场景覆盖
ButterKnife @OnClick
android:onClick OK
RecyclerView / ListView
Java Lambda NO
Kotlin Lambda OK
DataBinding OK
Editting…
我是小彭,带你构建 Android 知识体系。技术和职场问题,请关注公众号 [彭旭锐]私信我提问。
本文转载自: 掘金