RxJava:响应式编程

本文深入讲解ReactiveX旗下的RxJava框架,从核心概念到实战应用,帮助你掌握响应式编程的精髓。

一、引言:为什么选择RxJava

在现代软件开发中,异步编程已成为必不可少的技能。传统的回调、线程管理复杂等问题一直困扰着开发者。RxJava(Reactive Extensions for Java)提供了一种优雅的解决方案,让异步编程变得简单而强大。

RxJava的核心优势

复制代码
声明式编程 - 以数据流的方式描述业务逻辑
链式调用 - 操作符组合,代码简洁易读
强大的操作符 - 100+操作符满足各种场景
线程调度灵活 - 轻松切换执行线程
错误处理优雅 - 统一的异常处理机制
背压支持 - Flowable处理数据流速控制

二、核心架构解析

RxJava基于观察者模式,采用响应式编程思想,核心由四大组件构成。

核心组件详解

Observable(被观察者)

数据源,负责发射数据流。Observable是整个响应式流的起点,它负责产生数据并发送给订阅者。

scss 复制代码
publicabstractclassObservable<T> 
    implementsObservableSource<T> {

    publicfinalvoidsubscribe(
        Observer<? super T> observer) {
        subscribeActual(observer);
    }
    protectedabstractvoidsubscribeActual(
        Observer<? super T> observer);
}

Observer(观察者)

数据消费者,接收并处理数据。Observer定义了四个核心方法来处理数据流的不同状态。

csharp 复制代码
public interface Observer<T> {
    // 订阅开始
    void onSubscribe(Disposable d);

    // 接收数据
    void onNext(T t);

    // 发生错误
    void onError(Throwable e);

    // 完成
    void onComplete();
}

Operators(操作符)

用于转换、过滤、组合数据流的中间操作。操作符是RxJava的精髓,提供了丰富的数据处理能力。

Schedulers(调度器)

控制Observable和Observer在哪个线程执行,实现灵活的线程调度。

数据流类型

RxJava 3提供了5种主要的数据流类型,每种类型适用于不同的场景:

vbnet 复制代码
Observable - 发射0到N个数据项,不支持背压,适用于通用场景
Flowable - 发射0到N个数据项,支持背压,适用于大数据量场景
Single - 只发射1个数据项,适用于单一结果场景如网络请求
Maybe - 发射0或1个数据项,适用于可能有结果的场景
Completable - 不发射数据,只关心完成状态

三、快速上手:基础用法

创建Observable的多种方式

方式1:just - 发射固定的数据项

arduino 复制代码
Observable<String> observable1 = 
    Observable.just("Hello", "RxJava");

方式2:from - 从数组或集合创建

ini 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Observable<Integer> observable2 = 
    Observable.fromIterable(numbers);

方式3:create - 自定义发射逻辑

ini 复制代码
Observable<String> observable3 = 
    Observable.create(emitter -> {
        emitter.onNext("Data 1");
        emitter.onNext("Data 2");
        emitter.onComplete();
    });

方式4:interval - 定时发射

ini 复制代码
Observable<Long> observable4 = 
    Observable.interval(1, TimeUnit.SECONDS);

方式5:range - 发射一系列整数

ini 复制代码
Observable<Integer> observable5 = 
    Observable.range(1, 10);

订阅Observable

完整的订阅示例:

typescript 复制代码
Observable.just("Apple", "Banana", "Orange")
    .subscribe(new Observer<String>() {
        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("开始订阅");
        }
        @Override
        public void onNext(String item) {
            System.out.println("收到数据: " + item);
        }
        @Override
        public void onError(Throwable e) {
            System.err.println("发生错误: " 
                + e.getMessage());
        }
        @Override
        public void onComplete() {
            System.out.println("完成");
        }
    });

输出结果:

makefile 复制代码
开始订阅收到数据: Apple收到数据: Banana收到数据: Orange完成

简化的订阅方式

实际开发中,我们经常使用Lambda表达式简化订阅代码:

go 复制代码
// 只处理onNext
Observable.just(1, 2, 3)
    .subscribe(item -> System.out.println(item));
// 处理onNext和onError
Observable.just(1, 2, 3)
    .subscribe(
        item -> System.out.println(item),
        error -> System.err.println(error)
    );
// 处理onNext、onError和onComplete
Observable.just(1, 2, 3)
    .subscribe(
        item -> System.out.println(item),
        error -> System.err.println(error),
        () -> System.out.println("完成")
    );

四、操作符详解:数据变换的艺术

操作符是RxJava的核心,掌握常用操作符是使用RxJava的关键。

创建操作符

创建操作符用于生成Observable数据源:

