上车RxJava2(二)

笔者在上一篇博客介绍了 RxJava 的基本使用和它的线程控制,这篇博客介绍RxJava一个很核心、很牛逼的功能-操作符。

RxJava的操作符有很多,主要分为以下几大类:创建操作符、变换操作符、过滤操作符、组合操作符、错误处理符、辅助操作符、条件和布尔操作符等等。每一种类型操作符下又有很多个具体的操作符,笔者在这篇博客介绍其中几个操作符。

上一篇博客的实战部分,用到了几个操作符,比如create、just、from,它们都是创建操作符,用于创建Observable的操作符。它比较简单,在本篇博客不再介绍。

上车RxJava2(一)

变换操作符

RxJava一个很牛逼的地方是它提供了对事件序列进行变换的支持,所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。

变化操作符有:FlatMap、Map、Buffer、Scan、GroupBy、Window

变换操作符主要介绍一下两个:

  • Map — 映射,通过对序列的每一项都应用一个函数变换Observable发射的数据,实质是对序列中的每一项执行一个函数,函数的参数就是这个数据项
  • FlatMap — 扁平映射,将Observable发射的数据变换为Observables集合,然后将这些Observable发射的数据平坦化的放进一个单独的Observable,可以认为是一个将嵌套的数据结构展开的过程。

概念过于抽象,先看个图吧

7911186-856eb7d6e3efe2e1.png
map操作符.png

结合这张图,map可以简单的理解为通过映射关系把一个对象转换成另外一个对象。比如注册的时候,把用户这个对象(包含用户名和密码)。作为参数通过网络请求进行注册,对返回的json数据解析完后又是一个结果对象,通过map可以将请求对象转换成结果对象。是不是很神奇且牛批。

下面用一个注册用户的栗子先介绍Map的使用

(1)请求接口

public interface HttpRequest {
    @GET("zhuce/rxjava.php/")
    Call<HttpResult> getRegisteResult(@Query("name") String name, @Query("pass") String pass);
}

(2)请求对象类

public class UserInfo {
    private String name;
    private String pass;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPass() {
        return pass;
    }
    public void setPass(String pass) {
        this.pass = pass;
    }
}

(3)结果对象类

public class HttpResult {
    private String result;
    public String getResult() {
        return result;
    }
    public void setResult(String result) {
        this.result = result;
    }
}

(4)map操作符的使用

public void mapOperation(View view) {
  Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
       final HttpRequest httpRequest = retrofit.create(HttpRequest.class);
       
       Observable.just(getUserInfo())
       .map(new Function<UserInfo, HttpResult>() {
           @Override
           public HttpResult apply(UserInfo userInfo) throws Exception {
               HttpResult httpResult = httpRequest.getRegisteResult(userInfo.getName()
               ,userInfo.getPass()).execute().body();
               return  httpResult;
           }
       }).subscribeOn(Schedulers.io())
               .subscribe(new Consumer<HttpResult>() {
                   @Override
                   public void accept(HttpResult httpResult) throws Exception {
                       //Log.d(TAG, "accept: " + httpResult.getResult());
                       mText.setText(httpResult.getResult());
                   }
               });   
}


先使用just这个创建符创建一个Observable,并获取用户的信息。之后使用map转换操作符

map(new Function<UserInfo, HttpResult>(){...});

map里需要一个Function对象,它有两个泛型参数,前者是转换前的对象、后者是转换后的对象

这样在回调的apply方法里就可以实现网络请求操作

  HttpResult httpResult = httpRequest.getRegisteResult(userInfo.getName()
               ,userInfo.getPass()).execute().body();

不要忘记切换到io线程中操作

.subscribeOn(Schedulers.io())

最后就是订阅了,需要创建一个观察者消费该事件,进行ui的更新

.subscribe(new Consumer<HttpResult>() {
                   @Override
                   public void accept(HttpResult httpResult) throws Exception {
                       mText.setText(httpResult.getResult());
                   }
               });   
    }
FlatMap变换操作符

FlatMap 和 map 有一个相同点:它也是把传入的参数转化之后返回另一个对象。

