RxJava初探

来源:互联网 发布:东三环java培训 编辑:程序博客网 时间:2024/05/01 21:12

  • 目录
    • - 1 - Lambda
      • - 1-1 那么什么是Lambda呢
      • - 1-2 Lambda的基础语法
      • - 1-3 Lambda的简单使用
    • - 2 - RxJava
      • - 2-1 什么是RxJava
        • - 2-1-1 什么是观察者模式
        • - 2-1-2 观察者模式的不足
        • - 2-1-3 RxJava的引入
        • - 2-1-4 进入正题
      • - 2-2 RxJava的基本用法
        • - 2-2-1 如何创建被观察者Observable
        • - 2-2-2 如何创建观察者Observer
        • - 2-2-3 使用操作符对发送事件进行变换列在此处只为了说明RxJava完整的使用的流程内容在23进行讲解
        • - 2-2-4 订阅Subscribe
      • - 2-3 RxJava的常用操作符
        • - 2-3-1 变换操作符
          • Map操作符
          • flatMap与concatMap操作符
        • - 2-3-2 线程相关操作符
          • observeOn与subscribeOn操作符
        • - 2-3-3 过滤操作符
          • Filter与distinct操作符
        • - 2-3-4 聚合操作符
          • Merge和zip操作符
    • - 3 - RxJava相关

目录

- 1 - Lambda

  在正式开始认识RxJava之前,我们先来学习一下Java8的一个新特性Lambda,因为RxJava+Lambda才能将RxJava的优势发挥到最大。

- 1-1 那么什么是Lambda呢?

  用来简化函数式接口(只包含唯一一个抽象方法的接口)的一个工具。郑重声明:这并非官方描述,只是我个人的一点理解。
  下面我们先来看两条使用了Lambda特性的代码:

