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

相关推荐
安东尼肉店6 小时前
Android compose屏幕适配终极解决方案
android
2501_916007477 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun8 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户20187928316712 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子12 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822712 小时前
安卓接入Max广告源
android
齊家治國平天下12 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO12 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel12 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢12 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