不同的是, flatMap返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中

7911186-a1b47cb53d3d2af1.png
FlatMap.png

flatMap过程原理如下

  1. 使用传入的事件对象创建一个 Observable 对象
  2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件
  3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。

这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象铺平(flat)之后通过统一路径分发了下去。

改写之前注册的例子

  Observable.just(getUserInfo()).flatMap(new Function<UserInfo, ObservableSource<HttpResult>>() {
           @Override
           public ObservableSource<HttpResult> apply(UserInfo userInfo) throws Exception {
               HttpResult httpResult = httpRequest.getRegisteResult(userInfo.getName(),
               userInfo.getPass()).execute().body();
               return  Observable.just(httpResult);
           }
       }).subscribeOn(Schedulers.io())
               .subscribe(new Consumer<HttpResult>() {
                   @Override
                   public void accept(HttpResult httpResult) throws Exception {
                       mText.setText(httpResult.getResult());
                   }
               });

可以看到基本没有什么改动,最明显的不同就是在返回的结果对象中

flatMap(new Function<UserInfo, ObservableSource<HttpResult>>()

不直接是 HTTPResult 作为参数 而是一个ObservableSource。这样看并没有比map有优势啊,
但是如果有这样一个需求,注册完之后自动登录。用map就不能直接将这两个事件汇在一起最后接收处理。
这个时候就可以用在使用一个flatMap,它对注册的结果对象作为参数进行登录请求,再转换成登录结果对象。
这样两个事件被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。

Observable.just(getUserInfo())
           .flatMap(new Function<UserInfo, ObservableSource<HttpResult>>() {
           @Override
           public ObservableSource<HttpResult> apply(UserInfo userInfo) throws Exception {
               HttpResult httpResult = httpRequest.getRegisteResult(userInfo.getName(),
               userInfo.getPass()).execute().body();
               return  Observable.just(httpResult);
           }
         })
          .flatMap(new Function<HttpResult, ObservableSource<LoginResult>>() {
             @Override
             public ObservableSource<LoginResult> apply(HttpResult httpResult) throws Exception {
                if(httpResult.getResult().equals("used")){
                    UserInfo userInfo = getUserInfo();
                    LoginResult loginResult = httpRequest.getLoginResult(userInfo.getName(),
                    userInfo.getPass()).execute().body();
                    return Observable.just(loginResult);
              }
           }
       })
      .subscribeOn(Schedulers.io())
      .subscribe(new Consumer<LoginResult>() {
           @Override
          public void accept(LoginResult loginResult) throws Exception {
              mText.setText(loginResult.getResult());
                   }
               });

这样就可以实现注册完直接登录的需求。把注册和登录这两个事件平铺完之后统一发放下去

Retrofit 和 Rxjava2的适配

我们创建Retrofit对象的时候添加了和Rxjava的适配,有什么作用呢?

addCallAdapterFactory(RxJavaCallAdapterFactory.create())

它能让Retrofit和Rxjava能够无缝适配,让我们更专注于事件的逻辑。比如之前的代码可以变的更简洁

(1)把请求接口方法返回值改为Observable

  @GET("zhuce/rxjava.php/")
    Observable<HttpResult> getRegisteResult(@Query("name") String name, @Query("pass") String pass);

(2)只需要进行请求,它会直接返回一个Observable对象,不需要自己去创建该对象并返回,不要太方便

 Observable.just(getUserInfo())
               .flatMap(new Function<UserInfo, ObservableSource<HttpResult>>() {
           @Override
           public ObservableSource<HttpResult> apply(UserInfo userInfo) throws Exception {
               return  httpRequest.getRegisteResult(userInfo.getName(),userInfo.getPass());
           }
       })
       //....

(3)运行后抛出异常

Unable to create call adapter for io.reactivex.Observable<com.example.rxjavademo.bean.HttpResult>

adapter-rxjava目前还不支持Rxjava 2.x。但是jakewharton大神自己写了一个库让Retrofit来支持Rxjava 2.x

(4)引入库

implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'

将设配工厂代码改为

addCallAdapterFactory(RxJava2CallAdapterFactory.create())

再次运行不再抛异常

过滤操作符

过滤操作符的目的是在一系列的Observable中过滤一些事件后再发出。

过滤操作符有:Debounce、Filter、Distinct、First、Take.....

比如经常看见的一个需求:在文本框输入关键字,对关键字进行过滤搜索(可以联想淘宝搜索框)。

普通的做法是,通过一个文本框监听文本内容的变化后进行网络请求,再展示返回的数据

mEdSearch.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) {
                //文本变化 --请求网络---更新结果
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

这会有两个问题:

  • 往往我们需要的是一定量的关键字再过滤,而不是文本内容一变化就搜索,这会造成很多流量的浪费
  • 有可能因为网络的波动,会引起第一次搜索结果晚于第二次搜索结果到达,结果不可控。

通过过滤操作符可以解决以上两个问题:

(1)引入RxBinding库

这个实例需要引入RxBinding库

implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha2'

(2)使用过滤操作符的debounce

7911186-8d2a00ec9470913d.png
过滤操作符debounce.png

过滤操作符debounce 它会在定义的时间内监测最后的变化结果。
比如下面的例子表示它会获取在监测到文本变化后 2000ms 内的最后一次结果

RxTextView.textChanges(mEdSearch).debounce(2000,TimeUnit.MILLISECONDS)

(3)使用过滤操作符filter

7911186-be9d5e62e44df362.png
过滤操作符filter.png

过滤操作符filter,它会根据你的过滤规则返回一个布尔值,如果为false就不会将事件传递下去
比如下面的例子表示只有当输入的文本内容不为空,才可以继续之后的事件

 .filter(new Predicate<CharSequence>() {
                    @Override
                    public boolean test(CharSequence charSequence) throws Exception {
                       return  charSequence.toString().length()>0;
                    }
                })

(4)获取网络请求过滤的结果

switchMap(new Function<CharSequence, ObservableSource<List<String>>>() {
                    @Override
                    public ObservableSource<List<String>> apply(CharSequence charSequence) throws Exception {
                        //模拟网络请求过滤的结果
                        List<String> list = new ArrayList<>();
                        list.add("abc");
                        list.add("abcc");
                        return Observable.just(list);
                    }
                })

(5)把事件结果传递给 Subscribe

 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Consumer<List<String>>() {
            @Override
            public void accept(List<String> stringList) throws Exception {
                StringBuilder str = new StringBuilder();
                for(String s : stringList){
                    str.append(s+"\n");
                }
                mText.setText(str.toString());
            }
        });

