Hot Observble和Cold Observable
- 一、概述
- [二、Cold Observable](#二、Cold Observable)
- [三、Hot Observable](#三、Hot Observable)
一、概述
在RxJava中,Observable有Hot和Cold之分。
Hot Observable无论有没有观察者进行订阅,事件始终都会发生。当Hot Observable有多个订阅者时(多个观察者进行订阅时),Hot Observable与订阅者们的关系是一对多的关系,可以与多个订阅者共享信息。
Cold Observable是只有观察者订阅了,才开始执行发射数据流的代码。并且ColdObservable和Observer只能是一对一的关系。当有多个不同的订阅时,消息是重新完整发送的。也就是说,对Cold Observable而言,有多个Observer的时候,它们各自的事件是独立的。
下面更加形象地说明Cold和Hot的区别:
- 把一个Hot Observable想象成一个广播电台,所有在此刻收听的听众都会听到同一首歌。
- 而Cold Observable是一张音乐CD,人们可以独立购买并听取它。
| 特性 | Cold Observable | Hot Observable |
|---|---|---|
| 数据源 | 惰性/延迟的。每个订阅者会触发独立的数据流生产。 | 活跃/独立 的。数据生产独立于订阅者。 |
| 类比 | 流媒体音乐/视频(每次点播重头播放)。 | 电台/电视直播(不管何时打开,都从当前点开始收听)。 |
| 订阅时机影响 | 不影响数据完整性。每个订阅者收到完整的数据序列。 | 至关重要。后订阅的会错过之前已发射的数据。 |
| 典型创建方式 | Observable.just , fromIterable , create (大部分操作符) | ConnectableObservable 及其变体(如 publish ) |
| 数据共享 | 不共享,除非使用操作符转换为 Hot。 | 共享 同一个数据源和发射序列。 |
二、Cold Observable
- 核心特征:对每个订阅者,都从头开始独立地执行数据发射序列。
- 工作原理 :
- 数据生产逻辑(例如,从磁盘读取文件、网络请求、遍历集合)嵌入在Observable内部
- 每次调用subscribe()时,都会触发这个逻辑的执行,为这个订阅者创建一个全新的、独立的数据流通道
- 订阅者之间互不干扰
- 典型来源:
- Observable.just(), fromIterable(), fromArray()
- Observable.create()(大部分实现)
- Observable.fromCallable()
- 从静态数据转换而来的流
java
@Test
public void testColdObservable() {
//一个Cold Observable
Observable<Integer> cold = Observable.create(emitter -> {
System.out.println("开始生产数据");
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
emitter.onComplete();
});
System.out.println("第一次订阅");
cold.subscribe(i -> System.out.println("A:" + i));
System.out.println("第二次订阅");
cold.subscribe(i -> System.out.println("B:" + i));
}
第一次订阅
开始生产数据
A:1
A:2
A:3
第二次订阅
开始生产数据
B:1
B:2
B:3
三、Hot Observable
- 核心特征 :
- 主动生产(Active):数据生产独立于订阅状态。它可能在生产数据时还没有订阅者。
- 共享执行(Shared):所有订阅者共享同一个数据生产过程。
- 数据共享:订阅者只能收到订阅之后发出的数据,无法获取订阅之前的历史数据(除非使用特殊的操作符缓存)。
- 类比:就像 广播电台 或 直播电视。电台一直在播放(主动生产),你什么时候打开收音机(订阅),就从那个时间点开始听,听不到之前播放的内容。
3.1、典型创建方式
在 RxJava 2 中,通常通过将 Cold Observable "热化" 来创建 Hot Observable:
1. ConnectableObservable与 publish()+ connect()
java
Observable<Integer> cold = Observable.range(1, 3).doOnSubscribe(d -> System.out.println("有订阅"));
ConnectableObservable<Integer> hot = cold.publish(); // 将其转换为可连接的热源
hot.subscribe(i -> System.out.println("A: " + i));
System.out.println("订阅A完成,但还未连接,无输出");
hot.connect(); // 手动启动数据生产!这是关键
hot.subscribe(i -> System.out.println("B: " + i)); // B订阅晚了,错过了数据1
/* 可能输出:
有订阅
A: 1
A: 2
A: 3
*/
// B可能什么也收不到,因为生产在它订阅前可能已结束
2. Subjects
Subject同时是 Observer和 Observable,是天然的热源。你可以通过onNext()手动向其推送数据。
java
PublishSubject<String> subject = PublishSubject.create();
// 这是热的:数据发射与订阅无关
subject.onNext("数据1"); // 此时无订阅者,数据丢失
subject.subscribe(s -> System.out.println("收到: " + s));
subject.onNext("数据2"); // 订阅者会收到
subject.onNext("数据3");
3.2、refCount()
refCount()是 ConnectableObservable的一个方法。它返回一个普通的 Observable。
- 核心行为 :引用计数。
- 当返回的 Observable收到第一个订阅时,它自动调用上游 ConnectableObservable的 connect()方法,开始数据生产。
- 它会追踪活跃订阅者的数量(引用计数)。
- 当最后一个订阅者取消订阅(onComplete或 onError也会导致取消)时,它会自动断开与上游的连接。
- 如果之后又有新的订阅者,它会重新开始这个循环(重新连接,重新开始数据生产)。
- 效果:它创建了一个 "有订阅则活,无订阅则死"的共享热源。资源在需要时被分配,在无人使用时被释放。
java
Observable<Long> source = Observable.interval(1, TimeUnit.SECONDS)
.doOnSubscribe(d -> System.out.println("上游被订阅(连接)"))
.doOnDispose(() -> System.out.println("上游被取消订阅(断开)"))
.publish() // 变为 ConnectableObservable
.refCount(); // 添加引用计数逻辑
System.out.println("第一次订阅开始");
Disposable d1 = source.subscribe(i -> System.out.println("订阅者1: " + i));
Thread.sleep(2500); // 输出: 上游被订阅(连接), 订阅者1: 0, 订阅者1: 1
System.out.println("\n第二次订阅开始");
Disposable d2 = source.subscribe(i -> System.out.println("订阅者2: " + i));
Thread.sleep(2000); // 输出: 订阅者1: 2, 订阅者2: 2, 订阅者1: 3, 订阅者2: 3
System.out.println("\n第一次订阅取消");
d1.dispose(); // 只取消一个订阅者
Thread.sleep(2000); // 上游仍在运行!输出: 订阅者2: 4, 订阅者2: 5
System.out.println("\n第二次订阅取消");
d2.dispose(); // 最后一个订阅者离开
Thread.sleep(1000); // 输出: 上游被取消订阅(断开), 数据生产停止
3.3、share()
hare()是一个快捷操作符,通常直接作用于普通 Observable。
- 定义:在 RxJava 中,share()通常等价于 publish().refCount()。
java
// 这两行代码在功能上是等价的
Observable.sharedStream = source.publish().refCount();
Observable.sharedStream = source.share();
- 核心行为 :它结合了 publish()和 refCount()的所有特性。
- 将上游转换为一个共享的热源。
- 在有第一个订阅者时自动连接。
- 在最后一个订阅者离开时自动断开。
- 如果断开后又有新订阅者,会重新建立连接,数据流会从 - 头开始(对于 interval这类操作,就是重新从0计数)。
java
Observable<Long> shared = Observable.interval(1, TimeUnit.SECONDS)
.doOnSubscribe(d -> System.out.println("连接建立"))
.share(); // 关键在这里!
Disposable d1 = shared.subscribe(i -> System.out.println("A: " + i));
Thread.sleep(2500); // 输出: 连接建立, A:0, A:1
Disposable d2 = shared.subscribe(i -> System.out.println("B: " + i));
Thread.sleep(2000); // 输出: A:2, B:2, A:3, B:3 (B错过了0,1)
d1.dispose();
d2.dispose(); // 最后一个订阅者离开,连接断开
Thread.sleep(1500); // 无输出,生产已停止
// 重新订阅,会建立新的连接,数据流重新开始
shared.subscribe(i -> System.out.println("C: " + i));
Thread.sleep(2500); // 输出: 连接建立, C:0, C:1 (重新从0开始)