RxJava3 的使用
RxJava 是 ReactiveX 规范在 JVM 上的实现,最初由 Netflix 在 2013 年开源。2016 年发布 2.x(支持背压、支持 Reactive Streams 规范),2020 年进 3.x------包名迁移到 io.reactivex.rxjava3,底层基于 Java 8,同时去了几个过时 API。
RxJava 的核心思想简单粗暴:一切皆流。网络请求是流、点击事件是流、数据库查询是流、定时任务是流。这些流用操作符串起来,数据经过 map/filter/flatMap 层层变换,最终在 observer 收到结果。写完一段链式调用,业务就描述完了------不需要维护一堆状态变量和回调嵌套。
Android 开发里 RxJava 曾经统治了近十年。现在 Kotlin 协程 + Flow 逐渐取代了不少场景,但老项目、存量的 RxJava 代码依然大量存在,理解它的设计对维护工作有帮助。
接入项目
kotlin
implementation("io.reactivex.rxjava3:rxjava:3.1.9")
implementation("io.reactivex.rxjava3:rxandroid:3.0.2")
Groovy 写法:
groovy
implementation 'io.reactivex.rxjava3:rxjava:3.1.9'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
版本管理抽到 libs.versions.toml:
toml
[versions]
rxjava = "3.1.9"
rxandroid = "3.0.2"
[libraries]
rxjava = { group = "io.reactivex.rxjava3", name = "rxjava", version.ref = "rxjava" }
rxandroid = { group = "io.reactivex.rxjava3", name = "rxandroid", version.ref = "rxandroid" }
然后:
kotlin
implementation(libs.rxjava)
implementation(libs.rxandroid)
rxandroid 那包很小,就提供了一个 AndroidSchedulers.mainThread()。但没它的话切主线程得自己搞 Handler,实际项目里基本都引了。
Retrofit 配合 RxJava 还需要 Adapter:
kotlin
implementation("com.squareup.retrofit2:adapter-rxjava3:2.11.0")
基础用法
RxJava 3.x 有五种基本数据类型:
| 类型 | 发射项 | 用途 |
|---|---|---|
Observable |
0..N | 不支持背压,适合 UI 事件 |
Flowable |
0..N | 支持背压,适合数据流 |
Single |
1 或 error | 网络请求 |
Maybe |
0..1 | 可能为空的数据 |
Completable |
仅完成/出错 | 无返回值的操作 |
创建流------冷启动
kotlin
// Single --- 网络请求最常见
Single.create<String> { emitter ->
val result = api.fetchData()
if (result != null) {
emitter.onSuccess(result)
} else {
emitter.onError(RuntimeException("数据为空"))
}
}
// 快捷方式
Single.just("hello")
Single.fromCallable { heavyComputation() }
// Observable --- 多值事件
Observable.create<String> { emitter ->
emitter.onNext("A")
emitter.onNext("B")
emitter.onComplete()
}
// 快捷方式
Observable.just(1, 2, 3)
Observable.fromIterable(list)
Observable.interval(1, TimeUnit.SECONDS) // 定时
// Flowable --- 多值 + 背压
Flowable.range(1, 10000)
Flowable.fromIterable(bigList)
// Maybe --- 可选数据
Maybe.create<String> { emitter ->
val value = cache.get("key")
if (value != null) {
emitter.onSuccess(value)
} else {
emitter.onComplete()
}
}
// Completable --- 无返回值操作
Completable.create { emitter ->
database.clear()
emitter.onComplete()
}
create() 里的 lambda 是冷 的------不 subscribe 不执行。这一点跟 Flow 的 flow {} 一样。
订阅
kotlin
observable.subscribe(
{ value -> println("收到: $value") }, // onNext
{ error -> println("出错: $error") }, // onError
{ println("完成") } // onComplete
)
通常用更精简的重载:
kotlin
single.subscribe({ result ->
// onSuccess
}, { error ->
// onError
})
操作符
操作符数量很多,列几个日常使用频率最高的:
kotlin
// map --- 类型转换
api.getUser(123)
.map { it.name }
.subscribe { name -> textView.text = name }
// flatMap --- 串联请求
api.getUser(123)
.flatMap { user -> api.getPosts(user.id) }
.subscribe { posts -> /* 拿到了帖子列表 */ }
// filter
listObservable
.filter { it.isActive }
.subscribe { /* 只处理活跃项 */ }
// zip --- 合并两个请求结果
Single.zip(
api.getUser(123),
api.getOrderList(123),
BiFunction { user, orders -> UserWithOrders(user, orders) }
).subscribe { /* user + orders 一起拿到了 */ }
// concatMap --- 保持顺序
listObservable
.concatMap { item -> processItem(item) }
.subscribe()
线程切换
kotlin
single
.subscribeOn(Schedulers.io()) // 上游执行在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 下游回调在主线程
.subscribe { result -> /* 更新 UI */ }
subscribeOn 决定数据源跑在哪个线程,多次调用只有第一次生效(后面会被忽略)。observeOn 决定后续操作符和回调跑在哪个线程,可以多次调用,每次都切换下游。
Schedulers 几种常用的:
Schedulers.io()--- IO 密集,无上限线程池Schedulers.computation()--- CPU 密集,线程数 = 核心数Schedulers.single()--- 单线程AndroidSchedulers.mainThread()--- 主线程
Disposable 管理
subscribe 返回一个 Disposable,必须在合适的时机 dispose()。忘了处理就是内存泄漏------Activity 毁掉了后台还在跑,回调里引着 View。
kotlin
class MyActivity : AppCompatActivity() {
private val compositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val disposable = api.getUser(123)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { user -> /* 更新 UI */ }
compositeDisposable.add(disposable)
}
override fun onDestroy() {
compositeDisposable.clear()
super.onDestroy()
}
}
CompositeDisposable.clear() 和 dispose() 的区别是:clear() 清空容器且不阻止继续往里加;dispose() 标记为 disposed,再添加时直接 dispose 新对象。
封装:让 RxJava 在项目里好用
直接用 API 有几个痛点:每个调用的地方都要写 subscribeOn/observeOn、每个 View 层都要管 CompositeDisposable、错误处理散乱。下面封装思路主要解决这几个问题。
线程自动切换
kotlin
fun <T> Observable<T>.ioToMain(): Observable<T> =
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
fun <T> Single<T>.ioToMain(): Single<T> =
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
fun <T> Flowable<T>.ioToMain(): Flowable<T> =
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
fun Completable.ioToMain(): Completable =
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
调用时省掉重复的线程声明:
kotlin
api.getUser(123).ioToMain().subscribe { user -> /* UI */ }
统一的 API Response 处理
后端一般统一个返回格式:
json
{ "code": 0, "message": "ok", "data": { ... } }
实体类:
kotlin
data class ApiResponse<T>(
val code: Int,
val message: String,
val data: T
)
扩展方法,非 0 时直接走 onError,调用方不用每次判断:
kotlin
fun <T> Single<ApiResponse<T>>.unwrap(): Single<T> =
flatMap { response ->
if (response.code == 0) {
Single.just(response.data)
} else {
Single.error(ApiException(response.code, response.message))
}
}
调用方干净了:
kotlin
api.getUser(123)
.unwrap()
.ioToMain()
.subscribe({ user -> /* 直接拿到 user */ }, { /* 统一处理错误 */ })
BaseViewModel 收拢 Disposable
kotlin
open class BaseViewModel : ViewModel() {
private val compositeDisposable = CompositeDisposable()
fun addDisposable(disposable: Disposable) {
compositeDisposable.add(disposable)
}
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
}
也可以再封个便捷方法:
kotlin
fun <T> Single<T>.autoDispose(viewModel: BaseViewModel): Disposable =
doOnSubscribe { viewModel.addDisposable(it) }
.subscribe({}, {}) // 空订阅,只是注册 Disposable
生命周期自动取消
Activity/Fragment 里直接 subscribe 的情况处理需要更小心。两个常见方案:
RxLifecycle
kotlin
implementation("com.trello.rxlifecycle4:rxlifecycle-components:4.0.0")
kotlin
api.getUser(123)
.compose(bindUntilEvent(ActivityEvent.DESTROY))
.ioToMain()
.subscribe { /* 安全更新 UI */ }
原理是 compose() 往里塞一个 Transformer,当 Activity 到达指定生命周期时自动 dispose。
AutoDispose
kotlin
implementation("com.uber.autodispose2:autodispose-android:2.2.1")
kotlin
api.getUser(123)
.`as`(autoDisposable(AndroidLifecycleScopeProvider.from(this)))
.ioToMain()
.subscribe { /* 安全更新 UI */ }
AutoDispose 用 ScopeProvider,不强制改调用链的 Composition 方式,侵入性比 RxLifecycle 小一些。
网络请求的完整封装链
把以上几件事串起来,一个网络请求从发起到 UI 更新的完整链路:
kotlin
api.getUser(123)
.unwrap() // 统一解包
.ioToMain() // 线程切换
.compose(bindUntilEvent(ActivityEvent.DESTROY)) // 生命周期绑定
.subscribe(
{ user -> _userLiveData.value = user },
{ error -> handleError(error) }
)
如果项目统一做完封装,每个业务页实际只需要关心成功回调。
底层原理
Observable 的订阅链路
写下 observable.map{}.filter{}.subscribe{} 时,背后的执行步骤:
subscribe()被调用,触发自下而上的订阅链。最下游的 observer 先创建,然后一层层往上游包。filter创建一个ObservableFilter,里面 hold 了map返回的 Observable 的引用。map创建ObservableMap,hold 了原始 Observable 的引用。- 原始 Observable 的
subscribeActual()被调用,开始自上而下发射数据。 - 数据经过 map → filter → 最终 observer 的
onNext。
这个双向过程写成伪代码大概是:
scss
subscribe(下游Observer)
→ ObservableFilter.subscribeActual(下游Observer)
→ ObservableMap.subscribeActual(filterObserver)
→ 原始Observable.subscribeActual(mapObserver)
→ 数据源开始发射
→ mapObserver.onNext → 转换
→ filterObserver.onNext → 过滤
→ 下游Observer.onNext → 用户拿到数据
理解这个自上而下建链、自下而上订阅的过程,对写操作符和排查问题有帮助。
subscribeOn 和 observeOn 怎么做到的
subscribeOn 只影响订阅流程,数据源的 subscribe() 和所有 subscribeOn 之前的操作符都在指定 Scheduler 上执行。内部实现是把原始 Observable 包进 ObservableSubscribeOn,subscribeActual() 里向目标线程池扔了一个 Runnable。
多次调用 subscribeOn 只有第一次有效------subscribeActual 是从最外层的 subscribeOn 往里走的,最外层先劫持了线程,里层就没机会了。
observeOn 影响数据下发流程。ObservableObserveOn 在 onNext 里把数据丢给目标 Scheduler 的 Worker 线程去执行,每次 observeOn 都会切一次。
flatMap 为什么用最多也最容易出问题
flatMap 是把上游的每个值映射成另一个 Observable,然后把所有 Observable 合并成一个。它内部不保证顺序、不限制并发数------上游瞬间发射 100 个值,flatMap 就同时开 100 个网络请求。
这也就是 concatMap 和 switchMap 存在的理由:
concatMap--- 严格按上游顺序,一个请求完成才发下一个switchMap--- 上游发新值时取消前一个未完成的
具体场景:上传图片用 concatMap(顺序重要),搜索建议用 switchMap(只关心最新结果),全量加载多个分类数据用 flatMap 配合 maxConcurrency 限制并发。
背压------Flowable 怎么做的
Observable 不处理背压。上游发射比下游消费快太多时,数据在内部缓冲区堆积,OOM 只是时间问题。
Flowable 解决了这个问题。BackpressureStrategy 五种模式:
MISSING--- 不处理,下游自己onBackpressureXXX兜底ERROR--- 下游跟不上就抛MissingBackpressureExceptionBUFFER--- 无界缓冲(内存炸掉)DROP--- 丢弃下游来不及消费的数据LATEST--- 只保最新一条
kotlin
Flowable.create<Int>({ emitter ->
for (i in 1..1_000_000) {
emitter.onNext(i)
}
emitter.onComplete()
}, BackpressureStrategy.DROP)
.observeOn(Schedulers.computation())
.subscribe { value -> heavyProcessing(value) }
协程 Flow 因为拉模型的特性,天然没背压问题------下游不 collect,上游不会生产。这是设计上的本质区别。
Subject --- 既是生产者也是消费者
Subject 同时实现 Observable 和 Observer,可以手动往里面塞数据:
kotlin
// PublishSubject --- 只收到订阅后的数据
val clickSubject = PublishSubject.create<View>()
clickSubject.subscribe { /* 处理点击 */ }
button.setOnClickListener { clickSubject.onNext(it) }
// BehaviorSubject --- 订阅时收到最新一条
val stateSubject = BehaviorSubject.createDefault(User())
stateSubject.subscribe { user -> /* 立即拿到当前用户 */ }
stateSubject.onNext(newUser) // 更新状态
// ReplaySubject --- 重放历史
val replaySubject = ReplaySubject.createWithSize<String>(10)
replaySubject.onNext("event-1")
replaySubject.subscribe { /* 拿到 event-1 */ }
Subject 在 MVVM 里常用来做 EventBus 的替代------不跨模块通信时比 EventBus 更可控,类型安全,不需要 @Subscribe 注解。
进阶用法
自定义操作符
RxJava 内置操作符覆盖了大多数场景,但有时封装业务逻辑到操作符里会更清晰------比如请求失败后先展示缓存的逻辑。
kotlin
fun <T> Single<T>.cacheThenRemote(
cacheLoader: () -> Single<T>,
remoteLoader: () -> Single<T>
): Single<T> =
cacheLoader()
.toObservable()
.concatWith(remoteLoader().toObservable().onErrorResumeNext(Observable.empty()))
.firstOrError()
调用:
kotlin
cacheThenRemote(
cacheLoader = { cache.getUser() },
remoteLoader = { api.getUser(123).unwrap() }
)
.ioToMain()
.subscribe { user -> /* 先展示缓存,再更新为远程 */ }
组合多个数据源
kotlin
// 任一更新
Observable.merge(
priceSubject,
stockSubject,
promotionSubject
).subscribe { /* 三者之一变了就刷新 */ }
// 全部拿到
Observable.combineLatest(
userSubject,
configSubject,
BiFunction { user: User, config: Config -> Pair(user, config) }
).subscribe { /* 两者齐全才处理 */ }
重试与轮询
kotlin
// 失败重试------简单的
api.getUser(123)
.unwrap()
.retry(3) // 失败最多重试 3 次
.ioToMain()
.subscribe()
// 失败重试------带延迟和条件
api.getUser(123)
.unwrap()
.retryWhen { errors ->
errors.zipWith(
Observable.range(1, 3),
BiFunction { _: Throwable, retryCount: Int -> retryCount }
).flatMap { retryCount ->
Observable.timer(retryCount * 2L, TimeUnit.SECONDS)
} // 第1次等2秒、第2次等4秒、第3次等6秒
}
.ioToMain()
.subscribe()
// 轮询
Observable.interval(0, 10, TimeUnit.SECONDS)
.flatMapSingle { api.getNotification().unwrap().ioToMain() }
.compose(bindUntilEvent(ActivityEvent.PAUSE))
.subscribe { /* 每10秒刷新一次通知 */ }
Retrofit + RxJava 完整写法
Retrofit 接口直接返回 RxJava 类型:
kotlin
interface UserApi {
@GET("user/{id}")
fun getUser(@Path("id") userId: Long): Single<ApiResponse<User>>
@GET("user/list")
fun getUsers(@Query("page") page: Int): Observable<ApiResponse<List<User>>>
}
使用时:
kotlin
userApi.getUser(123)
.unwrap()
.ioToMain()
.compose(bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(
{ user -> showUser(user) },
{ error -> showError(error) }
)
adapter-rxjava3 做的事不复杂,就实现了 CallAdapter.Factory,把 Retrofit 的 Call<T> 桥接到 Single<T> / Observable<T>。
踩过的坑
Disposable 忘了 dispose
最经典的问题。Activity 销毁时 Observable 还在跑,回调里 setText 直接对 null View 操作。坚持用 CompositeDisposable + onDestroy 清理,或者上 RxLifecycle。
subscribeOn 位置不对
kotlin
// 线上经常出问题
Single.just(heavyCompute()) // ← 这个已经在当前线程执行了!
.subscribeOn(Schedulers.io())
.subscribe()
Single.just() 的参数在构造时就计算了,跟后面 subscribeOn 无关。用 Single.fromCallable { heavyCompute() },计算会延迟到 subscribe 时。
observeOn 后面的操作符在主线程
kotlin
single
.subscribeOn(Schedulers.io())
.map { heavyTransform(it) } // ← 在 IO 线程
.observeOn(AndroidSchedulers.mainThread())
.map { heavyTransform(it) } // ← 在主线程!ANR 隐患
observeOn 只影响后面的操作符,想保持 IO 就放到 observeOn 之前,或者在主线程只做轻量的 UI 更新。
flatMap 引发并发过多
kotlin
listObservable
.flatMap { item -> api.upload(item) } // 上限 100+ 并发
.subscribe()
flatMap 默认并发数 = Integer.MAX_VALUE,用 flatMap { item -> api.upload(item) } 一行就可以把服务端打爆。限制并发数:
kotlin
listObservable
.flatMap({ item -> api.upload(item) }, maxConcurrency = 3)
.subscribe()
RxJava 不支持 Kotlin null-safety
RxJava 本身是 Java 写的,不关心 Kotlin 的空安全。Observable.just(null) 运行时直接 NPE。Single 必须走 onError 不能传 null,Maybe 是传 null 处理的选择。
onError 没处理
kotlin
observable.subscribe { value -> /* 只处理了 onNext */ }
这个重载下,onError 被忽略了。上游抛出异常时直接倒进程。subscribe 至少写两个参数。
误用 timer + Observable 做延迟导致内存泄漏
kotlin
Observable.timer(10, TimeUnit.SECONDS)
.subscribe { /* 10 秒后 */ }
这个 Disposable 没人管,页面销毁后还在计数,10 秒后回调里的 View 引用已经非法。Timer 用的时候一定放 CompositeDisposable 里。
Flowable + BUFFER 策略下大文件处理
用 BackpressureStrategy.BUFFER 读大文件,缓冲大到 OOM。读文件场景用 Flowable.generate() 控制生产速度,别用 create() + BUFFER。
跟 Kotlin Flow / 协程的关系
RxJava 在纯 Kotlin 项目里确实在被取代。Flow 的操作符集合跟 RxJava 有 70% 以上的重叠,属于同类设计范式,这里只概括关键差异:
- RxJava 推模型,Flow 拉模型------直接决定了背压行为
- Flow 是协程一等公民,结构化并发取消直接生效
- RxJava 200+ 操作符,Flow 100+,但 Flow 配合 suspend 的灵活性弥补了数量差距
- 新项目纯 Kotlin 场景用 Flow,维护老项目不必为了换而换
RxJava 不会死,就像 AsyncTask 退出舞台后,原理性的知识依然在起作用。理解它的流式抽象、线程调度、操作符组合方式,对理解协程和 Flow 有很大帮助------Reactive Streams 的思想就是协程 Flow 的基础。
最后
RxJava 的复杂度不低。最开始的 Observable / Single / Flowable / Maybe / Completable 五种类型、200+ 操作符、subscribeOn 和 observeOn 的微妙行为差异,上手时有明确的学习曲线。
但"用操作符链描述数据流"的方式一旦熟悉了,会显得简洁且合理------不需要维护分散的 flag,不需要回调套回调。网上大量的老项目仍然是 RxJava 写的,懂它在实际工作中用得上。
几个使用要点:坚持 CompositeDisposable 管理生命周期、subscribeOn 只写一次、后 observeOn 的主线程部分只做 UI、flatMap 控制并发数、异常必须处理。