1. 响应式编程与观察者模式
响应式编程 是一种基于数据流和变化传播的编程范式。RxJava 作为响应式编程库,其核心基于观察者模式。
异步数据流简介
-
可观察者(Observable/Flowable/Single/Maybe/Completable)
- 负责提供数据或者事件流。
-
订阅者(Observer/Subscriber/SingleObserver/MaybeObserver/CompletableObserver)
- 负责处理数据或者事件流。
Observable/Flowable/Single/Maybe/Completable 都是用来表示异步数据流的,但它们有各自的特点和使用场景。
-
Observable/Flowable/Single/Maybe/Completable:数据源,也就是数据流的生产者。- Observable:适用于数据流量较小或不需要背压(Backpressure)控制的场景。可以处理连续数据流。
- Flowable:专为数据流量大且可能出现背压问题的场景设计,它遵循 Reactive Streams 规范。可以处理连续数据流。
- Single:只会发送 一个数据(或错误),然后自动结束,没有
onComplete()回调。适用于只需要返回单个结果的操作,比如网络请求返回一个响应、从数据库中查询单条记录等。 - Maybe:可以发送 0 或 1 个数据,然后结束,或者发送错误,如果没有数据产生,则会回调 onComplete 而不是 onSuccess。适用场景:缓存中检索数据,若找到则返回,未找到则不发射数据直接完成。
- Completable:不发送任何数据,只关心“成功(完成)”或“失败(错误)”。仅执行一个动作,而不需要返回数据。只需要知道是否完成或者出错即可。适用场景:写文件、删数据库记录、日志上报、网络请求只关心结果状态等。
-
Observer / Subscriber:数据流的消费者。订阅者会接收数据流中依次发射的事件(onNext)、异常(onError)和完成通知(onComplete)。
-
Subscription/Disposable:用于管理订阅的生命周期,允许取消订阅,从而防止内存泄漏或不必要的资源占用。
异步数据流简介使用解析
1. Observable
-
数据项数量:
Observable可以发射 0 个、1 个或者多个数据项,然后结束或报错。
-
背压支持:
- 默认情况下,
Observable不支持背压(Backpressure)。当数据生产速度远快于消费速度时,可能会引起问题。
- 默认情况下,
-
使用场景:
- 适用于数据量较小或数据产生速度较慢的场景,比如 UI 事件流、简单的数据序列等。
-
示例:
Observable.just(1, 2, 3) .subscribe( { item -> println("Received: $item") }, { error -> println("Error: ${error.message}") }, { println("Completed") } )
2. Flowable
-
数据项数量:
- 与
Observable类似,Flowable也可以发射 0 个、1 个或者多个数据项。
- 与
-
背压支持:
Flowable是专为处理高速数据流和大数据量场景设计的,它内置了 背压机制。- 当生产者发射数据的速度超过消费者处理数据的能力时,可以通过
request()方法和各种背压策略(如BUFFER、DROP、LATEST、ERROR)来平衡数据流,防止内存溢出或资源耗尽。
-
使用场景:
- 适用于数据量大、数据产生速度快、可能引发背压问题的场景,例如从文件、网络或传感器中产生大量数据时。
-
示例:
Flowable.range(1, 1000) .onBackpressureBuffer() // 选择一个背压策略 .subscribe( { item -> println("Received: $item") }, { error -> println("Error: ${error.message}") } )
3. Single
-
数据项数量:
Single只能发射一个数据项,或者直接报错,比较适合网络请求。- 它不会发射多个数据,也没有 onComplete 事件(因为数据只有一个,onSuccess 表示成功完成)。
-
背压支持:
- 由于只涉及单个数据项,所以 不需要背压 控制。
-
使用场景:
- 适用于只需要返回单个结果的操作,比如网络请求返回一个响应、从数据库中查询单条记录等。
-
示例1:
Single.just("Hello, Single") .subscribe( { result -> println("Result: $result") }, { error -> println("Error: ${error.message}") } ) -
示例2:
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/") // 设置基础URL
.addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器
.addCallAdapterFactory(RxJava3CallAdapterFactory.createWithScheduler(Schedulers.io())) // 添加RxJava的Call适配器
.build()
val api: Api = retrofit.create(Api::class.java) // 创建Api接口实例
api.getRepos(username = "renguxian") // 调用获取仓库的方法
//.subscribeOn(Schedulers.io()) // addCallAdapterFactory完成了同意的后台线程设置,就不需要在这里再单独设置了。
.observeOn(AndroidSchedulers.mainThread()) // 在主线程上观察
.subscribe(object : SingleObserver<MutableList<Repo>> {
override fun onSuccess(repos: MutableList<Repo>) {
textView.text = "Result: ${repos[0].name}" // 显示第一个仓库的名称
}
override fun onSubscribe(d: Disposable) {
disposable = d // 订阅时处理
textView.text = "正在请求" // 显示请求中的提示
}
override fun onError(e: Throwable) {
textView.text = e.message ?: e.javaClass.name // 如果发生错误,显示错误信息
}
})
addCallAdapterFactory()用于与RxJava集成,这里通过RxJava3CallAdapterFactory创建一个适配器,并指定在后台线程(Schedulers.io())上执行网络请求。
4. Maybe
-
核心特征:可以发送 0 或 1 个数据,然后结束,或者发送错误。
-
回调接口(
MaybeObserver)中常见的方法:onSubscribe(Disposable d)onSuccess(T value):有数据时的回调onError(Throwable e):错误回调onComplete():如果没有数据产生,则会回调 onComplete 而不是 onSuccess
-
适用场景:
- 可能会返回一个值,也可能什么都不返回,或者出现错误。
- 例如:缓存中检索数据,若找到则返回,未找到则不发射数据直接完成。
举例:
Maybe<String> maybe = Maybe.create(emitter -> {
boolean foundData = ... // 假设有逻辑判断是否找到数据
if (foundData) {
emitter.onSuccess("Some data");
} else {
emitter.onComplete(); // 没有数据就完成
}
});
maybe.subscribe(new MaybeObserver<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {}
@Override
public void onSuccess(@NonNull String s) {
System.out.println("Maybe onSuccess: " + s);
}
@Override
public void onError(@NonNull Throwable e) {
System.err.println("Maybe onError: " + e.getMessage());
}
@Override
public void onComplete() {
System.out.println("Maybe onComplete: no data found.");
}
});
5. Completable
-
核心特征:不发送任何数据,只关心“成功(完成)”或“失败(错误)”。
-
回调接口(
CompletableObserver)中常见的方法:onSubscribe(Disposable d)onComplete():完成onError(Throwable e):错误
-
适用场景:
- 仅执行一个动作,而不需要返回数据。只需要知道是否完成或者出错即可。
- 常见例子:写文件、删数据库记录、日志上报、网络请求只关心结果状态等。
举例:
Completable completable = Completable.create(emitter -> {
try {
// 执行某些操作(如文件删除/写入)
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
completable.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(@NonNull Disposable d) {}
@Override
public void onComplete() {
System.out.println("Completable onComplete: success!");
}
@Override
public void onError(@NonNull Throwable e) {
System.err.println("Completable onError: " + e.getMessage());
}
});
总结
- Observable:适合 0 到多个数据项的场景,但不内置背压机制。
- Flowable:同样适合 0 到多个数据项,但内置了背压支持,适用于数据量大或数据产生速度快的场景。
- Single:专门用于只需要返回单个数据项或错误的场景,简化了只返回一个值的异步操作。
- Maybe:可能会有一个结果值,也可能没有
- Completable:不需要结果值,只需要知道完成或出错
| 类型 | 是否发送数据 | 发送数据数量 | 结束方式 | 错误方式 |
|---|---|---|---|---|
| Single | 是 | 只发送 1 个 | onSuccess() 后自动结束 | onError() |
| Maybe | 是 | 发送 0 或 1 个 | onSuccess() 或 onComplete() | onError() |
| Completable | 否 | 不发送数据 | onComplete() | onError() |
| Observable | 是 | 发送 0 或多个 | onNext() → onComplete() | onError() |
| Flowable | 是 | 发送 0 或多个 | onNext() → onComplete() | onError() |
Observable 和 Flowable 都是可以发送多个数据项的类型,但 Flowable 支持背压,适合处理大规模数据流或者可能出现流量过载的场景。还需要注意以下区分:
Observable:
- 是否发送数据:可以发送多个数据项。
- 发送数据数量:可以发送 0 到多个数据项。
- 结束方式:通过
onComplete()或onError()结束。 - 错误方式:通过
onError()发出错误。
Flowable:
- 是否发送数据:可以发送多个数据项(和
Observable类似)。 - 发送数据数量:可以发送 0 到多个数据项,但与
Observable不同的是,Flowable支持背压(Backpressure)。适用于需要控制数据发送速率的场景。 - 结束方式:通过
onComplete()或onError()结束。 - 错误方式:通过
onError()发出错误。
2. 操作符(Operators)与责任链模式
RxJava 的强大之处在于它提供了大量的操作符,用于数据转换、过滤、组合、错误处理等。其实现机制主要包括:
- 链式调用:通过操作符(如
map、filter、flatMap等)的链式组合,形成一个数据处理管道。 - 责任链/装饰器模式:每个操作符都包装了下游的 Observer,当数据通过操作符时,它们对数据进行处理(如转换、过滤)后,再将结果传递给下一个操作符或最终的 Observer。
- 惰性求值(Lazy Evaluation) :只有当真正有 Observer 订阅时,整个操作链才开始执行。这意味着创建 Observable 时不会立即执行数据计算。
例如,下面这段代码会构成一个数据处理链:
Observable.just(1, 2, 3)
.map { it * 2 }
.filter { it > 2 }
.subscribe { println(it) }
内部每个操作符都以包装的方式传递数据,形成一条从上游到下游的责任链。
3. 异步调度与线程管理
RxJava 通过 Scheduler 抽象出线程调度的概念,实现异步与并发操作。主要机制包括:
-
subscribeOn() :指定 Observable 发射数据(上游)的线程。
-
observeOn() :指定 Observer 接收数据(下游)的线程。
-
常见的 Scheduler 有:
Schedulers.io():用于 I/O 密集型任务,如网络请求、文件操作。Schedulers.computation():用于 CPU 密集型任务。Schedulers.newThread():为每个任务创建新的线程,一般不用,会导致线程无法复用,增加资源消耗。AndroidSchedulers.mainThread():用于 Android 中的主线程,常用来处理 UI 操作。Schedulers.trampoline():会按照调用顺序依次执行操作,它会将任务排入队列,顺序执行。
内部,Scheduler 会管理线程池或线程队列,在订阅时将任务提交到指定线程上执行,并通过线程切换来实现异步调用。
示例:多次切换线程
Observable.just("Hello")
.subscribeOn(Schedulers.io()) // 数据生产阶段在 IO 线程
.observeOn(Schedulers.computation()) // 第一次切换到计算线程
.map(item -> {
// 在计算线程中处理数据
return item + " Computed";
})
.observeOn(AndroidSchedulers.mainThread()) // 第二次切换到主线程
.subscribe(item -> {
// 在主线程中更新 UI
System.out.println("Received on main thread: " + item);
});
在这个例子中,数据流通过了两次线程切换:
- 第一处
subscribeOn(Schedulers.io())会在 IO 线程上执行数据的获取。 - 第二处
observeOn(Schedulers.computation())切换到计算线程处理数据。 - 最后,使用
observeOn(AndroidSchedulers.mainThread())切换回主线程更新 UI。
注意事项
- 线程切换的顺序:
subscribeOn()会影响到整个数据流的开始线程,而observeOn()只会影响它之后的操作。可以使用多个observeOn()来改变不同阶段的线程,subscribeOn()只能调用一次。,如果在多个地方调用subscribeOn(),只有 第一次调用的subscribeOn()会生效。后续的subscribeOn()不会起作用。示例:
Observable.just("Hello")
.subscribeOn(Schedulers.io()) // 第一次调用 subscribeOn
.subscribeOn(Schedulers.computation()) // 这不会生效,仍然会在 io 线程上订阅
.observeOn(AndroidSchedulers.mainThread()) // 切换到主线程
.subscribe(item -> {
System.out.println("Received on main thread: " + item);
});
在这个例子中,尽管我们调用了两次 subscribeOn(),但是只有第一次的 subscribeOn(Schedulers.io()) 会生效,后续的 subscribeOn(Schedulers.computation()) 会被忽略。因此,数据流会在 IO 线程 上开始执行,直到遇到 observeOn() 切换到主线程。
- 线程池:每个
Scheduler对应的线程池是有限的,所以要注意控制并发的数量,以避免过多的线程创建或线程池溢出。特别是在进行 I/O 操作时,应该考虑合适的并发数量。 subscribeOn()只影响订阅线程,也就是说,只有订阅操作被调用时才会真正启动流,而observeOn()是根据操作符链的执行顺序来影响执行线程的。observeOn()可以多次调用,影响不同阶段的线程切换。
4. 背压(Backpressure)机制
背压(Backpressure) 是用来解决 上游数据生产速度 大于 下游数据消费速度 时所面临的“拥堵”或“数据丢失”问题。RxJava 通过 Flowable(实现了 Reactive Streams 规范)以及一系列 背压策略,帮助我们在不同的场景下合理地处理数据流。
一、为什么需要背压
-
上游速度快,下游速度慢
- 例如:上游以 1,000,000/s 的速率发射数据,但下游只能处理 100/s。
- 如果没有背压机制,数量巨大的数据会堆积在内存或缓存队列中,造成 OOM(内存溢出)或系统崩溃。
-
常见场景
- 传感器/流式数据:例如 IoT 场景中,传感器高频发射数据,下游处理逻辑复杂且速度慢。
- 大型文件读取:上游可以快速读取文件,下游在网络或数据库写入速度受限时,会出现堵塞。
背压 机制的目标就是:让上游在必要时 减缓 或 限制 数据的发送,或通过一定的策略来丢弃/缓存部分数据,避免资源被无穷大数据淹没。
二、RxJava 对背压的支持
1. Flowable 与 Observable 的区别
Observable:不支持背压,如果上游发射速度过快,只能依靠内部的缓冲策略或下游某些操作符手动处理,否则可能出现MissingBackpressureException(在 RxJava2/3 中有些操作符会抛此错误)。Flowable:实现了 Reactive Streams 规范,使用Subscription+request(n)的方式提供背压支持,下游可以通过向上游“请求”一定数量的数据来控制节奏。
在 RxJava 3 中,
Flowable依旧是处理背压的主要途径,Observable仍然无背压。
2. Reactive Streams 规范中的 Subscription
当使用 Flowable.subscribe(Subscriber) 时,我们可以拿到一个 Subscription 对象,可以在 onSubscribe 回调中拿到它:
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
// 保存 subscription
// 主动 request(n) 告诉上游,你可以发射 n 个数据过来
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(String s) {
// 处理数据
}
@Override
public void onError(Throwable t) {
// 错误处理
}
@Override
public void onComplete() {
// 完成
}
};
Flowable<String> flowable = Flowable.just("A", "B", "C");
flowable.subscribe(subscriber);
在上面的例子中:
s.request(Long.MAX_VALUE)表示一次性请求最大数量的数据,不做限流。- 也可以根据自身的消费能力,分批次去请求(例如先
request(10),处理完这 10 条,再请求下一批)。
三、背压策略(BackpressureStrategy)
RxJava 中,为了简化对背压的处理,提供了多种背压策略。在使用 Flowable.create(...) 或某些操作符时,会需要指定一个 BackpressureStrategy:
Flowable.create(emitter -> {
// ...发射数据
}, BackpressureStrategy.BUFFER);
常见的背压策略:
-
BUFFER- 将所有上游数据都放到一个缓冲区中,直到下游消费。
- 如果数据量特别大,可能会导致内存占用过高。
-
DROP- 当下游来不及处理时,新的数据将被直接丢弃,不会缓存。
- 适合对数据完整性要求不高的场景(如某些实时监控,只关心最新数据)。
-
LATEST- 当下游来不及处理时,仅保留最新的一条数据,其余数据丢弃。
- 适合只关心最新状态、不关心历史数据的场景(如实时刷新界面)。
-
ERROR- 当上下游速率不匹配时会抛出
MissingBackpressureException。 - 适用于强一致性场景,希望上游严格按照下游可处理的速率发送数据,一旦无法跟上就抛错。
- 当上下游速率不匹配时会抛出
-
MISSING(或不指定)- 不进行任何背压处理,如果下游无法及时消费,可能会导致异常或数据丢失。
- 一般不推荐使用,除非确定背压不是问题(如数据非常小或下游处理足够快)。
示例:使用 BUFFER 策略
Flowable<String> flowable = Flowable.create(emitter -> {
for (int i = 0; i < 1000000; i++) {
// 在这里持续发射数据
if (emitter.isCancelled()) {
return; // 如果下游取消订阅,就停止发送
}
emitter.onNext("Item " + i);
}
emitter.onComplete();
}, BackpressureStrategy.BUFFER);
flowable.subscribe(new Subscriber<String>() {
private Subscription subscription;
@Override
public void onSubscribe(Subscription s) {
this.subscription = s;
// 一次性请求全部数据(不建议在大量数据时使用)
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(String s) {
// 消费数据
System.out.println("Received: " + s);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("Done!");
}
});
四、常见背压场景及应对方式
-
上游速度不可控,但又要保留全部数据
- 例如:日志系统,需要存储全部日志。
- 使用
BackpressureStrategy.BUFFER或结合磁盘队列、分批处理,防止内存爆炸。 - 或者手动实现一个消息队列,将溢出数据落地。
-
只关心最新数据
- 例如:实时状态显示,若数据瞬间涌入,不必全部显示,只需展示最新的状态。
- 使用
BackpressureStrategy.LATEST,保留最新一条,丢弃其他。
-
对历史数据完整性要求不高
- 例如:某些传感器数据,只需要间断采样即可。
- 使用
BackpressureStrategy.DROP,下游来不及处理就直接丢弃多余的数据。
-
强一致性/强背压场景
- 例如:金融交易系统,每条数据都必须处理且不可丢失。
- 使用
BackpressureStrategy.ERROR并在下游合理地request(n),一旦超量就抛错,提醒进行流控或扩容。
五、注意事项
-
Observable≠ 背压Observable在 RxJava 2/3 中并没有背压,数据量过大时可能抛出MissingBackpressureException或导致内存问题。- 若有大规模数据流,使用
Flowable更安全。
-
下游的
request(n)- 在
Subscriber中,可以通过subscription.request(n)分批请求数据。 - 若一次性
request(Long.MAX_VALUE), 等同于不做背压限制(或很宽松的限流)。
- 在
-
不要过度缓存
- 使用
BackpressureStrategy.BUFFER时,要注意可能导致内存占用飙升。 - 尤其在高并发或高频数据场景,需要考虑与系统资源上限的平衡。
- 使用
-
正确使用背压操作符
- RxJava 中也提供一些操作符(如
onBackpressureBuffer(),onBackpressureDrop()等)可以动态处理背压。 - 当我们从
Observable转换为Flowable时,也可以指定背压策略。
- RxJava 中也提供一些操作符(如
六、总结
- 背压的核心:当消费者无法跟上生产者速度时,需要一种机制来缓冲或丢弃多余数据,或者让消费者能主动告诉生产者“我只能处理这么多”。
- Flowable 在 RxJava3 中依旧是官方推荐的针对背压问题的解决方案,实现了
Subscription+request(n)的模式。 - 选择合适的 背压策略(
BUFFER,DROP,LATEST,ERROR等)可以让程序在面对快生产、慢消费的情况下更稳定地运行。 - 在实战中,还要结合实际的业务需求、数据规模和系统资源情况来综合考虑背压策略,不要无限制地堆积数据,也不要盲目地丢弃重要数据。
5. 内部数据流传递与优化
RxJava 内部为了高效传递数据和降低资源消耗,还采用了以下技术:
- 队列和状态机:在操作符之间的数据传递常使用队列来缓存数据项,并借助状态机保证线程安全。
- Fusion 优化:在某些操作符链中,如果上下游操作符支持“融合”(Fusion),就可以减少额外的排队与调度开销,直接在同一个线程中无缝传递数据。这一优化在性能上非常关键。
- 原子变量与 CAS:为了保证在多线程环境下操作符之间的数据传递和状态更新的正确性,RxJava 大量使用原子操作(如
AtomicInteger、AtomicReference)和 CAS(Compare-And-Swap)机制。
6. 异常处理与插件机制
- 严格的异常传递:在任何操作符中,如果出现异常,会立即调用下游的
onError(),并中断数据流。 - RxJavaPlugins:RxJava 提供了全局插件机制,允许用户在全局范围内捕获错误、修改调度器,甚至在调试时记录额外信息。通过
RxJavaPlugins.setErrorHandler()等接口,可以自定义异常处理逻辑。
7. 订阅管理与资源回收
- Disposable/Subscription:每个订阅返回一个管理对象,可以通过调用
dispose()或cancel()来停止数据流,并释放资源。 - 取消机制:在异步场景下,取消订阅后,内部会中断数据传递、取消挂起任务、清空内部队列,从而防止资源泄漏。
8. 总结
RxJava 3 的设计和实现基于以下几个核心思想:
- 解耦数据生产与消费:通过观察者模式与操作符链式调用,形成灵活且可扩展的数据处理管道。
- 高效的异步调度:通过 Scheduler 对线程进行抽象和管理,实现多线程异步调用,同时保持代码的声明式风格。
- 背压控制与资源保护:Flowable 提供的背压机制确保在数据量大时,消费者能够按照自己的处理能力消费数据,避免系统资源被过度消耗。
- 内部优化:利用队列、状态机、Fusion 优化、原子操作等手段,确保在高并发环境下数据流传递的高效性和线程安全性。
- 全局异常管理:通过 RxJavaPlugins 和严格的异常传递机制,保证了整个响应式系统在面对错误时能够及时响应和处理。
这种设计使得 RxJava 3 成为一个强大而灵活的响应式编程库,既能处理简单的异步任务,又能应对复杂的并发场景。
一些问题
- 创建 Single 对象的原理
- map() 操作符的原理
- Disposable 的原理
- subscribeOn() 的原理
- observeOn() 的原理
- Scheduler 切换线程的原理
创建 Single 对象的原理
在 RxJava 3 中,Single 是一种只发射单个结果(或者发生错误)的流类型,它是 Observable 的一个特例。创建 Single 对象的方式通常是通过以下方法之一:
- 使用
Single.create()或者其他构造器(如Single.just())来创建单一的流。
Single.create() 方法的原理是通过 SingleOnSubscribe 接口来实现的。SingleOnSubscribe 接口的实现会在内部执行一些耗时操作,并最终发射一个单一的结果值或者错误。Single 的行为本质上与 Observable 类似,但它只会发射一个单一的值(如果成功的话)或一个错误(如果发生异常的话)。
Single.create<Int> { emitter ->
try {
// 执行一些操作
emitter.onSuccess(1) // 发射成功的结果
} catch (e: Exception) {
emitter.onError(e) // 发生错误时通知
}
}
2. map() 操作符的原理
map() 是 RxJava 中的一个常用操作符,它的作用是对流中发射的每个项进行转换。在 Single, Observable, 或者 Flowable 上使用 map() 操作符时,操作符内部通过接收原始数据并返回转换后的数据来创建新的流。原理上,map() 操作符会在每个事件发射时,应用给定的函数并发射新的事件。
实现上,map() 返回的是一个新的 Observable 或 Single,它通过 onNext() 或 onSuccess() 向下游发送处理过的项。map() 并不改变上游的行为,它只修改下游接收到的数据。
Single.just(1)
.map { it * 2 } // 将值乘以 2
.subscribe { result ->
println(result) // 输出 2
}
3. Disposable 的原理
Disposable 是 RxJava 中一个用于管理订阅关系的接口。每次通过 subscribe() 创建的订阅都会返回一个 Disposable 对象,该对象可以用来管理订阅的生命周期,特别是用于取消订阅。在 RxJava 中,Disposable 的实现允许我们在不需要数据时取消订阅,防止内存泄漏或不必要的资源消耗。
实现上,Disposable 是一个接口,它通常由 DisposableObserver, CompositeDisposable 等类实现。通过调用 dispose() 方法,可以取消订阅,停止流的发射。
val disposable: Disposable = Single.just(1)
.subscribe { result ->
println(result)
}
disposable.dispose() // 取消订阅
4. subscribeOn() 的原理
subscribeOn() 是 RxJava 中的一个调度操作符,用于指定上游操作应该在哪个线程上执行。调用 subscribeOn() 时,RxJava 会确保后续的操作(即订阅操作)发生在指定的 Scheduler 上。注意,subscribeOn() 只影响订阅者(即开始执行流的地方),而不会影响流中间的其他操作符。subscribeOn()只有首次调用才会有效。
实现上,subscribeOn() 通过改变执行 subscribe() 操作的线程来实现调度,确保在指定的线程上开始执行任务。
Single.just(1)
.subscribeOn(Schedulers.io()) // 在 IO 线程上订阅
.subscribe { result ->
println(result) // 结果在 IO 线程中处理
}
5. observeOn() 的原理
observeOn() 是 RxJava 中的一个操作符,用于指定下游操作(即订阅者)的执行线程。与 subscribeOn() 不同,observeOn() 影响的是事件的传递过程,也就是从上游到下游的数据流转的线程切换。
调用 observeOn() 时,所有后续的操作会在指定的 Scheduler 上执行。可以使用多个 observeOn() 来逐步切换线程。
实现上,observeOn() 在事件传递时会更改事件的分发线程,确保下游的订阅者能够在指定的线程上处理数据。
Single.just(1)
.observeOn(Schedulers.io()) // 在 IO 线程上接收
.subscribe { result ->
println(result) // 在 IO 线程上处理
}
6. Scheduler 切换线程的原理
Scheduler 是 RxJava 中的核心类,用于调度任务的执行。它是 RxJava 的线程管理基础设施。Scheduler 本质上代表了一个执行任务的机制(例如一个线程池、事件循环、IO 线程等)。不同的 Scheduler 实现决定了任务的执行方式和位置。
Schedulers.io():用于执行 IO 密集型的操作(如网络请求、磁盘操作等)。它基于线程池来调度任务,并且如果有空闲线程,会复用现有线程。Schedulers.computation():用于执行计算密集型操作(如复杂的数学计算等)。它也基于线程池,但与 IO 调度不同,它不会进行阻塞操作。Schedulers.newThread():每次都会创建一个新的线程来执行任务,适用于需要独立线程的场景。AndroidSchedulers.mainThread():用于在主线程中执行任务,通常用于更新 UI。
调度的核心原理是通过将任务提交给适当的线程池或执行器来实现线程的切换。当调用 observeOn() 或 subscribeOn() 时,RxJava 会根据指定的 Scheduler 在合适的线程中执行任务。主线程的线程切换通常是通过 Handler.post() 实现的,特别是在 AndroidSchedulers.mainThread() 中。对于其他线程切换(如 IO 线程或计算线程),RxJava 使用线程池等其他机制。
Single.just(1)
.subscribeOn(Schedulers.io()) // IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 主线程
.subscribe { result ->
// 在主线程中处理结果
}
总结
Single的原理:通过SingleOnSubscribe创建并发射一个单一的结果或错误。map()的原理:对流中的数据应用转换函数,并发射转换后的数据。Disposable的原理:管理订阅的生命周期,可以取消订阅。subscribeOn()的原理:设置上游操作执行的线程,影响订阅的线程。observeOn()的原理:设置下游操作执行的线程,影响事件的传递过程。Scheduler切换线程的原理:通过不同的调度器实现线程切换和任务调度。