完整代码如下

RxTextView.textChanges(mEdSearch).debounce(2000,TimeUnit.MILLISECONDS)
                .subscribeOn(AndroidSchedulers.mainThread())  //UI操作要在主线程
                .filter(new Predicate<CharSequence>() {
                    @Override
                    public boolean test(CharSequence charSequence) throws Exception {
                       return  charSequence.toString().length()>0;
                    }
                })
                .subscribeOn(Schedulers.io())  //swtichmap里的网络请求放在io线程
                .switchMap(new Function<CharSequence, ObservableSource<List<String>>>() {
                    @Override
                    public ObservableSource<List<String>> apply(CharSequence charSequence) throws Exception {
                        //模拟网络请求过滤的结果
                        List<String> list = new ArrayList<>();
                        list.add("abc");
                        list.add("abcc");
                        return Observable.just(list);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<List<String>>() {
            @Override
            public void accept(List<String> stringList) throws Exception {
                StringBuilder str = new StringBuilder();
                for(String s : stringList){
                    str.append(s+"\n");
                }
                mText.setText(str.toString());
            }
        });
总结

本篇博客笔者介绍了变化操作符和过滤操作符中的几个,想学习其他的用法,可以传送到Rxjava的中文文档

RxJava的操作符中文文档

英文好的可以传送到官方文档

Rxjava的操作符官方文档

由于操作符太多,写着写着发现才写了两类操作符的几个,文章就有点过长了。还是需要再来一篇博客写剩下的操作符,可能还需要两篇[啊哈哈]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值