什么是响应式编程?
响应式编程是一种编程范式,它专注于异步数据流和变化传播。响应式编程的核心思想是“数据流是第一等公民”,程序的逻辑建立在数据流的变化之上。
响应式编程的几个核心概念:
数据流:响应式编程中,数据以流(Stream)的形式存在。流就像一条河,源源不断、有一个流向(比如从A系统到B系统再到C系统),它可以被过滤、观测、或者跟另一条河流合并成一个新的流。
比如用户输入、网络请求、文件读取都可以是数据流,可以很轻松的对流进行处理。
比如Java8的Stream API,下列代码中将数据作为一个流,依次进行过滤、转换、汇聚。
list.stream()
.filter() // intermediate operation
.map() // intermediate operation
.collect(); // terminal operation
异步处理:
响应式编程是异步的,即操作不会阻塞线程,而是通过回调或其他机制在未来某个时间点处理结果。这提高了应用的响应性和性能。
变化传播:
当数据源发送变化时,响应式编程模型会自动将变化传播到依赖这些数据源的地方。这种传播时自动的,不需要显式调用。
举个栗子,有一支股票涨了,所有订阅了这只股票的人,都会同时收到APP的通知,不用你自己时刻在APP上盯着看。
注意:响应式编程更倾向于声明式编程风格,通过定义数据流的转换和组合来实现复杂的逻辑。比如。可以利用filter、map等函数来实现数据转换,而不是将一大堆复杂的逻辑混杂在一个代码块中。
什么是RxJava?
RxJava是一个基于事件驱动的、利用可观测序列来实现异步编程的类库,是响应式编程在Java语言上的实现。
1.事件驱动:
事件可以是任何事情,如用户的点击操作、网络请求的结果、文件的读写等。事件驱动的编程模型是通过事件触发行动。
比如前端开发中,用户点击按钮后会进行弹窗,这就是“点击事件”触发了“弹窗行动”
// 前端按钮点击
btn.onClick(()->
// 弹窗
showModal();
)
在RxJava中,事件可以被看做是数据流中的数据项,称为“事件流”或”数据流“。每当一个事件发生,这个事件就会被推送给那些对它感兴趣的观察者(Observers)。
2.可观测序列
可观测序列是只一系列按照事件顺序发出的数据项,可以被观察和处理。可观测序列提供了一种将数据流和异步事件建模为一些列可以订阅和操作的事件的方式。
可以理解为在数据流的基础上封装了一层,多加了一点方法。
RxJava应用场景
它的一个核心应用场景就是UI场景,向Android开发都会用到RxJava。
UI场景天然涉及到响应和事件这两点,比如我们在手机app上某个按钮,对应app就会弹出某个界面,点击按钮其实就是一个事件,那么弹出界面就是对应的响应。
// 安装异步后台执行任务,完成后更新UI
new AsyncTask<Void.Void,ResultType>(){
protected ResultType doInBackground(Void...params){
// 在后台线程中执行的任务
return result;
}
protected void onPostExecute(ResultType result){
// doInBackground完成后执行的回调,用于更新UI
}
}.execute();
而RxJava给予我们一个统一的异步接口形式,提供链式编程、丰富的操作符让我们在面对复杂的业务场景编写的代码也异常简单。
下面的代码直观感受一下RxJava带来的编程便利性
Observable.from(folders)
.flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
.filter((Func1) (file) -> { file.getNmae().endsWith(".png") })
.map((Func1) (file) -> { getBitmapFromFile(file) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(Action1) (bitmap) -> { imageCollectiorView.addImage(bitmap) });
.subscribeOn(Schedulers.io())这行代码设置执行上游Observable即Observable.from(folders)及其后续操作(合并文件夹、查找png图片,转成bitmap)到这行代码之前的逻辑在一个适用于I/O操作的线程池中执行,即子线程中。
.observeOn(AndroidSchedulers.mainThread())这行代码指定了下游Observable(即subscrible方法及其之前的操作),在Android的主线程上执行更新UI。
.subscribe(Action1) (bitmap) -> { imageCollectiorView.addImage(bitmap) })这行代码是绑定观察者的操作,观察者针对被观察者的事件的响应动作是‘imageCollectiorView.addImage(bitmap)’。
以上是在安卓中实现的例子,它在子线程中实现了过来查找所有文件中的png图片,转成bitmap,之后再利用主线程显示图片。
因为安卓的主线程是需要事事响应用户的请求,如果它被利用去执行查找图片和转化png的过程,就会让app卡顿,影响用户体验,严重可能会导致无响应。
因此耗时的操作需要到子线程中操作,然后最终显示结果才需要利用主线程,上面的演示代码不仅业务逻辑处理便捷,线程的切换也非常简单。
RxJava的核心知识点
观察者模式
RxJava是基于观察者模式实现的,分别有观察者和被观察者两个角色,被观察者会实时传输数据流,观察者可以观测到这些数据流。
基于传输和观测的过程,用户可以通过一些操作方法对数据进行转换或其他处理。
在RxJava中,观察者就是Observer,被观察者是Observable和Flowable。
Observable适合处理相对较小的、可控的、不会迅速产生大量数据的场景。它不具备背压处理能力,也就是说,当数据生成速度超过数据消费速度时,可能会导致内存溢出或其他性能问题。
Flowable是针对背压(反向压力)问题而设计的可观测类型。背压问题出现于数据生成速度超过数据消费速度的场景。Flowable提供了多种背压策略来处理这种情况,确保系统在处理大量数据时仍然能够保持稳定。
被观察者.subscribe(观察者),它们之间就建立的订阅关系,被观察者传输的数据或者发出的事件会被观察者观察到。
常用操作符
前面提到用户可以通过一些方法对数据进行转换或其他处理,RxJava提供了很多操作符供我们使用,这块其实和Java8的Stream类似,概念上都是一样的。
操作符主要分为以下几大类:
1)变量类操作符,对数据流进行交换,如map、flatMap等。
比如利用map将int类型转换为string
Flowable<String> flowable = Flowable.range(0,Integer.MAX_VALUE)
.map(i -> String.valueof(i));
2)聚合类操作符,对数据流进行聚合,如toList、toMap等
将数据转成一个list
Flowable.range(0,Integer.MAX_VALUE)
.toList();
3) 过滤操作符,过滤或者跳过一些数据,如filter、skip等
将大于10的数据转成一个list
Flowable.range(0,Integer.MAX_VALUE)
.filter(i -> i > 10)
.toList();
4) 连接操作符,将多个数据流连接到一起,如concat、zip等
创建两个Flowable,通过concat连接得到一个被观察者,进行统一处理
// 创建两个Flowable对象
Flowable<String> flowable1 = Flowable.just("A","B","C");
Flowable<String> flowable2 = Flowable.just("D","E","F");
// 使用concat操作符将两个Flowable合并
Flowable<String> flowable = Folwable.concat(flowable1 ,flowable2 );
5) 排序操作符,对数据流内的数据进行排序,如sorted
Flowable<String> flowable = Folwable.concat(flowable1 ,flowable2 )
.sorted();
事件
RxJava也是一个基于事件驱动的框架,我们来看看一共有哪些事件,分别在什么时候触发:
- onNext,被观察者每发送一次数据,就会触发此事件。
- onError,如果发送数据过程中产生意料之外的错误,那么被观察者可以发送此事件。
- onComplete,如果没有发生错误,那么被观察者在最后一次调用onNext之后发送此事件表示完成数据传输。
对应的观察者得到这些事件后,可以进行一定处理,例如:
flowable.observaOn(Schedulers.io())
.doOnNext(item -> {
System.out.println("来数据啦" + item.toString());
})
.doOnError(e -> {
System.out.println("出错啦" + e.getMessage());
})
.doOnComplete(() -> {
System.out.println("数据处理完毕啦");
}).subscribe();
Demo演示
引入依赖
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>1.0.4</version>
</dependency>
编写单元测试
public class RxJavaTest {
@Test
void rxJavaDemo() throws InterruptedException {
// 创建一个流,每秒发射一个递增的整数(数据流变化)
Flowable<Long> flowable = Flowable.interval(1, TimeUnit.SECONDS)
.map(i -> i + 1)
.subscribeOn(Schedulers.io());// 指定创建流的线程池
// 订阅Flowable流,并打印每个接收到的数字
flowable.observeOn(Schedulers.io())
.doOnNext(item -> System.out.println(item.toString()))
.subscribe();
// 让主线程睡眠,以便观察输出
Thread.sleep(10000L);
}
}
结果