Android RxJava之葵花宝典(下)(看我就够了)---使用场景

关于入门基础看这里Android RxJava之葵花宝典(上)(看我就够了)----入门基础

讲使用场景之前先普及一下一些关于Rx的另外框架

  • RxBinding : 控件绑定库

使用如下

Platform bindings:
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'

'support-v4' library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0'

'appcompat-v7' library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.4.0'

'design' library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0'

'recyclerview-v7' library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-recyclerview-v7:0.4.0'

'leanback-v17' library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-leanback-v17:0.4.0'
  • RxPermissions

RxPermissions也是国外的大牛开发的基于RxJava的Android权限管理库,他让6.0以上的权限管理更加的简单,如果有适配6.0以上的手机的需求,这个库是个不错的选择。下面我们来看看基本的用法。

    // 请求相机权限
    RxPermissions.getInstance(this)
    .request(Manifest.permission.CAMERA)
    .subscribe(granted -> {
        if (granted) { // 用户同意了(在6.0之前的手机始终都为true)
          //可以拍照了
        } else {
           //可以在这里提示用户,或者再次请求
        }
    });

当然,如果我想一次请求多个权限呢,每次都去写上面的代码肯定是个不好的做法,RxPermissions的作者也考虑到了这一点,在Api里提供了一个多参数的重载

//取得相机权限和读取手机状态
RxPermissions.getInstance(this)
    .request(Manifest.permission.CAMERA,
             Manifest.permission.READ_PHONE_STATE)
    .subscribe(granted -> {
        if (granted) {

        } else {

        }
    });
  • RxBus

有了RxJava,EventBus、Otto什么的都可以靠边了,因为RxJava本身就自带了这个功能,我们只需做一下简单的封装就可以使用了,也顺便减少了我们项目的体积。

public class RxBus {

    private final Subject<Object, Object> _bus;

    private static class RxBusHolder {
        private static final RxBus instance = new RxBus();
    }

    private RxBus() {
        _bus = new SerializedSubject<>(PublishSubject.create());
    }

    public static synchronized RxBus getInstance() {
        return RxBusHolder.instance;
    }

    public void post(Object o) {
        _bus.onNext(o);
    }

    public <T> Observable<T> toObserverable(Class<T> eventType) {
        return _bus.ofType(eventType);
}

怎么去使用?
在需要发送消息的地方

RxBus.getInstance().post("SomeChange");

在需要接收消息的地方

 Subscription mSubscription = RxBus.getInstance().toObserverable(String.class).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                handleRxMsg(s);
            }
});

不要忘了在适当的地方去取消这个订阅(以免发生内存泄漏)

mSubscription.unsubscribe();
一、RxJava的使用场景
场景一:缓存检测

在请求取数据的处理过程中,我们的操作一般是这样一个原理:

  • 首先检查内存是否有缓存
  • 然后检查文件缓存中是否有
  • 最后才从网络中取
    任何一步一旦发现数据后面的操作都不执行
    在rxjava中为我们提供了两个解决这个问题的操作符,分别是: concatfirst
concat

不交错的发射两个或多个Observable
concat操作符连接多个Observable的输出,就好像它们是一个Observable,第一个Observable发射的所有数据在第二个Observable发射的任何数据前面,以此类推。直到前面一个Observable终止,Concat才会订阅额外的一个Observable

请注意上面所说的“就好像它们是一个Observable”,其实并不是一个Observable,是前面一个停止之后才会订阅下一个,所以说他们并不是一个,请君注意咯。其实就是将两个Observable连接起来了。
还有一个实例方法concatWith,它是和concat等价的:Observable.concat(a,b)==a.concatWith(b)

      Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "onError: onError");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "onNext: " + integer);
            }
        };

        Observable a = Observable.just(1, 2, 3, 4, 5);
        Observable b = Observable.just(6, 7, 8, 9, 10);

        Observable.concat(a, b)
                .subscribe(subscriber);

------------------------------------------
//打印结果
onNext: 1
onNext: 2
onNext: 3
 ...
onNext: 10
onCompleted: onCompleted

发现只打印了一次onCompleted: onCompleted,只有当onNext没有接收到数据时,才会调用onCompleted。

