从Android实际应用场景出发,讲述RxJava3的简单使用

本文不讨论方案的优缺点,毕竟方案各有千秋,也不大篇幅展开讨论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)    |
+-----------------+

详细步骤说明:

  1. Observable(被观察者)创建数据源

    • Observable是数据的源头,它定义了如何生成或获取数据。常见的操作符有Observable、Single、PublishSubject、Flowable等等
  2. (可选)创建Operator(s)

    • Operator(操作符)是RxJava中的中间件,用于对Observable发出的数据进行转换、过滤、合并等操作。常见的操作符有map, filter, flatMap等。
  3. Subscribe(订阅操作)

    • Observer(观察者)通过调用Observable的subscribe方法来订阅数据。在这一步,Observer将自己的回调方法(如onNext, onError, onComplete)注册到Observable上。
  4. Observer/Subscriber(观察者)接收并处理数据

    • 当Observable发出数据时,它会调用Observer的onNext方法。
    • 如果发生错误,Observable会调用onError方法,并终止数据流。
    • 当所有数据都成功发送完毕后,Observable会调用onComplete方法,表示数据流的结束。
  5. 数据处理逻辑

    • Observer在接收到数据后,会在onNext方法中处理这些数据。
    • 如果发生错误,onError方法会处理错误情况。
    • onComplete方法用于处理数据流结束后的清理工作。
  6. 线程切换

    • 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实在是太复杂了,再来几篇文章也讲不完,也有很多的语法糖操作符,很多操作符我也没怎么用过,就不献丑了。先记住文章列出的场景吧,覆盖了大多数的使用情况。剩下再慢慢研究吧。先把代码的逼格搞起来,至于其他的,慢慢来嘛。 同时文章中部分代码只截取了关键部分,完整代码实在是太长了。

相关推荐
千里马学框架12 分钟前
User手机上如何抓取界面的布局uiautomatorviewer
android·智能手机·aosp·uiautomator·布局抓取·user版本
阿巴~阿巴~39 分钟前
操作系统核心技术剖析:从Android驱动模型到鸿蒙微内核的国产化实践
android·华为·harmonyos
hsx6661 小时前
使用 MaterialShapeDrawable 自定义各种形状的 View
android
用户2018792831672 小时前
滑动城堡的奇妙管家 ——ViewPager故事
android
用户2018792831672 小时前
📜 童话:魔法卷轴与 ScrollView 的奥秘
android
??? Meggie4 小时前
【SQL】使用UPDATE修改表字段的时候,遇到1054 或者1064的问题怎么办?
android·数据库·sql
用户2018792831674 小时前
代码共享法宝之maven-publish
android
yjm4 小时前
从一例 Lottie OOM 线上事故读源码
android·app
用户2018792831674 小时前
浅谈View的滑动
android
用户2018792831675 小时前
舞台剧兼职演员Dialog
android