RxJava操作符在android中的使用场景详解(一)

最近学习了RxJava在android中的使用,关于RxJava是啥,为什么要用RxJava,好在哪,这里就不叙述了,如果想要了解请移步官方文档大神文章

这里只讲解一下RxJava中的操作符,以及项目中具体的使用场景。

因为学习了有20个操作符,可能一篇文章过于臃肿,所以打算写成系列文章,本文中所有的操作符使用,都写在了一个demo中,已上传至github

场景一:RxJava基本使用

配合Retrofit请求网络数据,如果你对Retrofit不熟悉就先看Retrofit官网,实现步骤如下:

  1. 先是build.gradle的配置
    compile 'io.reactivex:rxandroid:1.1.0'
    compile 'io.reactivex:rxjava:1.1.0'
    compile 'com.squareup.retrofit2:retrofit:2.0.0-beta3'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta3'
    compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta3'
    compile 'com.jakewharton:butterknife:7.0.1'

    也就是说本文是基于RxJava1.1.0和Retrofit 2.0.0-beta3来进行的。
    添加rxandroid是因为rxjava中的线程问题。

  2. 基本网络请求使用准备

    我们使用http://zhuangbi.info/search?q=param测试连接,返回的是json格式,代码就不贴了

    接下来我们要创建一个接口取名为ZhuangbiApi,代码如下:

    public interface ZhuangbiApi {
        @GET("search")
        Observable<List<ImageInfoBean>> search(@Query("q") String query);
    }
    
    

    Retrofit、Gson、RxJava结合使用,建立网络请求类:

     public static ZhuangbiApi getZhuangbiApi() {
            if (zhuangbiApi == null) {
                Retrofit retrofit = new Retrofit.Builder()
                        .client(okHttpClient)
                        .baseUrl("http://zhuangbi.info/")
                        .addConverterFactory(gsonConverterFactory)
                        .addCallAdapterFactory(rxJavaCallAdapterFactory)
                        .build();
                zhuangbiApi = retrofit.create(ZhuangbiApi.class);
            }
            return zhuangbiApi;
        }
    
  3. 具体使用
    将要查询的关键字传进去,使用上面建立的网络请求类请求数据,并在订阅者的回调方法中,进行网络请求结果的处理

     private void search(String key) {
    
            subscription = Network.getZhuangbiApi()
                    .search(key)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(getObserver());
        }        
    
        private Observer<? super List<ImageInfoBean>> getObserver() {
    
            if (null == observer) {
                observer = new Observer<List<ImageInfoBean>>() {
                    @Override
                    public void onCompleted() {
    
                    }
    
                    @Override
                    public void onError(Throwable e) {
                        swipeRefreshLayout.setRefreshing(false);
                        Toast.makeText(getActivity(), R.string.loading_failed, Toast.LENGTH_SHORT).show();
                    }
    
    
                    @Override
                    public void onNext(List<ImageInfoBean> images) {
    
                        swipeRefreshLayout.setRefreshing(false);
                        adapter.setImages(images);
                    }
                };
            }
    
            return observer;
        }
    
  1. 详解

    search方法中传入的key是要查询的关键词,getObserver()是获取订阅者对象,并在其回调方法中根据返回结果,做相应处理:

    其中onNext方法返回了数据,这样我们能够在onNext里面处理数据相关的逻辑;
    onError方法中处理错误,同时也可以停止ProgressDialog等;
    onComplated只调用一次结束本次请求操作,也可以停止ProgressDialog;

场景二:Map操作符的使用(变换)

对Observable发射的每一项数据应用一个函数,执行变换为指定类型的操作,然后再发射