scss 复制代码
// just - 发射预定义的数据项
Observable.just(1, 2, 3);
// fromArray - 从数组创建
Integer[] array = {1, 2, 3, 4, 5};
Observable.fromArray(array);
// fromCallable - 延迟执行
Observable.fromCallable(() -> {
    // 只有订阅时才执行
    return expensiveOperation();
});
// defer - 为每个订阅者创建新的Observable
Observable.defer(() -> 
    Observable.just(System.currentTimeMillis()));

转换操作符

map - 一对一转换

最常用的转换操作符,将每个数据项转换为另一种形式:

scss 复制代码
Observable.just(1, 2, 3, 4, 5)
    .map(number -> number * 2)
    .subscribe(System.out::println);
// 输出: 2, 4, 6, 8, 10

flatMap - 一对多转换并合并

适用于需要为每个数据项发起异步操作的场景:

kotlin 复制代码
// 场景:搜索用户并获取每个用户的订单
Observable.just("user1", "user2", "user3")
    .flatMap(userId -> {
        // 为每个用户发起网络请求
        return getUserOrders(userId);
    })
    .subscribe(order -> 
        System.out.println(order));

concatMap - 顺序的flatMap

当需要保证执行顺序时使用concatMap:

scss 复制代码
Observable.just(1, 2, 3)
    .concatMap(i -> Observable.just(i * 10)
        .delay(100, TimeUnit.MILLISECONDS))
    .subscribe(System.out::println);
// 输出严格按顺序: 10, 20, 30

过滤操作符

过滤操作符用于筛选数据流中的数据:

scss 复制代码
// filter - 过滤数据
Observable.range(1, 10)
    .filter(number -> number % 2 == 0)
    .subscribe(System.out::println);
// 输出: 2, 4, 6, 8, 10
// take - 只取前N个
Observable.range(1, 100)
    .take(5)
    .subscribe(System.out::println);
// 输出: 1, 2, 3, 4, 5
// skip - 跳过前N个
Observable.range(1, 10)
    .skip(5)
    .subscribe(System.out::println);
// 输出: 6, 7, 8, 9, 10
// distinct - 去重
Observable.just(1, 2, 2, 3, 3, 4)
    .distinct()
    .subscribe(System.out::println);
// 输出: 1, 2, 3, 4

debounce - 防抖(搜索框常用)

在用户停止输入一段时间后才触发搜索,避免频繁请求:

scss 复制代码
Observable.create(emitter -> {
    emitter.onNext("a");
    Thread.sleep(100);
    emitter.onNext("ab");
    Thread.sleep(100);
    emitter.onNext("abc");
    Thread.sleep(500); // 停顿超过300ms
    emitter.onComplete();
})
.debounce(300, TimeUnit.MILLISECONDS)
.subscribe(System.out::println);
// 只输出: abc

组合操作符

组合操作符用于合并多个数据源:

ini 复制代码
// merge - 合并多个Observable
Observable<String> obs1 = Observable.just("A", "B");
Observable<String> obs2 = Observable.just("C", "D");
Observable.merge(obs1, obs2)
    .subscribe(System.out::println);
// 输出: A, B, C, D(顺序不保证)
// zip - 组合对应位置的数据
Observable<Integer> numbers = 
    Observable.just(1, 2, 3);
Observable<String> letters = 
    Observable.just("A", "B", "C");
Observable.zip(numbers, letters,
    (number, letter) -> number + letter)
    .subscribe(System.out::println);
// 输出: 1A, 2B, 3C
// combineLatest - 任一数据源变化时组合最新值
Observable<Long> timer1 = 
    Observable.interval(1, TimeUnit.SECONDS);
Observable<Long> timer2 = 
    Observable.interval(2, TimeUnit.SECONDS);
Observable.combineLatest(timer1, timer2,
    (t1, t2) -> "T1:" + t1 + " T2:" + t2)
    .subscribe(System.out::println);

错误处理操作符

优雅的错误处理是RxJava的一大优势:

php 复制代码
// onErrorReturn - 错误时返回默认值
Observable.create(emitter -> {
    emitter.onNext(1);
    emitter.onNext(2);
    emitter.onError(new Exception("出错了"));
})
.onErrorReturn(throwable -> -1)
.subscribe(System.out::println);
// 输出: 1, 2, -1
// onErrorResumeNext - 错误时切换到另一个Observable
Observable.create(emitter -> {
    emitter.onNext("A");
    emitter.onError(new Exception("出错"));
})
.onErrorResumeNext(Observable.just("B", "C"))
.subscribe(System.out::println);
// 输出: A, B, C
// retry - 重试
AtomicInteger count = new AtomicInteger(0);
Observable.create(emitter -> {
    int attempt = count.incrementAndGet();
    System.out.println("尝试次数: " + attempt);
    if (attempt < 3) {
        emitter.onError(new Exception("失败"));
    } else {
        emitter.onNext("成功");
        emitter.onComplete();
    }
})
.retry(3)
.subscribe(System.out::println);