最后对这个操作符,再补充一点:如果当第一个Observable a抛异常,那么将不会继续执行后面的Observable b了。

测试代码

//讲a写成如下
Observable a = Observable.just(1, 2, 3, 4, new RuntimeException());
first

只发射第一项(或者满足某个条件的第一项)数据

      Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "onError: onError");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "onNext: " + integer);
            }
        };

        Observable a = Observable.just(1, 2, 3, 4, 5);
        a.first().subscribe(subscriber);

-----------------------------------
//打印结果
onNext: 1
onCompleted: onCompleted

这个只要第一项满足条件,后面的将不会再进行发射,所以只是得到了1这个数字。

在这儿必须为大家区别一个操作符:single(),这个操作符也是只打印一个数据的,但是single()和first()最大的区别在于:前者只会发射一个数据,不能发射多个,否则会报错;而first确实满足条件的那一个。
Observable a = Observable.just(1);
a.single().subscribe(subscriber);

最后总结这个场景的Demo

      final Observable<String> memory = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                if (memoryCache != null) {
                    subscriber.onNext(memoryCache);
                } else {
                    subscriber.onCompleted();
                }
            }
        });
        Observable<String> disk = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                String cachePref = rxPreferences.getString("cache").get();
                if (!TextUtils.isEmpty(cachePref)) {
                    subscriber.onNext(cachePref);
                } else {
                    subscriber.onCompleted();
                }
            }
        });

        Observable<String> network = Observable.just("network");

        //依次检查memory、disk、network  
        Observable
                .concat(memory, disk, network)
                .first()
                .subscribeOn(Schedulers.newThread())
                .subscribe(s -> {
                    memoryCache = "memory";
                    System.out.println("--------------subscribe: " + s);
                });

场景二:输入合法场景

在某些时候,我们需要所以的输入都合法后,我们的某些按钮才亮起来,或者才能点击
在这个场景中,我们得掌握两个操作符:skipcombineLatest

skip

抑制Observable发射的前N项数据

       Observable.just(1,2,3,4).skip(2).subscribe(new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "onError: onError");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "onNext: "+integer);
            }
        });

-----------------------------------------
//打印结果
onNext: 3
onNext: 4
onCompleted: onCompleted
combineLatest

当多个Observables中的任何一个发射了数据时,使用一个函数结合每个Observable发射的最近数据项,并且基于这个函数的结果发射数据。
CombineLatest在原始的Observable中任意一个发射了数据时发射一条数据。当原始Observables的任何一个发射了一条数据时,CombineLatest
使用一个函数结合它们最近发射的数据,然后发射这个函数的返回值。
不懂?看代码:

private void combineLatestEvent() {

        Observable<CharSequence> usernameObservable = RxTextView.textChanges(mUsername).skip(1);
        Observable<CharSequence> emailObservable = RxTextView.textChanges(mEmail).skip(1);
        Observable<CharSequence> passwordObservable = RxTextView.textChanges(mPassword).skip(1);

       Subscription 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() >5 && password.toString().length() < 11);

                        if (!isPasswordValid) {
                            mPassword.setError("密码无效");
                        }


                        return isUserNameValid && isEmailValid && isPasswordValid;
                    }
                })
                .subscribe(getObserver());
    }


    private Observer<Boolean> getObserver() {
        return new Observer<Boolean>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Boolean aBoolean) {
                //更改注册按钮是否可用的状态
                register.setEnabled(aBoolean);
            }
        };
    }

这个场景,有3个edittext,分别是mUsername,mEmail,mPassword,通过输入合法的内容进行判定注册按钮是否亮起来。
当我点击其中的任何一个进行编写的时候,就会发射数据,发射的是什么?是我们编辑的内容吗?其实不是的,发射的是结合Func3这个方法的返回值,在这里这个返回值是Boolean型的。返回了boolean型之后,就可以在观察者里面设置注册按钮是否亮起来。现在再看上面那句高深莫测的话,是不是简单多了!

场景三:数据过期场景

其实这个场景可以和上面的数据缓存检测场景进行合并:在缓存检测场景中,我们知道,如果memory中没有数据,就从disk上面寻找,然后再是网络请求,那么,问题来了,如果我们的memory中一直有数据,但是网络数据已经变更了,又由于缓存检测原则的只要有一个有数据就不会进行网络请求了,这就会造成我们显示的数据一直是一个旧数据。

