RxJava——Hot Observable和Cold Observable

Hot Observble和Cold 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开始)
相关推荐
dreams_dream3 小时前
MySQL 主从复制(小白友好 + 企业级)
android·数据库·mysql
城东米粉儿3 小时前
Android PLT Hook 笔记
android
城东米粉儿4 小时前
leakcanary原理
android
龙之叶4 小时前
Android ADB Shell 常用命令
android·adb
城东米粉儿5 小时前
Android 图片内存问题分析、定位
android
之歆5 小时前
MySQL 主从复制完全指南
android·mysql·adb
独行soc6 小时前
2026年渗透测试面试题总结-25(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
城东米粉儿7 小时前
Android KOOM 笔记
android
城东米粉儿7 小时前
android 内存优化笔记
android