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开始)
相关推荐
kymjs张涛6 小时前
OpenClaw 学习小组:初识
android·linux·人工智能
范特西林10 小时前
实战演练——从零实现一个高性能 Binder 服务
android
范特西林10 小时前
代码的生成:AIDL 编译器与 Parcel 的序列化艺术
android
范特西林10 小时前
深入内核:Binder 驱动的内存管理与事务调度
android
范特西林11 小时前
解剖麻雀:Binder 通信的整体架构全景图
android
范特西林11 小时前
破冰之旅:为什么 Android 选择了 Binder?
android
奔跑中的蜗牛66613 小时前
一次播放器架构升级:Android 直播间 ANR 下降 60%
android
测试工坊15 小时前
Android 视频播放卡顿检测——帧率之外的第二战场
android
Kapaseker16 小时前
一杯美式深入理解 data class
android·kotlin
鹏多多16 小时前
Flutter使用screenshot进行截屏和截长图以及分享保存的全流程指南
android·前端·flutter