解决方法有如下两个:

  • 采用定时进行清除本地缓存数据
  • 采用过滤操作符

我们先来看看第一种,如果是进行定时做本地数据清空的话,那么就会用到,我们一个轮询的操作符Interval
创建一个按固定时间间隔发射整数序列的Observable
Interval通俗的讲,就是每隔一段时间过后做什么事情!(不了解看这里Android RxJava之葵花宝典(上)(看我就够了)----入门基础

Observable.interval(3, TimeUnit.SECONDS).subscribe(new Observer<Long>() {
           ...
           @Override
           public void onNext(Long aLong) {
                //清除缓存操作
           }
       });
采用过滤操作符

其实这个操作符就是first,在众多的数据,有一个符合条件就发射数据,后面的都将不执行。

Observable source = Observable
    .concat(memory, disk, network)
    .first(new Func1() {
      @Override public Boolean call(Data data) {
        return data.isUpToDate();
      }
    })
场景四:合并两个数据源场景

使用merge合并两个数据源

 Observable.merge(getInfoFromFile(), getInfoFromNet())  
           .observeOn(AndroidSchedulers.mainThread())  
              .subscribe(new Subscriber<String>() {  
                  @Override  
                  public void onCompleted() {  
                     Log.d(TAG, "onCompleted: onCompleted");
                 }  

                 @Override  
                  public void onError(Throwable e) {  
                    Log.d(TAG, "onError: onError");
                  }  

                  @Override  
                  public void onNext(String data) {  
                       Log.d(TAG, "onNext: only one ! ");
             });
场景五:Retrofit结合RxJava场景

这个就太通用了,简单帖贴

subscription = Network.getApi()
                .CheckVersion(RequestMap)//检查版本是否有更新
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
场景六:控制输入的频繁度

如下图:我们需要动态搜索内容,输入框一改变就需要网络请求。但是过于频繁的网络请求就不是太友好了。


Paste_Image.png
rxjava为我们提供了一个很好的解决方案:
  • 使用debounce作为textSearch

debounce()函数过滤掉由Observable发射的速率过快的数据;如果在一个指定的时间间隔过去了仍旧没有发射一个,那么它将发射最后的那个。
debounce()使用TimeUnit对象指定时间间隔。

            RxTextView.textChanges(editText)
                .debounce(5000,TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<CharSequence>() {
                    @Override
                    public void onCompleted() {
                        Log.d(TAG, "onCompleted: onCompleted");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "onError: onError");
                    }

                    @Override
                    public void onNext(CharSequence charSequence) {
                        Log.d(TAG, "onNext: "+charSequence.toString());
                    }
                });

在这5s内,我输入了2,3,4,5(出最后一个5,其他输入之后就删除哈),但是最后得到的结果却是:

onNext: 5
场景七:防止按钮重复点击
Button button = (Button) findViewById(R.id.button);

        RxView.clicks(button).subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {
              Log.i("test", "clicked");
            }
        });

----------------------------
//将上面改成如下
//过滤掉了在300ms内的重复点击
RxView.clicks(button).debounce(300, TimeUnit.MILLISECONDS).subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {
                Log.i("test", "clicked");
            }
        });
场景七:轮询,定时操作

在做App的时候,有些地方我们可能会时不时的去请求服务器,以至于客户端的数据是最新的,在RxJava中可以这样做

//每隔两秒执行一次
   Observable.interval(2, 2, TimeUnit.SECONDS).subscribe(new Action1<Long>() {
            @Override
            public void call(Long aLong) {
                //TODO WHAT YOU WANT
            }
        });

在两秒后去执行一些操作(比如启动页跳转到主页面)

 Observable.timer(2, TimeUnit.SECONDS).subscribe(new Action1<Long>() {
            @Override
            public void call(Long aLong) {
                //TODO WHAT YOU WANT
            }
        });

###########场景八:能监听网络连接的广播自动重试,对网络无连接的情况不进行重试,并且重试有超时机制与重试间隔

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

推荐阅读更多精彩内容