2017-06-21
从这一篇我们开始对 RxBinding 库的学习, 主要学习它对按钮的封装, 以及它内部的实现原理.
对于RxBinding 库的使用, 网上已经有许多优秀的教程了. 我简单地梳理了一下:
文章 | 介绍 |
---|---|
使用RxBinding响应控件的异步事件 | 以一个 Demo 示例来讲解, RxToolbar, RxSnackbar |
RxBinding 学习笔记 | 基本用法, 点击, ListView 点击事件, 结合使用操作符 |
RxBinding(JakeWharton/RxBinding) | 实例: 验证过滤, 流量控制 |
RxJava RxBinding基础 | 按钮点击, 按钮防抖, EditText 文本改变, CheckBox, RecyclerView |
一些RxBinding使用场景 | 按钮防抖, 多次监听, 倒计时, 表单验证 |
RxJava 和 RxAndroid 四(RxBinding的使用) | 按钮防抖, 按钮的长按时间监听, ListView, CheckBox |
我在 GitHub 上建立了一个项目 MaxieeRxLearning 作为对学习的总结.
本文中我会用 MaxieeRxLearning 中的代码来进行讲解, 因此建议先将这个项目 Clone 下来参照着学习.
RxBinding 按钮的演示代码位于 ButtonFragment
, 截图如下:
一共有 4 种常用情况, 下面依次说明.
代码:
RxView.clicks(mNormalClickButton).subscribe( Object -> mNormalClickTextView.setText( "Clicked at " + System.currentTimeMillis()));
每次点击按钮, 订阅者就会被触发, 执行操作. 在这里所执行的操作是更新 TextView.
代码:
RxView.longClicks(mLongClickButton).subscribe( object -> mLongClickTextView.setText( "Long click at " + System.currentTimeMillis()));
只有长点击才会响应, 普通点击不会触发.
代码:
RxView.clicks(mThrottleClickButton) .throttleFirst(5, TimeUnit.SECONDS) .subscribe( object -> mThrottleTextView.setText( "Clicked at " + System.currentTimeMillis()));
上面代码中防抖点击的功能时这样的:
注意, 防抖的功能是将 RxBinding 的 RxView.clicks()
和 RxJava 的 throttleFirst 操作符组合起来使用的. throttleFirst 的文档在这里.
这个 Demo 是有一个 CheckBox 它能控制 Button 的 Enable 属性, CheckBox 勾选 Button 的 enable 为 true, 按钮可点击, 反之亦然. 代码:
RxCompoundButton.checkedChanges(mCheckEnable).subscribe( RxView.enabled(mCheckButton)); RxView.clicks(mCheckButton).subscribe( object -> mCheckTextView.setText( "Clicked at " + System.currentTimeMillis()));
其中:
RxCompoundButton.checkedChanges
观察的是 CheckBox 的勾选状态, 在观察者中调用 RxView.enabled
改变按钮的 Enable 属性.enabled
传入参数呢? enabled
类型是 Consumer<? super Boolean>
, checkedChanges
类型是 InitialValueObservable<Boolean>
, 都被封装了.这个 Demo 一般应用于点击 "已阅读协议" 然后才允许用户注册的页面.
前面介绍了很多实用的例子, 它们的底层是怎么实现的呢? 我们以 RxView.clicks
为例来看一下.
给按钮设置回调的方法是:
view.setOnClickListener(OnClickListener listener);
我们要做的是, 封装一个 Observable, 当点击回调触发时, 向 observer 发一个 onNext, 那怎么实现呢?
首先我们创建一个 Observable:
final class MaxieeViewClickObservable extends Observable<Object> { private final View mView; ViewClickObservable(View view) { mView = view; } @Override protected void subscribeActual(Observer<? super Object> observer) { ... } }
其中, subscribeActual
是干什么的呢? 它是 RxJava 内部的一个核心要点.
我们知道, 被观察者被观察者订阅, 他俩就建立起关系来了, 当被观察者发出数据就能触发观察者.
现在的问题是, 这个触发是怎么进行的? 就是通过 Observable 的 subscribeActual
实现的.
我们补全上面空着的 subscribeActual
:
private static class MaxieeViewClickObservable extends Observable<Object> { private View mView; MaxieeViewClickObservable(View view) { mView = view; } @Override protected void subscribeActual(Observer<? super Object> observer) { mView.setOnClickListener(v -> observer.onNext(null)); } }
其中, 建立关系的方式很简单, 创建一个点击回调绑定给 view, 回调的内容是调用 observer 的 onNext.
RxJava 中被观察者和观察者的触发就是通过这种机制建立起来的.
RxBinding 提供了一个工具方法, 也就是 RxView.clicks()
做了一个封装, 它的实现是这样:
public static Observable<Object> clicks(View view) { return new MaxieeViewClickObservable(view); }
这样我们的 clicks 跟 RxView.clicks()
就能一样使用了:
clicks(mHomeBrewButton).subscribe( object -> mHomeBrewTextView.setText( "Clicked at " + System.currentTimeMillis()));
上面这个代码展示了 RxView.clicks 的最核心的实现原理.
在理解了核心原理之后, 这段代码还有不完善的地方, 许多地方还有问题, 我们将在下一节中来完善它们.
上面我们实现的代码, 第一个问题是在解绑时会有问题.
我们用以下代码来解绑:
Disposable d = clicks(mHomeBrewButton).subscribe( object -> mHomeBrewTextView.setText( "Clicked at " + System.currentTimeMillis())); d.dispose();
这样再次运行, 点击按钮确实不会触发 TextView 变化了. 表面上没问题, 实际上有问题:
dispose 之后, button 的 OnClickListener 实现中仍然持有者 observer 对象. 这回造成内存泄漏.
怎么解决呢? 在 subscribeActual
中调用 observer 的 onSubscribe 方法, 传入一个 Disposable, 在 observer dispose 的时候会调用这个 Disposable 的 onDispose, 在里面将点击回调清空.
其中 onSubscribe 是 RxJava 中观察者 dispose 取消订阅时, 被观察者进行资源释放的机制.
写成代码就是:
@Override protected void subscribeActual(Observer<? super Object> observer) { mView.setOnClickListener(v -> observer.onNext(null)); observer.onSubscribe(new Disposable() { private boolean mDisposed = false; @Override public void dispose() { Log.d("maxiee", "clear the click listener of button"); mView.setOnClickListener(null); mDisposed = true; } @Override public boolean isDisposed() { return mDisposed; } }); }
这样, 当观察者取消订阅时, 按钮的点击回调也就清空了, 也就是释放了资源.
有一点要注意的是, 我在上面的实现跟 RxBinding 的实现有所不同, 但是原理是一样的, 上面这样写比较易于理解.
RxBinding 的实现如下:
final class ViewClickObservable extends Observable<Object> { private final View view; ViewClickObservable(View view) { this.view = view; } @Override protected void subscribeActual(Observer<? super Object> observer) { if (!checkMainThread(observer)) { return; } Listener listener = new Listener(view, observer); observer.onSubscribe(listener); view.setOnClickListener(listener); } static final class Listener extends MainThreadDisposable implements OnClickListener { private final View view; private final Observer<? super Object> observer; Listener(View view, Observer<? super Object> observer) { this.view = view; this.observer = observer; } @Override public void onClick(View v) { if (!isDisposed()) { observer.onNext(Notification.INSTANCE); } } @Override protected void onDispose() { view.setOnClickListener(null); } } }
其中:
在本篇中, 我们首先学习了 RxBinding 按钮的常用使用场景. 在此基础之上, 我们深入 RxBinding 底层, 学习了它的实现原理. 在学习原理的过程中, 我们学习了 RxJava2 的绑定与解绑的底层实现机制.
原理学会之后, 会发现各种绑定都很简单, 因此在下一篇中我会加快一点速度, 罗列 RxBinding 控件的使用场景.