本文不讨论方案的优缺点,毕竟方案各有千秋,也不大篇幅展开讨论Flow和Rxjava的优缺点,只单纯讲解Rxjava的简单用法,给大家在编码解决问题的过程中,多一种思路。
基本概念
集成
groovy
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
// RxAndroid 用于 Android 主线程调度
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
在RxJava 3中,发送和订阅数据的流程可以通过一个流程图来直观地展示。以下是一个简化的流程图示例,描述了从Observable(被观察者)创建数据,到Observer(观察者)订阅并接收数据的整个过程:
plaintext
+-----------------+
| Observable |
| (被观察者) |
| 创建数据源 |
+-------+---------+
|
v
+-------+---------+
| 创建Operator(s) | (可选,用于数据转换、过滤等)
+-------+---------+
|
v
+-------+---------+
| Subscribe |
| (订阅操作) |
+-------+---------+
|
v
+-----------------+
| Observer/ |
| Subscriber |
| (观察者) |
| 接收并处理数据 |
+-------+---------+
|
v
+-----------------+
| 数据处理逻辑 |
| (例如: onNext, |
| onError, |
| onComplete) |
+-----------------+
详细步骤说明:
-
Observable(被观察者)创建数据源:
- Observable是数据的源头,它定义了如何生成或获取数据。常见的操作符有Observable、Single、PublishSubject、Flowable等等
-
(可选)创建Operator(s):
- Operator(操作符)是RxJava中的中间件,用于对Observable发出的数据进行转换、过滤、合并等操作。常见的操作符有
map
,filter
,flatMap
等。
- Operator(操作符)是RxJava中的中间件,用于对Observable发出的数据进行转换、过滤、合并等操作。常见的操作符有
-
Subscribe(订阅操作):
- Observer(观察者)通过调用Observable的
subscribe
方法来订阅数据。在这一步,Observer将自己的回调方法(如onNext
,onError
,onComplete
)注册到Observable上。
- Observer(观察者)通过调用Observable的
-
Observer/Subscriber(观察者)接收并处理数据:
- 当Observable发出数据时,它会调用Observer的
onNext
方法。 - 如果发生错误,Observable会调用
onError
方法,并终止数据流。 - 当所有数据都成功发送完毕后,Observable会调用
onComplete
方法,表示数据流的结束。
- 当Observable发出数据时,它会调用Observer的
-
数据处理逻辑:
- Observer在接收到数据后,会在
onNext
方法中处理这些数据。 - 如果发生错误,
onError
方法会处理错误情况。 onComplete
方法用于处理数据流结束后的清理工作。
- Observer在接收到数据后,会在
-
线程切换:
- onNext、OnError等方法执行线程默认在上游发送线程,假设Observable.just在主线程,onNext也在主线程执行。
- 假设设置了subscribeOn(Schedulers.io())但未设置observeOn,那onNext在即在IO线程执行。(订阅影响了上游发送线程,间接影响)
- 假设设置observeOn(AndroidSchedulers.mainThread()),那onNext则在主线程执行,
以observeOn为准
。 - 多次调用,subscribeOn只有第一次设置有效,以后调用均被忽略。observeOn每次调用都有效,影响其下游的操作和回调线程。
- 如需确保onNext在特定线程触发,建议使用ObservableOn设置线程。
- 看文章不如自己动手试试,给大家一个例子,大家可以试试。
java
Observable.just(1, 2, 3)
.subscribeOn(Schedulers.io()) // 有效:在 IO 线程发送数据
.subscribeOn(Schedulers.computation()) // 被忽略
.observeOn(Schedulers.computation()) // map 在计算线程
.map(value -> value * 2)
.observeOn(AndroidSchedulers.mainThread()) // subscribe 在主线程
.subscribeOn(Schedulers.single()) // 被忽略
.subscribe(value -> System.out.println("Value: " + value + ", Thread: " + Thread.currentThread().getName());
示例代码(对应流程图):
java
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.observers.DisposableObserver;
public class RxJavaExample {
public static void main(String[] args) {
// 创建Observable数据源
Observable<Integer> observable = Observable.just(1, 2, 3, 4, 5);
// 创建Observer(观察者)
DisposableObserver<Integer> observer = new DisposableObserver<Integer>() {
@Override
public void onNext(Integer value) {
System.out.println("Received: " + value);
}
@Override
public void onError(Throwable e) {
System.err.println("Error: " + e.getMessage());
}
@Override
public void onComplete() {
System.out.println("Completed");
}
};
// 订阅操作
observable.subscribe
概念简单的讲完了,直接开启主体,先使用再学习。在使用中学习比背诵概念更加深刻。
1、需求:搜索框搜索
产品经理突然找到你,输入框要实现实时搜索,不需要搜索按钮了。你灵光一闪,可以监听EditText的输入,有了变化afterTextChanged()会触发回调,这个思路没什么问题,于是你开干了。可是测试却找来了,你的接口调用太频繁了,触发限流了。好家伙,搜索接口一秒调用了好几次。那我们是不是可以等待用户停止输入后再触发
了?
解决方案:Rxjava debounce操作符
java
RxTextView.textChanges(usernameEditText)
.skipInitialValue() // 跳过初始空值
.debounce(300, TimeUnit.MILLISECONDS) // 300ms 防抖
.map(CharSequence::toString)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(username -> {
// 在这里处理获取到的 usernameEditText 的值
Log.d("Username", username); // 打印用户名);}
进阶:现在有两个输入框,用户名和密码,实时校验是否满足条件,满足条件后登录按钮可
java
// 监听用户名和密码输入
Observable<String> usernameObservable = RxTextView.textChanges(usernameEditText)
.skipInitialValue() // 跳过初始空值
.debounce(300, TimeUnit.MILLISECONDS) // 300ms 防抖时间
.map(CharSequence::toString)
.observeOn(AndroidSchedulers.mainThread());
Observable<String> passwordObservable = RxTextView.textChanges(passwordEditText)
.skipInitialValue() // 跳过初始空值
.debounce(300, TimeUnit.MILLISECONDS) // 300ms 防抖时间
.map(CharSequence::toString)
.observeOn(AndroidSchedulers.mainThread());
// 组合用户名和密码,检查长度条件
ompositeDisposable.add(
Observable.combineLatest(usernameObservable, passwordObservable, (username, password) ->
username.length() > 6 && password.length() > 6
)
.distinctUntilChanged() // 避免重复发射相同状态
.subscribe(
isValid -> loginButton.setEnabled(isValid),
throwable -> Toast.makeText(this, "Error: " + throwable.getMessage(), Toast.LENGTH_SHORT).show()
)
);
扩展:RxTextView 是属于Rxjava3的扩展库Rxbinding。允许你以 RxJava 的方式订阅 TextView 的事件(如文本变化、点击、焦点变化等),将其转换为 Observable 或 Flowable 数据流。 简单理解为:这是rxjava 帮你封装一些TextView、EditText常用方法
,你要是不想用,发射数据部分(RxTextView.textChanges(usernameEditText)
)也可以使用如下代码代替:
java
public static Observable<String> textChanges(EditText editText) {
return Observable.create(emitter -> {
// 定义 TextWatcher
TextWatcher watcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if (!emitter.isDisposed()) {
emitter.onNext(s.toString());
}
}
};
// 添加监听器
editText.addTextChangedListener(watcher);
// 当 Observable 取消订阅时移除监听器
emitter.setCancellable(() -> editText.removeTextChangedListener(watcher));
});
}
2、需求:多接口数据合并获取
你想根据用户id获取用户的个人信息、会员等级信息、消费偏好,可是这竟然分成了三个接口。那就调用了三次吧,每次调用后重新刷新首页。这没啥大不了的。可是有个按钮的显示由这三个接口的某些值共同决定,某个接口返回延迟了,这个按钮显示过段时间才消失,测试来找你解决,好尴尬啊。
解决方案:zip操作符
java
String userId = "12345"; // 假设用户 ID
// 模拟三个接口请求
Single<UserInfo> userInfoObservable = fetchUserInfo(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Single<MembershipLevel> membershipLevelObservable = fetchMembershipLevel(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Single<PurchasePreference> purchasePreferenceObservable = fetchPurchasePreference(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
// 使用 zip 组合三个数据流
Single.zip(
userInfoObservable,
membershipLevelObservable,
purchasePreferenceObservable,
(userInfo, membershipLevel, purchasePreference) ->
new UserProfile(userInfo, membershipLevel, purchasePreference)
)
.subscribe(
profile -> profileTextView.setText(profile.toString()),
throwable -> Toast.makeText(this, "Error: " + throwable.getMessage(), Toast.LENGTH_LONG).show(),
() -> Toast.makeText(this, "Data loaded successfully", Toast.LENGTH_SHORT).show()
)
3、多接口数据串行获取
需求:根据id获取用户信息,在根据用户信息获取订单,再根据订单获取订单详情?回调嵌套,下面的方式更合适。
less
int userId = 123;
// 使用 RxJava 链式调用
Observable.just(userId)
.subscribeOn(Schedulers.io()) // 在 IO 线程执行
.flatMap(fetchUserInfo) // 获取用户信息
.flatMap(userInfo -> fetchOrderList(userInfo)) // 获取订单列表
.flatMapObservable(orderList -> Observable.fromIterable(orderList)) // 将订单列表转换为单个订单流
.flatMap(order -> fetchOrderDetails(order.getId())) // 获取每个订单的详情
.observeOn(Schedulers.newThread()) // 切换到新的线程来处理结果
.subscribe(orderDetail -> System.out.println("Order details: " + orderDetail));
4、定时器
现在有个需求,每隔5s检查发送一次状态。你是不是有以下方案:
- Handler + postdelay
- Timer + TimerTask
- AlarmManager
- JobScheduler 这些都能实现,具体的应用场景和差异就不阐述了。其实RxJava也可以实现。 请看下面示例:
java
Observable.interval(5, TimeUnit.SECONDS, Schedulers.io())
.flatMap(tick -> Observable.fromCallable(() -> reportStatus())
.onErrorReturn(throwable -> {
System.err.println("Error: " + throwable.getMessage());
return "Error";
}))
.subscribe(
result -> System.out.println("Status reported: " + result),
() -> System.out.println("Completed")
);
那要是单次了,也就是延迟5s后执行方法,那就使用Timer
操作符,将interval改为timer即可,这边不再赘述。 此处不阐述Observable.fromCallable、onErrorReturn操作的使用,它适用于那些可能抛出异常、需要延迟执行的操作。我们假设该接口可能会发生报错。
5、限流--(点击事件防抖)
有个数据监听接口,回调上报过于频繁,App消耗了大量资源去处理,其实你只要每分钟获取一次上报即可。
java
Observable.interval(1000, TimeUnit.MILLISECONDS)
.throttleFirst(1, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aLong -> {
Log.d("test", aLong);
});
作用:如果原始流每秒发射一次(0, 1, 2, 3...),那么 throttleFirst(1, MINUTES) 只让第 0 个发射出去,接下来的 59 秒内的所有事件都被忽略,直到第 60 秒才允许下一个事件通过。每分钟只允许第一个发送出去,作用如下
- 防止用户频繁触发某个操作(如刷新定位、同步数据)
- 控制后台轮询频率,避免资源浪费
- 高频传感器数据采样降频处理 这时候你是不是有点迷糊,throttleFirst和debounce到底有啥区别啊,他们作用有点像啊,请看下表。
操作符 | 行为 | 发射时机 | 典型用途 |
---|---|---|---|
throttleFirst | 时间窗口内只允许第一个事件通过 | 第一个事件 | 防止重复触发(如按钮 ) |
throttleLast / sample | 每个时间窗口结束时发射最后一个事件 | 时间窗口结束时 | 数据采样、传感器更新 |
debounce | 若有新事件则重置计时,否则超时后发射 | 最后一个稳定事件 | 输入防抖、网络请求防抖 |
throttleLatest | 窗口开始时立即发射第一个事件,窗口结束时发射最新事件(若存在) | 时间窗口开始和结束 | 快速响应用户输入,同时在窗口结束时更新最新输入 |
注意:"sample"和"throttleLast"本质是同一个操作符不同别名,两者参数和效果一致,sample 是更早期的命名,throttleLast 是为了与 throttleFirst、throttleLatest 等保持命名一致
6、过滤
需求如下:手机加速度等传感器检测用户走路情况,但部分数据过于离谱,如果并入计算将产生误差,现需要将异常数据剔除。
解决方案:操作符fliter
java
/**
1、使用Flowable.create创建一个背压策略为LATEST的Flowable,调用getWalkInfo方法发送步行数据。
2、使用subscribeOn(Schedulers.io())指定上游在IO线程执行。
3、使用observeOn(AndroidSchedulers.mainThread())指定下游在主线程执行。
4、通过filter过滤出有效的步行数据(isVaild()为true)。
5、最后通过subscribe订阅并打印有效数据到日志。
*/
Flowable.create(emitter -> getWalkInfo(emitter::onNext), BackpressureStrategy.LATEST)
.subscribeOn(Schedulers.io())
.onBackpressureBuffer(100)
.observeOn(AndroidSchedulers.mainThread())
.filter(walkInfo -> walkInfo.isVaild())
.subscribe({
validwalkInfo -> Log.d("有效步行数据", "getWalkInfo: " + walkInfo)
});
7、背压
需求如下:某个传感器打开后的前几秒钟,产生了很多数据,APP处理不过来,现在有一大堆数据等待处理,OOM了啊,那怎么办?有了,限流啊,把他们大部分都筛掉。可是这些数据都很重要,不能丢啊。那咋办。
解决方案:Flowable 示例:
java
Flowable.create(emitter -> {
for (int i = 1; i <= 100; i++) {
emitter.onNext(i);
Thread.sleep(1); // 模拟高频生产
}
emitter.onComplete();
}, BackpressureStrategy.LATEST) // 必须指定策略,这里用 LATEST,要是超出直接保留最新值
.observeOn(Schedulers.computation())
.subscribe(new Subscriber<Integer>() {
private Subscription subscription;
@Override
public void onSubscribe(Subscription s) {
subscription = s;
s.request(5); // 请求 5 条数据
}
@Override
public void onNext(Integer value) {
try {
Thread.sleep(100); // 模拟慢速消费
if (value % 5 == 0) {
subscription.request(5); // 再请求 5 条
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
注意:背压重点在于积压数据的处理的方式,溢出的数据是抛弃还是缓存最新。不建议缓存所有数据,除非你确认数据不会溢出
。
8、更多操作符相关
- Single和Observable的区别
操作符 | 定义 | 发射数量 | 完成状态 | 如何发送 |
---|---|---|---|---|
Single | 只发射单个数据或错误的响应式流 | 1 | 发射后自动结束 | onSuccess、onError |
Observable | 发射零个、一个或多个数据的响应式流 | 0、1、多个 | 显式调用 onComplete或 onError结束 | onNext()、onComplete、OnError |
这两个操作符使用方法类似,区别最大发送个数,单次使用获取一个接口数据或者数据库数据,优先使用Single,需要持续发送数据请使用Observable。
- Flowable
背压操作符,可与Observable互相转化 - 热流
上述创建全部是冷流,热流也很常见。两者的区别如下,总结一点,热流只接受订阅后的数据发送,而冷流订阅号会进行数据重新生成发送。我们再直白一点,热流相当于看直播,你只能看到打开后的画面。冷流相当于点播,你可以从头看起
。
!类型 | 定义 | 生成时机 | 订阅行为 | 例子 |
---|---|---|---|---|
冷流 | 数据流在每次订阅时从头开始生成数据,每个订阅者独立接收完整数据序列 | 订阅时生成数据,数据生成过程可重复。 | 每个订阅者获取独立的数据流,互不影响 | Observable.just, Observable.fromIterable, Flowable.create |
热流 | 数据流独立于订阅者持续发射数据,订阅者只接收订阅后发射的数据。 | 数据生成与订阅无关,实时发射(如事件或状态) | 所有订阅者共享同一数据流,接收订阅时刻之后的数据。 | Subject(如 PublishSubject)、ConnectableObservable |
集帅们,看到热流的订阅-发送的机制有没有眼熟,像不像LiveDataBus事件总线。是的,它也可以实现这样的功能,且功能更加丰富,但也有缺点,需要手动管理生命周期吗,最重要的一点,学习成本稍高
。同样的也有同学尝试使用Koltin Flow代替LiveDataBus,大同小异。
- Rxjava和 Flow的对比
使用Kotlin Flow操作符的同学会发现,两者有很多相似之处,同样也有热冷流的概念,很多操作符都有对应的实现,实现的效果也基本一致。Rxjava操作符更加丰富,适用Java和Android,Flow脱胎于Kotlin的原生支持,跟生命周期高度融合,更加友好。 - 取消订阅
差点忘了这件事,当我们离开订阅所在页面,或者不再需要了,要及时移除订阅,防止内存泄漏。 Disposable disposable = Observable.interval(100, TimeUnit.MILLISECONDS); disposable.dispose();如果有多个订阅,也可以使用compositeDisposable.add(dispose)统一管理。解除的时候compositeDisposable.dispose()即可。
9、写在最后
Rxjava实在是太复杂了,再来几篇文章也讲不完,也有很多的语法糖操作符,很多操作符我也没怎么用过,就不献丑了。先记住文章列出的场景吧,覆盖了大多数的使用情况。剩下再慢慢研究吧。先把代码的逼格搞起来,至于其他的,慢慢来嘛。 同时文章中部分代码只截取了关键部分,完整代码实在是太长了。