RxJava 系列(十):自定义防抖操作符
本文概述:
- 此为RxJava 系列第十篇文章,文章以自定义RxJava 操作符为主题,实现了为按钮控件添加的防抖操作符;文章详细展示了代码编写过程,具有一定借鉴意义;
业务需求
- 为按钮控件编写一个防抖操作符
具体编辑过程
工程结构展示:
RxView:
-
暴露给用户使用:静态
public static Observable<Object> clicks(View view) { return new ViewClickObservable(view); }-
细节一:为什么要返回 Observable (观察系统操作符的实现)
-
细节二:为什么不写成泛型T
- 为了简化,不用让用户去指定泛型
-
细节三:返回的什么东西(自定义的Observable 的子类)
-
-
完整代码:
ViewClickObservable
搭架子:ViewClickObservable
-
类签名:指定继承关系
public class ViewClickObservable extends Observable<Object> {-
细节一:Observable 子类在源码中的类签名是怎么干的?
- 传递了一个泛型给Observable,但在前面已经指定了泛型为Object
-
-
重写方法:subscribeActual
@Override protected void subscribeActual(Observer<? super Object> observer) {- 为什么要重写这个:根据源码执行流程,会调用到这个方法的
-
定义View:因为我们是对View操作(为按钮控件添加防抖操作符)
private final View view; -
定义控件的关联事件:最终是没有用到的
private static Object EVENT2; -
将View 作为参数传给ViewClickObservable 构造方法
public ViewClickObservable(View view) { this.view = view; EVENT2 = view; } -
定义本类中的包裹:是可以中断的
-
重写三个方法
- 监听、中断、检测中断
-
-
架子就搭好了
添加业务代码:ViewClickObservable
-
丰富包裹
- 传入View ,下一层的包裹,添加构造函数
- 定义原子变量:作为中断的标记信号
- 丰富监听函数:通过检测中断标志位,没有被中断则向下一层分发事件
-
丰富当中断时的操作:直接返回中断信号
-
丰富中断操作:提供给用户的,让事件流动中断掉
- 没有中断时,才能去取消事件流动
- 主线程中的中断操作:直接将监听器置空即可
-
子线程:使用Handler 切换到主线程后去中断(将监听器置空)
- 简单操作
- 借鉴源码方式进行线程切换:查看文末 重点探究一
-
丰富重写函数:subscribeActual
- 实例化包裹
- 当订阅后将包裹向下传递
- 关联控件
完整代码:ViewClickObservable
public class ViewClickObservable extends Observable<Object> {
private final View view;
// 事件 第一节课 防抖 事件 没用
private static final Object EVENT = new Object();
private static Object EVENT2;
public ViewClickObservable(View view) {
this.view = view;
EVENT2 = view;
}
@Override
protected void subscribeActual(Observer<? super Object> observer) {
// 实例化包裹
MyListener myListener = new MyListener(view, observer);
//当订阅后将包裹向下传递
observer.onSubscribe(myListener);
//关联控件
this.view.setOnClickListener(myListener);
}
// 我们的包裹
static final class MyListener implements View.OnClickListener, Disposable {
private final View view;
private Observer<Object> observer; // 存一份 下一层
// 原子性,同学们自行看看文章
// https://www.jianshu.com/p/8a44d4a819bc
// boolean == AtomicBoolean
private final AtomicBoolean isDisposable = new AtomicBoolean();
public MyListener(View view, Observer<Object> observer) {
this.view = view;
this.observer = observer;
}
@Override
public void onClick(View v) {
if (isDisposed() == false) {
observer.onNext(EVENT);
}
}
// 如果用调用了 中断
@Override
public void dispose() {
// 如果没有中断过,才有资格, 取消view.setOnClickListener(null);
if (isDisposable.compareAndSet(false, true)) {
// 主线程 很好的中断
if (Looper.myLooper() == Looper.getMainLooper()) {
view.setOnClickListener(null);
} else { // 此时为子线程,那么通过Handler到主线程后,才执行中断(将监听器置空)
new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
view.setOnClickListener(null);
}
};
// HandlerScheduler.scheduleDirect
AndroidSchedulers.mainThread().scheduleDirect(new Runnable() {
@Override
public void run() {
view.setOnClickListener(null);
}
});
}
}
}
@Override
public boolean isDisposed() {
return isDisposable.get();
}
}
}
重点探究一:源码中是怎么切换到主线程的?
-
借助了HandlerScheduler
-
查阅HandlerScheduler.scheduleDirect
- 接收了Runnable,并且是运行在主线程中的;结合此时的业务需求(需要从子线程切换到主线程后,执行事件中断操作(将监听器置空))
-
尝试一:直接使用HandlerScheduler.scheduleDirect ,将Runnable 丢进去就行了
- 报错了
- 因为HandlerScheduler 是一个final 类型的
-
尝试二:通过AndroidSchedulers.mainThread()
-
最终是会调用到HandlerScheduler.scheduleDirect (系列前部分文章已经分析过了)
-
将Runnable 传给它,就实现了线程的切换了
-
业务层代码:RxActivity
运行结果:
- 疯狂点击:2 秒中才响应一次