Android 响应式编程 RxJava2 完全解析

organicmeercat 发布于1年前 阅读12603次
0 条评论

转载请注明出处:http://blog.csdn.net/smartbetter/article/details/68941859

使用了 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 作为一个工具库,使用的便是通用形式的观察者模式:

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"

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'

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
}

2.ApiService,这里返回Observable对象,也就是我们RxJava的被观察者

public interface ApiService {
    @FormUrlEncoded
    @POST("/post")
    Observable<NetBean> getUserInfo(@Field("username")String username,
                                    @Field("password")String password);
}

3.RxJava + Retrofit 的实现

// 构建Retrofit
ApiService apiService = new Retrofit.Builder()
        .baseUrl("http://httpbin.org/")
        .addConverterFactory(GsonConverterFactory.create()) // RxJava2与Gson混用
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2与Retrofit混用
        .build()
        .create(ApiService.class);

// 构建RxJava
UserParam 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;
@Override
protected 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合并为一个ObservableSource
Observable.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());
            }
        });

2.优化点击请求

当用户一直点击一个按钮的时候,我们不应该一直调用访问网络请求,而是 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() 方法执行的时候或者不需要继续执行的时候应该解除订阅。

更多 RxJava 示例代码请访问:https://github.com/smartbetter/RxJavaDemo

查看原文: Android 响应式编程 RxJava2 完全解析

 

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。