五、线程调度:掌控异步执行

合理的线程调度是保证应用性能的关键。

Schedulers类型

RxJava提供了多种调度器,每种适用于不同的场景:

Schedulers.io() - IO密集型任务

适用于网络请求、文件读写等IO操作:

scss 复制代码
Observable.fromCallable(() -> {
    // 网络请求、文件读写
    return downloadFile();
})
.subscribeOn(Schedulers.io())
.subscribe(result -> System.out.println(result));

Schedulers.computation() - 计算密集型

适用于CPU密集型计算:

scss 复制代码
Observable.range(1, 1000000)
    .map(i -> i * i)
    .subscribeOn(Schedulers.computation())
    .subscribe();

Schedulers.newThread() - 每次创建新线程

scss 复制代码
Observable.just(1)
    .subscribeOn(Schedulers.newThread())
    .subscribe();

Schedulers.single() - 单线程执行

scss 复制代码
Observable.just(1, 2, 3)
    .subscribeOn(Schedulers.single())
    .subscribe();

AndroidSchedulers.mainThread() - Android主线程

在Android开发中更新UI时使用:

arduino 复制代码
Observable.just("更新UI")
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(text -> textView.setText(text));

subscribeOn vs observeOn

理解这两个方法的区别是掌握线程调度的关键:

less 复制代码
Observable.just(1, 2, 3)
    .subscribeOn(Schedulers.io())        
    // 指定数据源在IO线程
    .map(number -> {
        System.out.println("map线程: " +
            Thread.currentThread().getName());
        return number * 2;
    })
    .observeOn(Schedulers.computation()) 
    // 切换到计算线程
    .filter(number -> {
        System.out.println("filter线程: " +
            Thread.currentThread().getName());
        return number > 2;
    })
    .observeOn(Schedulers.newThread())   
    // 再次切换线程
    .subscribe(number -> {
        System.out.println("subscribe线程: " +
            Thread.currentThread().getName());
        System.out.println("结果: " + number);
    });

关键区别:

scss 复制代码
subscribeOn - 指定Observable自身在哪个线程执行(只有第一次有效)
observeOn - 指定后续操作在哪个线程执行(可多次使用)

六、实战应用:结合Retrofit

网络请求 - 结合Retrofit

API接口定义:

less 复制代码
public interface ApiService {
    @GET("users/{id}")
    Single<User> getUser(@Path("id") int userId);
    @GET("users/{id}/posts")
    Observable<List<Post>> getUserPosts(
        @Path("id") int userId);
}

Retrofit配置:

csharp 复制代码
public class NetworkModule {
    private static ApiService apiService;
    public static ApiService getApiService() {
        if (apiService == null) {
            Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.example.com/")
                .addConverterFactory(
                    GsonConverterFactory.create())
                .addCallAdapterFactory(
                    RxJava3CallAdapterFactory.create())
                .build();
            apiService = retrofit.create(ApiService.class);
        }
        return apiService;
    }
}

使用示例:

java 复制代码
public class UserRepository {
    private final ApiService apiService;
    private final CompositeDisposable disposables = 
        new CompositeDisposable();
    public UserRepository() {
        this.apiService = NetworkModule.getApiService();
    }
    // 获取用户信息
    public void loadUser(int userId, 
                         Consumer<User> onSuccess,
                         Consumer<Throwable> onError) {
        Disposable disposable = apiService.getUser(userId)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(onSuccess, onError);
        disposables.add(disposable);
    }
    // 清理订阅
    public void clear() {
        disposables.clear();
    }
}

搜索防抖

实现智能搜索,避免频繁请求:

scss 复制代码
public class SearchActivity extends AppCompatActivity {
    private EditText searchEditText;
    private final CompositeDisposable disposables = 
        new CompositeDisposable();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search);
        searchEditText = 
            findViewById(R.id.search_edit_text);
        // 监听搜索框输入
        Disposable disposable = 
            RxTextView.textChanges(searchEditText)
            .debounce(500, TimeUnit.MILLISECONDS)  
            // 防抖500ms
            .filter(text -> text.length() >= 2)     
            // 至少2个字符
            .distinctUntilChanged()                 
            // 内容变化才触发
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(text -> {
                // 执行搜索
                performSearch(text.toString());
            });
        disposables.add(disposable);
    }
    private void performSearch(String keyword) {
        apiService.search(keyword)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                results -> updateSearchResults(results),
                error -> showError(error)
            );
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        disposables.clear();  // 防止内存泄漏
    }
}

多数据源合并

