什么是防抖动?举一个典型场景:
笔者处理一个bug,【留言板】留言列表点击编辑,多次点击删除键,提示“留言发送失败” ,问题表现为点击删除按钮多次,发出了同一个请求多次,服务端无法响应返回了失败的响应码。
1 | @Override |
测试人员频繁点击一个Delete按钮,多次发出了同一份Request,该Request中携带了相同的请求体,服务端只能处理一份,先到达的Request将被服务端处理,后到达的Request将被服务端标记为处理失败,客户端会依次收到处理成功、处理失败、处理失败……
以上就是一个典型的点击事件频繁创建的问题,为了防止事件的频繁创建、响应、发出Request、让服务端处理,我们一般会做防抖动——即将点击事件过滤调。
防抖动方案按事件的产生顺序和处理顺序的方向来划分,可分为“先到达先处理型”和“后到达后处理型”
本文包括以下几种
- 传统计算时间间隔
- RxJava
- RxBinding
- 同一任务执行最后一次
前三个是先到达先处理型,第四个是厚道的后处理型
传统计算时间间隔#
第一次触发事件的时候记录一个时间戳,下一次触发事件时,再记录一个时间戳,在指定的时间间隔内,返回false,不去执行后续的业务逻辑
1 | public class MBUtils { |
将文章开头提到的错误代码改为:
1 | @Override |
RxJava#
这种方案的效果是,当一个动作连续触发,则只执行第一次。主要利用了Rxjava#throttleFirst方法的特性,在指定时间间隔内,Observer不会收到subscribe发来的消息,达到防抖动的效果
- ObservableOnSubscribe用于观察View的Click事件,在接收到系统发来的click消息后,通过onNext传递给它的观察者
- Observer用于接收消息,一旦Rxjava底层发来了消息,将在onNext处理它,并通过myClickListener返回给上层,进行下一步的业务处理
1 | public class MBUtils { |
将文章开头提到的错误代码改为:
1 | MBUtils.throttleFirstProcess(1, TimeUnit.SECONDS, childView, new MBUtils.ThrottleClickListener() { |
RxBinding#
RxBinding是一款非常强大组件库,用于将特定的UI组件与事件绑定起来,如将一个Button与Button的点击事件绑定。
以下是针对本文提到到频繁点击删除按钮的过滤用法,通过RxView对象,将Button对象与click事件绑定,用户点击Button后,会执行subscribe的Action#Call回调
1 | Button btn = helper.itemView.findViewById(R.id.iv_delete); |
RxView支持哪些事件?
clicks
longClicks
draws
drag
layoutChange
scrollChange
setVisibility
setClickable
attaches
detaches
focusChanges
globalLayouts
hovers
touches
集成方式:
1 | implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0' # 按需使用 implementation 'com.jakewharton.rxbinding4:rxbinding-core:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-appcompat:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-drawerlayout:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-leanback:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-recyclerview:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-slidingpanelayout:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-swiperefreshlayout:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-viewpager:4.0.0' implementation 'com.jakewharton.rxbinding4:rxbinding-viewpager2:4.0.0' # 按需使用 implementation 'com.jakewharton.rxbinding4:rxbinding-material:4.0.0' |
同一任务执行最后一次#
这种方案简单的说,当一个动作连续触发,则只执行最后一次。
查看代码,只有一个方法throttleFirstProcess方法会接收Runnnable对象作为value,以任务名作为key存储在一个ConcurrentHashMap里,每个Runnable的特点是将被延时1秒执行
为了实现只执行最后一次的效果,我们主要利用了ConcurrentHashMap对象put方法的特性,
举个例子,第一次执行Delete任务,ConcurrentHashMap会记下一个key为Delete,value为Rannable的任务;
指定时间间隔内,若第二次触发Delete任务,则ConcurrentHashMap会进行查找,一旦发现存在名为Delete的任务,我们将取出,并cancel掉第一次存入的Delete任务,这样保证了第一笔Delete任务在被执行之前取消掉。最终的效果就是只执行了第二次存入的Delete任务。
1 | public class MBUtils { |
使用起来也很简单:
将文章前面提到的错误代码改为:
1 | @Override |
1秒内点击多次,会创建n个同名为删除留言的Runnable,只有最后一个存入的Runnable才会被执行。
总结#
优点 | 缺点 | 适用场景 | |
---|---|---|---|
传统计算时间间隔 | - | - | - |
RxJava | 事件异常捕获 | 需要创建观察者和被观察者 | - |
RxBinding | 事件异常捕获支持大部分系统UI组件 | 需要及时释放观察者,否则有内存泄漏 | - |
同一任务执行最后一次 | - | 任务完成的时间会被延后 | 对于任务完成时效性不敏感的:删除操作,如删除同一笔留言、同一笔订单、创建虚拟用户创建操作,如添加同一个食材编辑在线文档,只在3秒内无新的编辑时,自动上传文档内容 |