button.setOnClickListener(v -> v.setText(“Hello Lambda"));Runnable runnable = () -> doSomething();

  虽然写法是很简洁,但是是不是感觉看到这样的代码略感懵逼呢?没关系,接下来我就来和大家一块儿学习一下Lambda的一些基础语法。

- 1-2 Lambda的基础语法

基本格式:(参数列表)->{代码块}
语法规则:
    1.参数类型可推导时,不需要指定参数类型;
    2.当只有一个参数且类型可推导时,不强制写 ();
    3.参数指定类型时,必须有括号,如 (int a) -> System.out.println(a);
    4.参数可以为空,但是括号不能省,如 () -> System.out.println(“Hi!”);
    5.代码块需要用 {}包裹,当代码块只有一条语句时 {} 可省略;
我这里只是简单说了一点接下来要用到的Lambda的语法规则,若想要更加详细的学习一下Lambda的用法,请移步:Java8 特性详解(一) Lambda
  说了这么多条条框框的东西还是不如用一下理解的清楚,接下来就跟我一起来看一个小栗子。

- 1-3 Lambda的简单使用

字符串按长度排序①(未使用Lambda)

public static List<String> compareTest1(){    wordList.sort(new Comparator<String>() {        @Override        public int compare(String w1, String w2) {            return Integer.compare(w1.length(), w2.length());        }    });    return wordList;        }

②按照最基本的Lambda简化语法提取出参数列表和代码块简化后的代码:

public static List<String> compareTest2(){    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn","winter");    wordList.sort((String w1, String w2) -> {        return Integer.compare(w1.length(), w2.length());    });    return wordList;        }

③如果参数列表中参数的类型可以根据上下文推导出来,就可以将参数列表的类型省略掉,简化后的代码:

public static List<String> compareTest3(){    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn","winter");    wordList.sort((w1, w2) -> {        return Integer.compare(w1.length(), w2.length());     });    return wordList;        }

④如果一个lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化,将return省略,又因为代码块中只有一条语句时,可以省略代码块外面的大括号,所以最终简化版代码为:

public static List<String> compareTest4(){    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn","winter");    wordList.sort((w1, w2) -> Integer.compare(w1.length(), w2.length()));    return wordList;        }

- 2 - RxJava

- 2-1 什么是RxJava?

  在解决这个是什么的问题之前,我们先来了解一下观察者模式,这毕竟是RxJava始祖级别的东西,学习它能帮助我们更好的理解RxJava。

- 2-1-1 什么是观察者模式?

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
文字性的东西还是有点晦涩,接下来放张图解释一下:
观察者模式的三大要素之间的关系
观察者模式包含三大要素:主题(图中的Subject,即被观察者)、观察者(图中的Observer)、订阅。
  通常一个自定义的被观察者(Subject)中都必须要实现Attach(),Detach(),Notify()三个方法。一个自定义的观察者(Observer)必须要实现一个Update()方法,接下来我们就来说一下这几个方法都是用来干嘛的。
  Attach()方法:用来将观察者与被观察者绑定,使二者产生订阅关系。
  Detach()方法:用来将被观察者与观察者解除绑定,取消订阅关系。
  Notify()方法:用于提醒订阅这个被观察者的所有观察者产生新消息了。
  Update()方法:当观察者收到自己订阅的被观察者产生新消息的提示时,自己进行状态的更新。
  由于个人表达能力有限,特以某设计模式书中的几页讲解来帮助大家理解观察者模式。下面的两种图表述了一个完整的简单观察者模式的工作方式。
这里写图片描述
这里写图片描述

- 2-1-2 观察者模式的不足

经过上面的讲解,大家可能有下面这么几点困惑:
  1.不知事件是何时结束的;
  2.缺少错误通知机制;

- 2-1-3 RxJava的引入

  RxJava的创造者见到如此好用又低耦合的观察者模式有这么大的两个缺陷,心有不甘,于是决心要对观察者模式进行改进,于是RxJava就这么诞生了。接下来我们看看RxJava对观察者模式做了哪些改进:
  1. 添加onCompleted()方法,用于在事件结束时调用,被观察者可以在事件发送结束时,调用该方法通知观察者事件发送结束了;
  2. 添加onError()方法,此方法在事件传送过程中抛出异常时执行,对异常进行处理;
  3. 引入了很多的操作符,用于在发送事件过程中对事件做各种灵活的处理;

- 2-1-4 进入正题

GitHub上对RxJava的专业介绍:

  A library for composing asynchronous and event-based programs using observable sequences for the Java VM“(一个在 JVM 上使用可观测的序列来组成异步的、基于事件的程序的库)。

简言之,RxJava = 改进后的观察者模式 + 异步执行

- 2-2 RxJava的基本用法

- 2-2-1 如何创建被观察者(Observable)?

被观察者:决定什么时候触发事件以及触发怎样的事件。
方式1(create):

Observable observable = Observable.create(new Observable.OnSubscribe() {              @Override              public void call(Subscriber o) {                  //触发的事件写在这里                  subscriber.onNext("1");                  subscriber.onNext("2");                  subscriber.onCompleted();              }          });  

方式2(just):

Observable observable = Observable.just("1","2");

方式3(from):

String [] event = {"1","2"};  Observable observable = Observable.from(event);

  当Observable被订阅时,OnSubscribe的call()方法会自动被调用,事件序列就会依照设定依次触发,对于上面的代码,观察者将会调用两次onNext()和一次onCompleted()。

- 2-2-2 如何创建观察者(Observer)?

观察者:决定着事件触发的时候将怎样处理。
方式1:

Observer<T> observer = new Observer<T>() {            @Override            public void onCompleted() {}            @Override            public void onError(Throwable e) {}            @Override            public void onNext(T s) {}        };

方式2:

Subscriber<T> subscriber = new Subscriber<T>() {            @Override            public void onCompleted() {}            @Override            public void onError(Throwable e) {}            @Override            public void onNext(T t) {}        };

  方式1和方式2的区别:Subscriber是一个实现了Observer接口的抽象方法,在Subscriber中对Observer接口进行了一些扩展,添加了onStart(),unsubscribe(),isUnsubscribed()三个方法,功能也更加强大,而且程序在subscribe(订阅)时Observer也总是先被转换成一个Subscriber对象再使用,因此建议使用方式二的方式来创建观察者。
  onStart()方法:用于在事件执行前进行一些初始化操作。
  isUnsubscribed()方法:用于判断当前的观察者和被观察者之间是否还存在订阅关系。
  unsubscribe()方法:用于取消观察者和被观察者之间的订阅关系,在不需要订阅时,及时终止订阅关系可以防止内存泄漏,此方法不要在onCompleted()或者生命周期的onDestroy()方法中调用,因为我们在创建被观察者对象时,传入的Subsrciber或者Observer类的对象都会被转化成SafeSubscriber类的对象,SafeSubscriber类在调用onCompleted()方法时,会执行unsubscribe(),取消观察者和被观察者之间的订阅关系,因此,如果我们想要手动调用unsubscribe()方法时,应该在执行oncompleted()方法执行之前使用,而且在使用之前,请先采用isUnsubscribed()方法判断是否订阅关系还存在,再使用,否则程序会crash掉;
方式3:采用不完全回调接口(Action1和Action0)的方式

subscribe(new Action1<String>() {                            @Override                            public void call(String string) {                                s += "map->onNext:" + string + "\n";                            }                        }, new Action1<Throwable>() {                            @Override                            public void call(Throwable e) {                                s += "map->onError:" + e.getMessage();                                onShow(s);                            }                        }, new Action0() {                            @Override                            public void call() {                                s += "map->onCompleted:run completed";                                onShow(s);                            }                        });    }

- 2-2-3 使用操作符对发送事件进行变换(列在此处只为了说明RxJava完整的使用的流程,内容在2.3进行讲解)

- 2-2-4 订阅(Subscribe)

方式1:

observable.subscribe(observer);

方式2:

observable.subscribe(subscriber);

- 2-3 RxJava的常用操作符

- 2-3-1 变换操作符

Map操作符

  Map是将一种对象,转换成另一种对象的操作符,简而言之,就是用于对事件进行一对一转换的操作符。
这里写图片描述
  官方文档上的图画的很通俗易懂map就是将一种圆形的事件转换成了一种菱形的事件。
  接下来还是来上个小栗子,看一下map操作符是如何使用的:
  
1.不使用Lambda特性的代码

private void Test1() {        subscription =                Observable.just("java", "C", "C++")                        .map(s1 -> s1 +"Hi,")                        .subscribe(new Action1<String>() {                            @Override                            public void call(String string) {                                s += "map->onNext:" + string + "\n";                            }                        }, new Action1<Throwable>() {                            @Override                            public void call(Throwable e) {                                s += "map->onError:" + e.getMessage();                                onShow(s);                            }                        }, new Action0() {                            @Override                            public void call() {                                s += "map->onCompleted:run completed";                                onShow(s);                            }                        });    }

2.使用Lambda特性的代码

private void Test2() {        subscription =                Observable.just("java", "C", "C++")                .map(s -> "Hi," + s)                .subscribe(string -> s += "map->onNext:" + string + "\n",//onNext()方法                        e -> {                          //onError()方法                            s += "map->onError:"+e.getMessage();                            onShow(s);                        },                        () -> {                                         //onCompleted()方法                            s += "map->onCompleted:run completed";                            onShow(s);                        });    }

运行结果:

map->onNext:Hi,javamap->onNext:Hi,Cmap->onNext:Hi,C++map->onCompleted:run completed

  从这两段代码可以看出,Lambda极大地简化的代码的书写,但是理解起来稍微有些不太容易,不过用多了熟悉了应该也就看起来很容易理解了。Lambda虽好,但也不能滥用。
3.另一种形式的代码

private void Test3() {        subscription =                Observable.just("java", "C", "C++")                        .map(new Func1<String, String>() {                            @Override                            public String call(String s) {                                return "Hi," + s;                            }                        })                        .subscribe(new Subscriber<String>() {                            @Override                            public void onCompleted() {                                s += "map->onCompleted:run completed";                                onShow(s);                            }                            @Override                            public void onError(Throwable e) {                                s += "map->onError:"+e.getMessage();                                onShow(s);                            }                            @Override                            public void onNext(String string) {                                s +=  "map->onNext:"+string+"\n";                            }                        });    }

  大家觉得是第三段代码可以使用Lambda转化成第二段代码的形式,还是第一段代码可以转化成第二段代码的形式,还是都可以?
  正确答案是:第一段代码可以使用Lambda转化成第二段代码的形式,因为Action1,Action0是函数式接口,而Subscriber并不是函数式接口。
Action1源码:

/** * A one-argument action. * @param <T> the first argument type */public interface Action1<T> extends Action {    void call(T t);}

Action0源码:

/** * A zero-argument action. */public interface Action0 extends Action {    void call();}

Subscriber源码:

public abstract class Subscriber<T> implements Observer<T>, Subscription {    ...    public final void add(Subscription s) {        subscriptions.add(s);    }    @Override    public final void unsubscribe() {        subscriptions.unsubscribe();    }    @Override    public final boolean isUnsubscribed() {        return subscriptions.isUnsubscribed();    }    public void onStart() {        // do nothing by default    }}
flatMap与concatMap操作符

  FlatMap将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据合并后放进一个单独的Observable,再将这些数据依次发送给Observer,简单来说,这个操作符是一个用于一对多转换的操作符,注意这个操作符的顺序可能不会遵循原来的发送顺序。
  这里写图片描述
  将一个圆形的事件转换成一个菱形和一个矩形的事件,从图中的描述可以看出圆形绿色和圆形蓝色两个事件转换后的事件是交叉发送的,因此这个操作符并不一定完全遵循原来的发送顺序,但是我在测试中并没有测试出来这种效果,但是大家在用的时候也一定要注意。
  concatMap的作用和FlatMap是一样的,不过它发送数据的顺序是按照原来的顺序发送的。
  这里写图片描述
举个栗子:

public class TestFlatAndConcatMap extends Activity{    private EditText ed;    private List<List<Integer>> datas;    private String s1 = "";    private List<Integer> data;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.testflatconcat_layout);        ed = (EditText) findViewById(R.id.id_test_flat_concat_ed);        datas = new ArrayList<>();        for(int i = 0;i < 20;i++) {            data = new ArrayList<>();            for(int j = 1;j < 4;j++){                data.add(3*i+j);            }            datas.add(data);        }        TestConcatMap();    }    private void TestFlatMap(){        Observable                .from(datas)                .flatMap(new Func1<List<Integer>, Observable<Integer>>() {                        @Override                        public Observable<Integer> call(List<Integer> integers) {                            return Observable.from(integers);                        }                    })                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread())                .subscribe(new Action1<Integer>() {            @Override            public void call(Integer integer) {                s1 += "flatmap->onNext:" + integer + "\n";                onShow(s1);            }        });    }    private void TestFlatMap1(){        Observable                .from(datas)                .flatMap(integers -> Observable.from(integers))                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread())                .subscribe(integer -> {                    s1 += "flatmap->onNext:" + integer + "\n";                    onShow(s1);                });    }    private void TestConcatMap(){        Observable.from(datas)                .concatMap(integers -> Observable.from(integers))                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread())                .subscribe(integer -> {                    s1 += "Concatmap->onNext:" + integer + "\n";                    onShow(s1);                });    }    private void onShow(String str) {        ed.setText(str);    }}

运行结果:

flatmap->onNext:1flatmap->onNext:2flatmap->onNext:3flatmap->onNext:4flatmap->onNext:5...flatmap->onNext:56flatmap->onNext:57flatmap->onNext:58flatmap->onNext:59flatmap->onNext:60Concatmap->onNext:1Concatmap->onNext:2Concatmap->onNext:3Concatmap->onNext:4Concatmap->onNext:5...Concatmap->onNext:56Concatmap->onNext:57Concatmap->onNext:58Concatmap->onNext:59Concatmap->onNext:60

- 2-3-2 线程相关操作符

observeOn与subscribeOn操作符

  subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
这里写图片描述
  subscribeOn()将事件产生的线程变为黄颜色的线程。
  observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。
这里写图片描述
  observeOn()将事件消费的线程指定为黄色线程,而事件产生的线程仍然在黑色线程上。
  observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber 并不是(严格说应该为『不一定是』,但这里不妨理解为『不是』)subscribe() 参数中的 Subscriber ,而是 observeOn() 执行时的当前 Observable 所对应的 Subscriber ,即它的直接下级 Subscriber 。换句话说,observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次 observeOn() 即可。
上代码:

Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定    .subscribeOn(Schedulers.io())    .observeOn(Schedulers.newThread())    .map(mapOperator) // 新线程,由 observeOn() 指定    .observeOn(Schedulers.io())    .map(mapOperator2) // IO 线程,由 observeOn() 指定    .observeOn(AndroidSchedulers.mainThread)     .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

  接下来我们来看看RxJava的线程调度器中给我提供了哪些可以直接使用的线程类型:
  Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。
  Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
  Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
  Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
  AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行,这个方法也是现在RxAndroid唯一的一个功能。

- 2-3-3 过滤操作符

Filter与distinct操作符

Filter操作符用于按照指定条件对发送的数据进行过滤,类似于执行了一条if语句;
这里写图片描述
将大于10的事件发送出去
  Distinct操作符用于去掉发送的重复数据;
这里写图片描述
这两个操作符比较简单直接上代码:

public class TestFilterAndDistinct extends Activity{    private EditText ed;    private String s1 = "";    private Integer [] data = new Integer[]{3,12,9,6,22,33,4,2,100,33,12};    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.testfilter_layout);        ed = (EditText) findViewById(R.id.id_test_filter_ed);        Test1();    }    private void Test1(){        Observable.from(data)                .filter(new Func1<Integer, Boolean>() {                    @Override                    public Boolean call(Integer integer) {                        return integer > 10;                    }                })                .distinct()                .subscribe(new Action1<Integer>() {                    @Override                    public void call(Integer integer) {                        s1 += "filter->onNext:" + integer + "\n";                        onShow(s1);                    }                });    }    private void Test2(){        Observable.from(data)                .filter(integer -> integer > 10)                .distinct()                .subscribe(integer -> {                    s1 += "filter->onNext:" + integer + "\n";                    onShow(s1);                });    }    private void onShow(String str) {        ed.setText(str);    }}

执行结果:

filter->onNext:12filter->onNext:22filter->onNext:33filter->onNext:100

- 2-3-4 聚合操作符

Merge和zip操作符

  Merge操作符用于将多个数据源的数据合并起来作为一个数据源的数据依次发送;
  这里写图片描述
第一个数据源是:20,40,60,80,100
第二个数据源是:1,1
使用merge合并之后:20,40,60,1,80,100,1
由合并之后的数据源来看,merge的合并操作并不是遵循原顺序的。
  Zip操作符用于将多个数据源的对应位置的数据进行某些操作后,将操作后的数据依次发送;
  这里写图片描述
最后一个例子:

public class TestMergeAndZip extends Activity {    private EditText ed;    private String s1 = "";    private String [] data1 = new String[]{"Hi,","Hello,"};    private String [] data2 = new String[]{"JAVA","C++","C"};    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.testmergezip_layout);        ed = (EditText) findViewById(R.id.id_test_merge_zip_ed);        TestMerge1();        TestZip2();    }    private void TestMerge1(){        Observable                .merge(Observable.from(data1),Observable.from(data2))                .subscribe(new Action1<String>() {                    @Override                    public void call(String s) {                        s1 += "merge->onNext:" + s + "\n";                        onShow(s1);                    }                });    }    private void TestMerge2(){        Observable                .merge(Observable.from(data1),Observable.from(data2))                .subscribe(s -> {                    s1 += "merge->onNext:" + s + "\n";                    onShow(s1);                });    }    private void TestZip1(){        Observable                .zip(Observable.from(data1), Observable.from(data2), new Func2<String, String, String>() {                        @Override                        public String call(String s, String s2) {                            return s+s2;                        }                    })                .subscribe(new Action1<String>() {                    @Override                    public void call(String s) {                        s1 += "zip->onNext:" + s + "\n";                        onShow(s1);                    }                });    }    private void TestZip2(){        Observable                .zip(Observable.from(data1), Observable.from(data2), (s,s2) -> s+s2)                .subscribe(s -> {                    s1 += "zip->onNext:" + s + "\n";                    onShow(s1);                });    }    private void onShow(String str) {        ed.setText(str);    }}

执行结果:

merge->onNext:Hi,merge->onNext:Hello,merge->onNext:JAVAmerge->onNext:C++merge->onNext:Czip->onNext:Hi,JAVAzip->onNext:Hello,C++

- 3 - RxJava相关

  由于我目前只是一个RxJava的初学者,所以我看到的很多关于RxJava比较好的东西,我并没有能力在我的博客中写出来,所以我在此附上原文链接,以供有能力,对RxJava感兴趣的童鞋进一步深入的学习RxJava,由于本人水平有限,文中难免出现错误,如果文中有表述不对的地方,还望大家指正。
官方中文文档
关于RxJava最友好的文章
Hi大头鬼的博客
给 Android 开发者的 RxJava 详解(强力推荐)
RxJava学习总结
技术男的后花园RxJava专栏
关于RxJava最友好的文章(进阶)
拆轮子系列:拆 RxJava

0 0