同时加载多个数据源,等待全部完成后统一处理:

scss 复制代码
public class HomeRepository {
    // 同时加载多个数据源
    public Single<HomeData> loadHomeData() {
        Single<User> userSingle = 
            apiService.getCurrentUser();
        Single<List<Post>> postsSingle = 
            apiService.getLatestPosts();
        Single<List<Notification>> notificationsSingle =
            apiService.getNotifications();
        return Single.zip(userSingle, 
                         postsSingle, 
                         notificationsSingle,
            (user, posts, notifications) ->
                new HomeData(user, posts, notifications))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());
    }
}

定时刷新

实现自动刷新功能:

typescript 复制代码
public class RefreshManager {
    private Disposable refreshDisposable;
    // 每30秒自动刷新
    public void startAutoRefresh(Runnable refreshAction) {
        refreshDisposable = 
            Observable.interval(30, TimeUnit.SECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(tick -> refreshAction.run());
    }
    public void stopAutoRefresh() {
        if (refreshDisposable != null && 
            !refreshDisposable.isDisposed()) {
            refreshDisposable.dispose();
        }
    }
}

七、最佳实践:避坑指南

管理订阅 - 使用CompositeDisposable

正确做法:

scala 复制代码
public class MyActivity extends AppCompatActivity {
    private final CompositeDisposable disposables = 
        new CompositeDisposable();
    private void loadData() {
        Disposable disposable = observable
            .subscribe(data -> process(data));
        disposables.add(disposable);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        disposables.clear();  // 清理所有订阅
    }
}

统一的错误处理

创建一个统一的错误处理器:

java 复制代码
public class RxErrorHandler {
    public static Consumer<Throwable> handleError() {
        return throwable -> {
            if (throwable instanceof IOException) {
                // 网络错误
                showToast("网络连接失败");
            } else if (throwable instanceof HttpException) {
                // HTTP错误
                int code = 
                    ((HttpException) throwable).code();
                showToast("服务器错误: " + code);
            } else {
                // 其他错误
                showToast("发生未知错误");
            }
            // 记录日志
            Log.e("RxError", "Error occurred", throwable);
        };
    }
}
// 使用
observable.subscribe(
    data -> process(data),
    RxErrorHandler.handleError()
);

操作符选择

根据场景选择合适的操作符:

less 复制代码
// flatMap - 不保证顺序,适合并发
Observable.just("A", "B", "C")
    .flatMap(item -> processAsync(item))
    .subscribe();
// concatMap - 保证顺序,适合顺序执行
Observable.just("A", "B", "C")
    .concatMap(item -> processAsync(item))
    .subscribe();
// switchMap - 只保留最新的,适合搜索
searchObservable
    .switchMap(keyword -> apiService.search(keyword))
    .subscribe();

背压处理

处理大数据量时使用Flowable:

scss 复制代码
Flowable.range(1, 1000000)
    .onBackpressureBuffer(100)  // 缓冲区
    .observeOn(Schedulers.computation(), false, 10)
    .subscribe(
        data -> process(data),
        error -> handleError(error)
    );
// 背压策略
Flowable.create(emitter -> {
    for (int i = 0; i < 10000; i++) {
        emitter.onNext(i);
    }
    emitter.onComplete();
}, BackpressureStrategy.DROP)  // 丢弃策略
.subscribe();

八、总结

RxJava核心要点

复制代码
响应式思维 - 以数据流的方式思考问题
操作符链 - 灵活组合操作符完成复杂逻辑
线程调度 - 合理使用subscribeOn和observeOn
订阅管理 - 及时dispose避免内存泄漏
错误处理 - 使用专门的错误处理操作符
背压控制 - 大数据量场景使用Flowable
相关推荐
正在走向自律5 天前
智能体时代:字节跳动Coze平台应用开发完全指南
android·rxjava·知识库·智能体·coze·字节·coze平台
周杰伦_Jay12 天前
【Spring AI】Spring生态AI应用开发框架
人工智能·spring·rxjava
微学AI16 天前
生成式AI应用平台架构设计:ModelEngine核心能力与工程化实践路径
android·人工智能·rxjava
晚霞的不甘17 天前
华为云 DevUI 低代码平台集成实战:从可视化搭建到企业级扩展
低代码·华为云·rxjava
小七有话说20 天前
DevUI与企业级中后台系统融合:低代码表单构建器实战
android·rxjava·devui
7***533424 天前
后端在消息队列中的选型
big data·sqoop·rxjava
Q6882388624 天前
三相APF仿真实战:从谐波检测到SVPWM全解析
rxjava
daidaidaiyu1 个月前
Jetlinks 物联网平台 开源版学习源码分析
java·mqtt·rxjava·tcp