使用了 RxJava2 有一段时间了,深深感受到了其“牛逼”之处。下面,就从 RxJava2 的基础开始,一步步与大家分享一下这个强大的异步库的用法!RxJava 是 一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库,也就是用于实现异步操作的库。
1.RxJava2 基础
RxJava可以浓缩为异步两个字,其核心的东西不外乎两个, Observables(被观察者) 和 Observable(观察者)。Observables可以发出一系列的 事件(例如网络请求、复杂计算、数据库操作、文件读取等),事件执行结束后交给Observable 的回调处理。
1.RxJava2 的观察者模式
观察者模式是对象的行为模式,也叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
什么是观察者模式?举个栗子,Android中View的点击监听器的实现,View是被观察者,OnClickListener对象是观察者,Activity要如何知道View被点击了?那就是派一个OnClickListener对象,入驻View,与View达成一个订阅关系,一旦View被点击了,就通过OnClickListener对象的OnClick方法传达给Activity。采用观察者模式可以避免去轮询检查,节约有限的cpu资源。
RxJava 作为一个工具库,使用的便是通用形式的观察者模式:
普通事件:onNext(),相当于 onClick()、onEvent();特殊事件:onCompleted() 和 onError()
如图所示,RxJava 的基本概念分别为:Observable(被观察者,事件源),Observer(观察者,订阅者),subscribe (订阅)、事件;不同的是,RxJava 把多个事件看做一个队列,并对每个事件单独处理。在一个队列中 onCompleted() 和 onError(),只有一个会被调用。如果调用了 onCompleted() 就说明队列执行完毕,没有出现异常,否则调用 onError() 方法并终止队列。
2.RxJava2 响应式编程结构
什么是响应式编程?举个栗子,a = b + c; 这句代码将b+c的值赋给a,而之后如果b和c的值改变了不会影响到a,然而,对于响应式编程,之后b和c的值的改变也动态影响着a,意味着a会随着b和c的变化而变化。
响应式编程的组成为Observable/Operator/Subscriber,RxJava在响应式编程中的基本流程如下:
这个流程,可以简单的理解为:Observable -> Operator1 -> Operator2 -> Operator3 -> Subscriber
1. Observable发出一系列事件,他是事件的产生者; 2. Subscriber负责处理事件,他是事件的消费者; 3. Operator是对Observable发出的事件进行修改和变换; 4. 若事件从产生到消费不需要其他处理,则可以省略掉中间的Operator,从而流程变为Obsevable -> Subscriber; 5. Subscriber通常在主线程执行,所以原则上不要去处理太多的事务,而这些复杂的处理则交给Operator;
3.创建一个完整的 RxJava2 调用
首先需要添加 RxJava2 在 android 中的 Gradle 依赖:
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'compile "io.reactivex.rxjava2:rxjava:2.0.8"1212
RxJava2 可以通过下面这几种方法创建被观察者:
// 发送对应的方法Observable.create(new ObservableOnSubscribe<String>() { // 默认在主线程里执行该方法 @Override public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception { e.onNext("Hello"); e.onNext("World"); // 结束标识 e.onComplete(); } });// 发送多个数据Observable.just("Hello", "World");// 发送数组Observable.fromArray("Hello", "World");// 发送一个数据Observable.fromCallable(new Callable<String>() { @Override public String call() throws Exception { return "Hello"; } });
RxJava2 支持链式编程,下来我们创建被观察者,然后创建观察者并订阅:
// 创建被观察者Observable.just("Hello", "World")// 将被观察者切换到子线程.subscribeOn(Schedulers.io())// 将观察者切换到主线程.observeOn(AndroidSchedulers.mainThread())// 创建观察者并订阅.subscribe(new Observer<String>() { // Disposable 相当于RxJava1.x中的 Subscription,用于解除订阅 private Disposable disposable; @Override public void onSubscribe(Disposable d) { disposable = d; } @Override public void onNext(String s) { Log.i("JAVA", "被观察者向观察者发送的数据:" + s); if (s == "-1") { // "-1" 时为异常数据,解除订阅 disposable.dispose(); } } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
一旦 Observer 订阅了 Observable,Observable 就会调用 Observer 的 onNext()、onCompleted()、onError() 等方法。至此一个完整的 RxJava 调用就完成了。看一下输出的Log:
I/JAVA: 被观察者向观察者发送的数据:Hello I/JAVA: 被观察者向观察者发送的数据:World
若喜欢简洁、定制服务,那么可以实现的方法跟上面的实现方法是对应起来的,大家看参数就知道哪个对应哪个了,你可以通过new Consumer(不需要实现的方法你可以不写,看上去更简洁),Consumer就是消费者的意思,可以理解为消费了 onNext 等事件:
Observable.just("Hello", "World") .subscribe(new Consumer<String>() { @Override public void accept(@NonNull String s) throws Exception { Log.i("JAVA", "被观察者向观察者发送的数据:" + s); } }, new Consumer<Throwable>() { @Override public void accept(@NonNull Throwable throwable) throws Exception { } }, new Action() { @Override public void run() throws Exception { } }, new Consumer<Disposable>() { @Override public void accept(@NonNull Disposable disposable) throws Exception { } });
4.RxJava2 的操作符
RxJava中提供了大量不同种类,不同场景的Operators(操作符),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 | 订阅 |
5.RxJava2 线程调度器
调度器 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 主线程运行。 |
可以使用 subscribeOn() 和 ObserveOn() 操作符进行线程调度,让 Observable 在一个特定的调度器上执行。subscribeOn() 指定 subscribe() 所发生的线程,事件产生的线程。ObserveOn() 指定 Observer 所运行在的线程,事件消费的线程。
6.RxJava2 模拟发送验证码倒计时功能
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("发送验证码"); } }); }
2.RxJava2 系列框架
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 |
3.RxJava2 与 Retrofit 的使用
RxJava 与 Retrofit 的使用,更像我们的 AsyncTask,通过网络获取数据然后通过 Handler 更新UI。首先需要导入依赖:
compile 'com.squareup.retrofit2:retrofit:2.2.0'compile 'com.squareup.retrofit2:converter-gson:2.2.0'compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'123123
1.模拟用户登陆获取用户数据
1.Bean对象:
public class UserParam { private String param1; private String param2; public UserParam(String param1, String param2) { this.param1 = param1; this.param2 = param2; } // 省略了 getter setter}public class NetBean { private FormBean form; // 省略了 getter setter public static class FormBean { private String username; private String password; // 省略了 getter setter } }public class UserBean { private String username; private String password; public UserBean(String username, String password) { this.username = username; this.password = password; } // 省略了 getter setter}123456789101112131415161718192021222324252627123456789101112131415161718192021222324252627
2.ApiService,这里返回Observable对象,也就是我们RxJava的被观察者
public interface ApiService { @FormUrlEncoded @POST("/post") Observable<NetBean> getUserInfo(@Field("username")String username, @Field("password")String password); }
3.RxJava + Retrofit 的实现
// 构建RetrofitApiService apiService = new Retrofit.Builder() .baseUrl("http://httpbin.org/") .addConverterFactory(GsonConverterFactory.create()) // RxJava2与Gson混用 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2与Retrofit混用 .build() .create(ApiService.class);// 构建RxJavaUserParam param = new UserParam("zhangsan", "123");// 发送param参数Observable.just(param) // flatMap方法是用于数据格式转换的方法,参数一表示原数据, // 参数二表示转换的数据,那么就是通过发送网络参数,转换成网络返回的数据,调用Retrofit .flatMap(new Function<UserParam, ObservableSource<NetBean>>() { @Override public ObservableSource<NetBean> apply(@NonNull UserParam userParam) throws Exception { // 1.发送网络请求,获取NetBean return apiService.getUserInfo(userParam.getParam1(), userParam.getParam2()); } }) .flatMap(new Function<NetBean, ObservableSource<UserBean>>() { @Override public ObservableSource<UserBean> apply(@NonNull NetBean netBean) throws Exception { UserBean user = new UserBean(netBean.getForm().getUsername(), netBean.getForm().getPassword()); // 2.转换NetBean数据为我们需要的UserBean数据 return Observable.just(user); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<UserBean>() { @Override public void accept(@NonNull UserBean userBean) throws Exception { Log.i("JAVA", "" + "用户名:" + userBean.getUsername() + ", 密码:" + userBean.getPassword()); } });
2.模拟合并本地与服务器购物车列表
这个案例其实就是用户添加购物车的时候,首先会在本地存储一份,然后发现如果没有网络,那么没办法提交到服务器上,只能等下一次有网络的时候采用本地数据库和服务器数据的合并来实现上传到服务器。
首先需要准备 Retrofit 对象和获取本地数据、网络数据的方法:
private ApiService apiService;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 省略 // 构建Retrofit apiService = new Retrofit.Builder() .baseUrl("http://httpbin.org/") .addConverterFactory(GsonConverterFactory.create()) // RxJava2与Gson混用 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2与Retrofit混用 .build() .create(ApiService.class); }/** * 获取本地数据 */private Observable<List<String>> getDataForLocal() { List<String> list = new ArrayList<>(); list.add("购物车的商品1"); list.add("购物车的商品2"); return Observable.just(list); }/** * 获取网络数据 */private Observable<List<String>> getDataForNet() { return Observable.just("shopName") // flatMap方法是用于数据格式转换的方法,参数一表示原数据, // 参数二表示转换的数据,那么就是通过发送网络参数,转换成网络返回的数据,调用Retrofit .flatMap(new Function<String, ObservableSource<NetBean>>() { @Override public ObservableSource<NetBean> apply(@NonNull String s) throws Exception { // 1.发送网络请求,获取数据 return apiService.getCartList(s); } }).flatMap(new Function<NetBean, ObservableSource<List<String>>>() { @Override public ObservableSource<List<String>> apply(@NonNull NetBean netBean) throws Exception { // String shop = netBean.get_$Args257().getShopName(); String shop = "购物车的商品3"; List<String> list = new ArrayList<>(); list.add(shop); // 2.转换NetBean数据为我们需要的List<String>数据 return Observable.just(list); } }).subscribeOn(Schedulers.io()); }
然后我们就可以创建被观察者并订阅了,来完成合并本地与服务器购物车列表操作:
// merge操作符: 将两个ObservableSource合并为一个ObservableSourceObservable.merge(getDataForLocal(), getDataForNet()) .subscribe(new Observer<List<String>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(List<String> strings) { for (String str: strings) { Log.i("JAVA", str); } } @Override public void onError(Throwable e) { } @Override public void onComplete() { Log.i("JAVA", "onComplete"); } });
最后的打印结果是:
I/JAVA: 购物车的商品1 I/JAVA: 购物车的商品2 I/JAVA: 购物车的商品3 I/JAVA: onComplete
4.RxJava2 与 RxBinding 的使用
1.优化搜索请求
当我们在 EditText 打字时搜索的时候,可能用户会打字很会快,那么我们就没有必要一直发送网络请求,请求搜索结果,我们可以通过当用户打字停止后的延时500毫秒再发送搜索请求:
// RxTextView.textChanges(edittext): Rxbinding用法RxTextView.textChanges(editText) // 表示延时多少秒后执行,当你敲完字之后停下来的半秒就会执行下面语句 .debounce(500, TimeUnit.MILLISECONDS) // 数据转换 flatMap: 当同时多个数据请求访问的时候,前面的网络数据会覆盖后面的网络数据 // 数据转换 switchMap: 当同时多个网络请求访问的时候,会以最后一个发送请求为准,前面网络数据会被最后一个覆盖 .switchMap(new Function<CharSequence, ObservableSource<List<String>>>() { @Override public ObservableSource<List<String>> apply( @NonNull CharSequence charSequence) throws Exception { // 网络请求操作,获取我们需要的数据 List<String> list = new ArrayList<String>(); list.add("2017"); list.add("2018"); return Observable.just(list); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<List<String>>() { @Override public void accept(@NonNull List<String> strings) throws Exception { // 更新UI Log.i("JAVA", strings.toString()); } });
当用户一直点击一个按钮的时候,我们不应该一直调用访问网络请求,而是 1秒内,只执行一次网络请求。
RxView.clicks(button).throttleFirst(1, TimeUnit.SECONDS) .subscribe(new Observer<Object>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(Object o) { Log.i("JAVA", "onClick"); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
5.RxJava2 踩过的一些坑
1.未解除订阅而引起的内存泄漏
举个例子,对于前面常用操作符 interval 做周期性操作的例子,并没有使之停下来的,没有去控制订阅的生命周期,这样,就有可能引发内存泄漏。所以,在 Activity 的 onDestroy() 方法执行的时候或者不需要继续执行的时候应该解除订阅。