有些服务端的接口设计,会在返回的数据外层包裹一些额外信息,这些信息对于调试很有用,但本地显示是用不到的。使用 map() 可以把外层的格式剥掉,只留下我们只关心的部分,具体实现步骤如下:

  1. 网络请求使用准备
    我们使用http://gank.io/api/测试连接
    接下来我们要创建一个接口取名为GankApi ,代码如下:

    public interface GankApi {
        
        @GET("data/福利/{number}/{page}")
        Observable<BeautyResult> getBeauties(@Path("number") int number, @Path("page") int page);
        
    }    
    

    Retrofit、Gson、RxJava结合使用,建立网络请求类:

     public static GankApi getGankApi() {
            if (gankApi == null) {
                Retrofit retrofit = new Retrofit.Builder()
                        .client(okHttpClient)
                        .baseUrl("http://gank.io/api/")
                        .addConverterFactory(gsonConverterFactory)
                        .addCallAdapterFactory(rxJavaCallAdapterFactory)
                        .build();
                gankApi = retrofit.create(GankApi.class);
            }
            return gankApi;
        }
    
  2. 数据转换
    返回数据就不贴了,有兴趣可以请求接口看一下。
    接口返回的数据包含了一些额外的信息,但是我们只需要返回数据中的list部分,所以创建一个类,来实现数据转换的功能,代码如下:

    public class BeautyResult2Beautise implements Func1<BeautyResult, List<ImageInfoBean>> {
    
    
        public static BeautyResult2Beautise newInstance() {
            return new BeautyResult2Beautise();
        }
    
    
        /**
         * 将接口返回的BeautyResult数据中的list部分提取出来,返回集合List<ImageInfoBean>
         * @param beautyResult
         * @return
         */
        @Override
        public List<ImageInfoBean> call(BeautyResult beautyResult) {
    
            List<ImageInfoBean> imageInfoBeanList = new ArrayList<>(beautyResult.results.size());
    
            for (ImageInfoBean bean : beautyResult.results) {
                ImageInfoBean imageInfoBean = new ImageInfoBean();
    
                imageInfoBean.description = bean.desc;
    
                imageInfoBean.image_url = bean.url;
    
                imageInfoBeanList.add(imageInfoBean);
    
            }
    
            return imageInfoBeanList;
        }
    }
    
  1. 操作符的使用

    加载数据

    /**
         * 加载数据的方法
         * @param page
         */
        private void loadPage(int page) {
            mSwipeRefreshLayout.setRefreshing(true);
            unsubscribe();
            subscription = Network.getGankApi()
                    .getBeauties(8, page)
                    .map(BeautyResult2Beautise.newInstance())
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(getObserver());
        }
    

    订阅者

    /**
         * 获取订阅者
         * @return
         */
        private Observer<? super List<ImageInfoBean>> getObserver() {
    
            if (null == observer) {
                observer = new Observer<List<ImageInfoBean>>() {
                    @Override
                    public void onCompleted() {
                        mSwipeRefreshLayout.setRefreshing(false);
                    }
    
                    @Override
                    public void onError(Throwable e) {
                        mSwipeRefreshLayout.setRefreshing(false);
    
                        Toast.makeText(getActivity(), R.string.loading_failed, Toast.LENGTH_SHORT).show();
                    }
    
                    @Override
                    public void onNext(List<ImageInfoBean> images) {
                        adapter.setImages(images);
                    }
                };
            }
    
    
            return observer;
        }
    
  1. 详解
    Map操作符对Observable发射的每一项数据应用一个函数,执行变换操作,然后返回一个发射这些结果的Observable。
    本例中,接口返回的数据格式是:
    public class BeautyResult {
    
    
        public boolean error;
    
        public List<ImageInfoBean> results;
    
    
    }
    
    
    但是我们只关心list部分的数据,所以进行转换操作,这样订阅者回调方法中拿到的数据直接进行使用就好了

场景三:Zip操作符的使用(结合)

通过一个函数将多个Observables的发射物结合到一起,基于这个函数的结果为每个结合体发射单个数据项,具体实现步骤如下:

  1. 网络请求装备
    网络请求Api,以及请求类,还是使用场景一、二中的创建好的。

  2. 请求数据,并结合,代码如下:

     /**
         * 请求两个接口,对返回的数据进行结合
         */
        private void load() {
    
            swipeRefreshLayout.setRefreshing(true);
            subscription = Observable.zip(Network.getGankApi().getBeauties(188, 1).map(BeautyResult2Beautise.newInstance()),
                    Network.getZhuangbiApi().search("装逼"),
                    new Func2<List<ImageInfoBean>, List<ImageInfoBean>, List<ImageInfoBean>>() {
                        @Override
                        public List<ImageInfoBean> call(List<ImageInfoBean> imageInfoBeen, List<ImageInfoBean> imageInfoBeen2) {
    
                            int num = imageInfoBeen.size() < imageInfoBeen2.size() ? imageInfoBeen.size() : imageInfoBeen2.size();
                            List<ImageInfoBean> list = new ArrayList<>();
                            for (int i = 0; i < num; i++) {
    
                                list.add(imageInfoBeen.get(i));
                                list.add(imageInfoBeen2.get(i));
    
                            }
    
                            return list;
                        }
                    }).subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(getObserver());
        }
    
  1. 详解
    请求GankApi中的数据使用map操作符进行转换,取出自己想要的list数据,然后结合ZhuangbiApi中的数据,形成新的数据集合,填充到view。

    Zip操作符使用函数按顺序结合多个Observables发射的数据项,然后它发射这个函数返回的结果,它只发射与数据项最少的那个Observable一样多的数据。

    一般app中同一个界面有时会需要同时访问不同接口,然后将结果糅合后转为统一的格式后输出(例如将第三方广告 API 的广告夹杂进自家平台返回的数据 List 中)。这种并行的异步处理比较麻烦,不过用了 zip() 之后就会简单明了。
    上一个效果图:


    图片描述
    图片描述

    可以看出,recyclerView中使用了一个数据集合,但左侧的一列展示的是GankApi中的数据,右侧一列展示的是ZhuangbiApi 中的数据。

