RxJava3 的使用

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{} 时,背后的执行步骤:

  1. subscribe() 被调用,触发自下而上的订阅链。最下游的 observer 先创建,然后一层层往上游包。
  2. filter 创建一个 ObservableFilter,里面 hold 了 map 返回的 Observable 的引用。
  3. map 创建 ObservableMap,hold 了原始 Observable 的引用。
  4. 原始 Observable 的 subscribeActual() 被调用,开始自上而下发射数据。
  5. 数据经过 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 包进 ObservableSubscribeOnsubscribeActual() 里向目标线程池扔了一个 Runnable。

多次调用 subscribeOn 只有第一次有效------subscribeActual 是从最外层的 subscribeOn 往里走的,最外层先劫持了线程,里层就没机会了。

observeOn 影响数据下发流程。ObservableObserveOnonNext 里把数据丢给目标 Scheduler 的 Worker 线程去执行,每次 observeOn 都会切一次。

flatMap 为什么用最多也最容易出问题

flatMap 是把上游的每个值映射成另一个 Observable,然后把所有 Observable 合并成一个。它内部不保证顺序、不限制并发数------上游瞬间发射 100 个值,flatMap 就同时开 100 个网络请求。

这也就是 concatMapswitchMap 存在的理由:

  • concatMap --- 严格按上游顺序,一个请求完成才发下一个
  • switchMap --- 上游发新值时取消前一个未完成的

具体场景:上传图片用 concatMap(顺序重要),搜索建议用 switchMap(只关心最新结果),全量加载多个分类数据用 flatMap 配合 maxConcurrency 限制并发。

背压------Flowable 怎么做的

Observable 不处理背压。上游发射比下游消费快太多时,数据在内部缓冲区堆积,OOM 只是时间问题。

Flowable 解决了这个问题。BackpressureStrategy 五种模式:

  • MISSING --- 不处理,下游自己 onBackpressureXXX 兜底
  • ERROR --- 下游跟不上就抛 MissingBackpressureException
  • BUFFER --- 无界缓冲(内存炸掉)
  • 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 同时实现 ObservableObserver,可以手动往里面塞数据:

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 控制并发数、异常必须处理。

相关推荐
AC赳赳老秦13 天前
用 OpenClaw 搭建服务器故障应急响应系统,自动处理 80% 常见运维故障
android·运维·服务器·python·rxjava·deepseek·openclaw
oioihoii16 天前
探索React与Microi吾码的完美结合:快速搭建项目,低代码便捷开发教程
react.js·低代码·rxjava
chian-ocean16 天前
Microi吾码:从零到服装ERP:低代码打造企业级系统的实战之旅
android·低代码·rxjava
消失的旧时光-194320 天前
Kotlin 协程设计思想(十):Kotlin 协程到底解决了什么问题?
开发语言·kotlin·生命周期·rxjava·协程·结构化并发
token-go25 天前
Means:基于 .NET 10 打造的开源自部署 S3 兼容对象存储服务
低代码·.net·rxjava
有味道的男人1 个月前
Open Claw对接1688平台
android·rxjava
企客宝CRM1 个月前
2026年中小企业CRM选型指南:企客宝CRM处于什么位置?
android·算法·企业微信·rxjava·crm
wenzhangli72 个月前
在低代码设计中践行 Harness Engineering
android·低代码·rxjava
低代码布道师2 个月前
微搭低代码MBA 培训管理系统实战 30——学习卡
学习·低代码·rxjava