先看RxJava在GitHub上的解释:(截止目前为止:最新版本2.2.8)
RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
翻译过来为:
RxJava是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库。
个人使用总结:
RxJava 是以观察者模式为核心,可以通过强大的操作符,对事件中的消息进行加工包装,并且可以轻松实现线程调度的一个框架。
关键点:异步。
关键点:链式调用、简洁
异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写也难被读懂。 Android 创造的 AsyncTask
和Handler
,其实都是为了让异步代码更加简洁。RxJava 的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。
RxJava是利用观察者模式来进行一系列操作,所以需要了解一下其他涉及到的基本概念,方便上车。
Observable :被观察者,用来生产发送事件;
Observer:观察者,接收被观察者传来的事件;
Event:包装事件发送中的消息,在事件的传递过程中,可以通过操作符对事件进行各种加工(转换,过滤,组合……);
Subscribe:被观察者和观察者通过订阅产生关系后,才具备事件发送和接收能力;
**Subscriber:**也是一种观察者,在2.0中 它与Observer没什么实质的区别,不同的是 Subscriber要与Flowable(也是一种被观察者)联合使用,该部分内容是2.0新增的。Obsesrver用于订阅Observable,而Subscriber用于订阅Flowable.
RxJava事件回调方法:
OnNext()
、onCompleted()
、onError()
onCompleted()
: 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext()
发出时,需要触发 onCompleted()
方法作为标志。
onError()
: 事件队列异常。在事件处理过程中出异常时,onError()
会被触发,同时队列自动终止,不允许再有事件发出。
在一个正确运行的事件序列中, onCompleted()
和 onError()
有且只有一个,并且是事件序列中的最后一个。需要注意的是,onCompleted()
和 onError()
二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。
RxJava 的观察者模式大致如下图:
RxJava的强大性就来自于它所定义的操作符。主要分类:
RxJava 的操作符 | 说明 | 例如 |
---|---|---|
创建操作 | 用于创建Observable的操作符 | create、defer、from、just、start、repeat、 range |
变换操作 | 用于对Observable发射的数据进行变换 | buffer、window、map、flatMap、groupBy、scan |
过滤操作 | 用于从Observable发射的数据中进行选择 | debounce、distinct、filter、sample、skip、take |
组合操作 | 用于将多个Observable组合成一个单一的Observable | and、startwith、join、merge、switch、zip |
异常处理 | 用于从错误通知中恢复 | catch、retry |
辅助操作 | 用于处理Observable的操作符 | delay、do、observeOn、subscribeOn、subscribe |
条件和布尔操作 | all、amb、contains、skipUntil、takeUntil | |
算法和聚合操作 | average、concat、count、max、min、sum、reduce | |
异步操作 | start、toAsync、startFuture、FromAction、FromCallable、runAsync | |
连接操作 | connect、publish、refcount、replay | |
转换操作 | toFuture、toList、toIterable、toMap、toMultiMap | |
阻塞操作 | forEach、first、last、mostRecent、next、single | |
字符串操作 | byLine、decode、encode、from、join、split、stringConcat |
常用分类:
常用操作符 | 说明 |
---|---|
interval | 延时几秒,每隔几秒开始执行 |
take | 超过多少秒停止执行 |
map | 类型转换 |
observeOn | 在主线程运行 |
doOnSubscribe | 在执行的过程中 |
subscribe | 订阅 |
RxJava 线程调度器
调度器 Scheduler 用于控制操作符和被观察者事件所执行的线程,不同的调度器对应不同的线程。RxJava提供了5种调度器:
RxJava 线程调度器 | 说明 |
---|---|
Schedulers.immediate() | 默认线程,允许立即在当前线程执行所指定的工作。 |
Schedulers.newThread() | 新建线程,总是启用新线程,并在新线程执行操作。 |
Schedulers.io() | 适用于I/O操作,根据需要增长或缩减来自适应的线程池。多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。 |
Schedulers.computation() | 适用于计算工作(CPU 密集型计算),即不会被 I/O 等操作限制性能的操作。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。 |
Schedulers.trampoline() | 当我们想在当前线程执行一个任务时,并不是立即,我们可以用.trampoline()将它入队。这个调度器将会处理它的队列并且按序运行队列中每一个任务。 |
AndroidSchedulers.mainThread() | RxAndroid 提供的,它指定的操作将在 Android 主线程运行。 |
模拟发送验证码倒计时:
public void onCodeClick() {
final long count = 60; // 设置60秒
Observable.interval(0, 1, TimeUnit.SECONDS)
.take(count + 1)
.map(new Function<Long, Long>() {
@Override
public Long apply(@NonNull Long aLong) throws Exception {
return count - aLong; // 由于是倒计时,需要将倒计时的数字反过来
}
})
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
button.setEnabled(false);
button.setTextColor(Color.GRAY);
}
})
.subscribe(new Observer<Long>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Long aLong) {
button.setText(aLong + "秒后重发");
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
button.setEnabled(true);
button.setTextColor(Color.RED);
button.setText("发送验证码");
}
});
}
再举一例,项目中使用实例:轮询,每隔1分钟 更新停车费和停车时长:
//定时刷新时间和费用
private var intervalRefreshDisposable: Disposable? = null
//启动轮询
override fun onResume() {
super.onResume()
intervalRefreshDisposable = refreshTimeAndFeeInterval()
}
private fun refreshTimeAndFeeInterval(): Disposable? {
val disposable = Observable.interval(0, 1, TimeUnit.MINUTES)
.applyScheduler()
.subscribe({
//do something 如刷新界面
}, {
LogUtil.printStackTrace(it)
})
//绑定生命周期
disposable.attachToLifecycle(viewLifecycleOwner)
return disposable
}
//界面不可见时 解除订阅
override fun OnDestory() {
super.OnDestory()
intervalRefreshDisposable?.safeDisposable()
}
//用到的RxJava扩展工具类
/**
* 安全解除订阅
*/
fun Disposable.safeDisposable() {
if (!this.isDisposed) {
this.dispose()
}
}
/**
* Observable 应用线程控制
* @param subscribeScheduler 订阅时的线程,默认是 IO 线程
* @param observeScheduler 观察时的线程,默认是主线程
*/
fun <T> Observable<T>.applyScheduler(
subscribeScheduler: Scheduler = Schedulers.io(),
observeScheduler: Scheduler = AndroidSchedulers.mainThread()
): Observable<T> {
return this.subscribeOn(subscribeScheduler)
.observeOn(observeScheduler)
.unsubscribeOn(subscribeScheduler)
}
RxJava 系列框架
RxJava 框架 | 说明 | 开源地址 |
---|---|---|
RxAndroid | 针对 Android 平台的扩展框架,方便 RxJava 用于 Android 开发,目前 RxAndroid 主要的功能是对 Android 主线程的调度 AndroidSchedulers.mainThread()。 | https://github.com/ReactiveX/RxAndroid |
DataBinding | DataBinding 是基于MVVM思想实现数据和UI绑定的的框架,支持双向绑定。 | DataBinding 是一个support库,最低支持到Android 2.1 |
RxBinding | 基于 RxJava 的用于绑定 Android UI 控件的框架,它可以异步获取并处理控件的各类事件(例如点击事件、文字变化、选中状态) | https://github.com/JakeWharton/RxBinding |
Retrofit | 网络请求框架,Retrofit 结合 RxJava 简化请求流程。 | https://github.com/square/retrofit |
RxPermissions | 动态权限管理框架,动态权限内容可参考Android 6.0+ 运行时权限处理。 | https://github.com/tbruyelle/RxPermissions |
RxLifecycle | 生命周期绑定,提供了基于 Activity 和 Fragment 生命周期事件的自动完成队列,用于避免不完整回调导致的内存泄漏。 | https://github.com/trello/RxLifecycle |
RxBus | 是一种基于RxJava实现事件总线的一种思想。可以替代EventBus/Otto,因为他们都依赖于观察者模式。 | https://github.com/AndroidKnife/RxBus |
Retrofit 除了提供了传统的 Callback
形式的 API,还有 RxJava 版本的 Observable
形式 API。下面我用对比的方式来介绍 Retrofit 的 RxJava 版 API 和传统版本的区别。
Callback
形式和 Observable
形式长得不太一样,但本质都差不多。RXJava在可读上要好得多,比如在网络嵌套上可以flatMap()
实现链式调用,代码上更优雅。
测试接口:
https://api.douban.com/v2/movie/top250?start=0&count=10
首先我们要根据返回的结果封装一个Entity,暂命名为MovieEntity,代码就不贴了。
接下来我们要创建一个接口取名为MovieService,代码如下:
(1)只用Retrofit
public interface MovieService {
@GET("top250")
Call<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}
在界面中调用 获取数据的接口getMovie方法:
//进行网络请求
private void getMovie(){
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
MovieService movieService = retrofit.create(MovieService.class);
Call<MovieEntity> call = movieService.getTopMovie(0, 10);
call.enqueue(new Callback<MovieEntity>() {
@Override
public void onResponse(Call<MovieEntity> call, Response<MovieEntity> response) {
resultTV.setText(response.body().toString());
}
@Override
public void onFailure(Call<MovieEntity> call, Throwable t) {
resultTV.setText(t.getMessage());
}
});
}
(2)引入RxJava后
现在定义接口时不再是Call,而是Observable
public interface MovieService {
@GET("top250")
Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}
getMovie方法改为:
//进行网络请求
private void getMovie(){
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
MovieService movieService = retrofit.create(MovieService.class);
movieService.getTopMovie(0, 10)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MovieEntity>() {
@Override
public void onCompleted() {
Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
resultTV.setText(e.getMessage());
}
@Override
public void onNext(MovieEntity movieEntity) {
resultTV.setText(movieEntity.toString());
}
});
}
以上实现了RxJava如何与Retrofit结合,当然还不完善,还有很多(如以下4点)需要封装,此处不再详细介绍,更多请查看RxJava 与 Retrofit 结合的最佳实践
- 相同格式的Http请求数据该如何封装
- 相同格式的Http请求数据统一进行预处理
- 如何取消一个Http请求 – 观察者之间的对决,Oberver VS Subscriber
- 一个需要ProgressDialog的Subscriber该有的样子
RxBinding 是 Jake Wharton 的一个开源库,它提供了一套在 Android 平台上的基于 RxJava 的 Binding API。所谓 Binding,就是类似设置
OnClickListener
、设置TextWatcher
这样的注册绑定对象的 API。
(1)Button 防抖处理:
throttleFirst()
,用于去抖动,也就是消除手抖导致的快速连环点击:
RxView.clickEvents(button)
.throttleFirst(500, TimeUnit.MILLISECONDS) //设置防抖间隔为 500ms
.subscribe(clickAction);
在项目中的实际运用,项目中为防止重复点击,在凡是有点击事件时,都用View的扩展类onClick来实现(Kotlin)
// 点击时间间隔
private const val TIME_INTERVAL = 1000L
/**
* View点击事件,限定时间之内只取第一个点击事件,防止重复点击
*/
fun View.onClick(time: Long = TIME_INTERVAL, click: (View) -> Unit) {
this.clicks()
.throttleFirst(time, TimeUnit.MILLISECONDS)
.subscribe({
click(this)
}, {
LogUtil.printStackTrace(it)
})
}
//调用
ibBack.onClick {
//do something
}
(2)按钮的长按时间监听
//监听长按时间
RxView.longClicks( button)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Toast.makeText(MainActivity.this, "long click !!", Toast.LENGTH_SHORT).show();
}
}) ;
//监听某按钮多次点击,比如双击
Observable<Void> observable = RxView.clicks(doubleClickBtn).share();
observable.buffer(observable.debounce(400, TimeUnit.MILLISECONDS))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Void>>() {
@Override
public void call(List<Void> voids) {
if (voids.size() >= 2) {
//double click detected
showToast(" 400毫秒内进行了" + voids.size() + "点次击");
}
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Android是不能多次监听同一个点击事件。但利用RxJava
的操作符,例如publish
, share
或replay
可以实现。而RxBinding
恰好支持对点击事件的多次监听。
(3)用户登录界面,勾选同意隐私协议,登录按钮就变高亮
//原始方式
usernameEt.addTextChangedListener(....);
pwdEt.addTextChangedListener(....;
protocolCb.setOnCheckedChangeListener(....);
//现在
Observable<CharSequence> usernameOb = RxTextView.textChanges(usernameEt);
Observable<CharSequence> pwdOb = RxTextView.textChanges(pwdEt);
Observable<Boolean> protocolOb = RxCompoundButton.checkedChanges(protocolCb);
Observable.combineLatest(usernameOb, pwdOb, protocolOb, new Func3<CharSequence, CharSequence, Boolean, Boolean>() {
@Override
public Boolean call(CharSequence username, CharSequence pwd, Boolean isChecked) {
return !TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd) && isChecked;
}
}).subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean aBoolean) {
RxView.enabled(highLightkBtn).call(aBoolean);
}
});
操作符combineLatest
作用就是当多个Observables
中的任何一个发射了一个数据时,通过一定的方法去组合多个Observables
的最新数据,然后发射最终结果。
在本例中两个输入框只要内容发生变化,就会发送Observable
然后我们在Fun2中利用我们的验证方法去判断输入框中最新的内容,最终返回是否可点击的结果。
(4)输入文本框监听
//原始方式
final EditText name = (EditText) v.findViewById(R.id.name);
name.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// do some work here with the updated text
}
@Override
public void afterTextChanged(Editable s) {
}
});
//现在
final EditText name = (EditText) v.findViewById(R.id.name);
Subscription editTextSub =RxTextView.textChanges(name)
.subscribe(new Action1<String>() {
@Override
public void call(String value) {
// do some work with the updated text
}
});
// Make sure to unsubscribe the subscription
(5)搜索 :debounce()在一定的时间内没有操作就会发送事件。
RxTextView.textChanges(searchEt)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<CharSequence, String>() {
@Override
public String call(CharSequence charSequence) {
String searchKey = charSequence.toString();
if (TextUtils.isEmpty(searchKey)) {
showToast("搜索关键字不能为null");
}
else {
showToast("0.5秒内没有操作,关键字:" + searchKey);
}
return searchKey;
}
})
.observeOn(Schedulers.io())
.map(new Func1<String, List<String>>() {
@Override
public List<String> call(String searchKey) {
return search(searchKey);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<String>>() {
@Override
public void call(List<String> strings) {
}
});
前面举的 Retrofit
和 RxBinding
的例子,是两个可以提供现成的 Observable
的库。而如果你有某些异步操作无法用这些库来自动生成 Observable
,也完全可以自己写。例如数据库的读写、大图片的载入、文件压缩/解压等各种需要放在后台工作的耗时操作,都可以用 RxJava 来实现。
RxBus 名字看起来像一个库,但它并不是一个库,而是一种模式,它的思想是使用 RxJava 来实现了 EventBus ,而让你不再需要使用 Otto
或者 GreenRobot 的 EventBus
此文作为RxJava中的入门介绍,在学习的总结的同时,对参考资料中的知识加以提炼,并结合自身项目中运用到的实例进行了总结。如有不足之处,欢迎指出~
参考资料:
1.Rxjava
2.RxKotlin
3.给 Android 开发者的 RxJava 详解(推荐)
4.RxBus
5.Android 响应式编程 RxJava2 完全解析
6.RxJava 与 Retrofit 结合的最佳实践
7.这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)
8.神兵利器–RxBinding,用的就是你