场景四:CombineLatest操作符的使用(结合)

结合多个Observable发射的最近数据项,当原始Observables的任何一个发射了一条数据时,CombineLatest使用一个函数结合它们最近发射的数据,然后发射这个函数的返回值,具体实现步骤如下:

  1. 使用场景,用一个简单明了的图片来表示吧


    图片描述
    图片描述
  2. 上图简单演示了CombineLatest的使用场景,看代码吧:

     /**
         * 将3个EditText的事件进行结合
         */
        private void combineLatestEvent() {
    
            usernameObservable = RxTextView.textChanges(mUsername).skip(1);
            emailObservable = RxTextView.textChanges(mEmail).skip(1);
            passwordObservable = RxTextView.textChanges(mPassword).skip(1);
    
            subscription = Observable.combineLatest(usernameObservable, emailObservable, passwordObservable,
                    new Func3<CharSequence, CharSequence, CharSequence, Boolean>() {
                        @Override
                        public Boolean call(CharSequence userName, CharSequence email, CharSequence password) {
    
                            boolean isUserNameValid = !TextUtils.isEmpty(userName) && (userName.toString().length() > 2 && userName.toString().length() < 9);
    
                            if (!isUserNameValid) {
                                mUsername.setError("用户名无效");
                            }
    
    
                            boolean isEmailValid = !TextUtils.isEmpty(email) && Patterns.EMAIL_ADDRESS.matcher(email).matches();
    
                            if (!isEmailValid) {
                                mEmail.setError("邮箱无效");
                            }
    
                            boolean isPasswordValid = !TextUtils.isEmpty(password) && (password.toString().length() > 6 && password.toString().length() < 11);
    
                            if (!isPasswordValid) {
                                mPassword.setError("密码无效");
                            }
    
    
                            return isUserNameValid && isEmailValid && isPasswordValid;
                        }
                    })
                    .subscribe(getObserver());
        }
    
    
    /**
         * 获取订阅者
         * @return
         */
        private Observer<Boolean> getObserver() {
            return new Observer<Boolean>() {
                @Override
                public void onCompleted() {
    
                }
    
                @Override
                public void onError(Throwable e) {
    
                }
    
                @Override
                public void onNext(Boolean aBoolean) {
                    //更改注册按钮是否可用的状态
                    mButton.setEnabled(aBoolean);
                }
            };
        }
    
  3. 详解
    CombineLatest操作符行为类似于zip,但是只有当原始的Observable中的每一个都发射了一条数据时zip才发射数据。

    CombineLatest则在原始的Observable中任意一个发射了数据时发射一条数据。

    当原始Observables的任何一个发射了一条数据时,CombineLatest使用一个函数结合它们最近发射的数据,然后发射这个函数的返回值。

    本例中,含用户名、邮箱、密码、注册按钮的注册页面的场景非常常见,当然可以使用普通的处理方式能够达成,注册按钮的是否可用更改的效果,以及输入是否合法的及时提示。

    但是使用RxJava的方式,代码明显简洁、易懂。

小结:

虽然,上面四个使用场景主要介绍四个操作符的使用,但其实demo中穿插了不少其他操作符的使用,想要详细了解的话,代码在这里

暂时先写到这里,后面会把其他自己学会的的操作符,写成系列文章。如有兴趣,请关注我的github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容