前言

本章开始 RxJava 的讲解,主要分为三个章节;
- RxJava 应用场景
- RxJava 模式与原理
- RxJava 原理与自定义操作符
首先,开始我们的第一章节:RxJava 应用场景
RxJava 应用场景
第一章节主要分为五部分
- 核心思想
- RxJava 配合 Retrofit
- 防抖
- 网络嵌套
- doOnNext 运用
核心思想
Rx 思维:以下载图片为例,会有一个起点,一个终点,起点获取数据(图片 api 地址)将 Bitmap 流线下来传递给终点,从起点到终点没有断掉,思维链式调用下来,将结构铺平,思维链式中间可以添加任何拦截卡片(所以 RxJava 的响应式编程又叫卡片式编程,起点到终点之间可以添加任何其他业务需求,(例如:给图片加水印,给图片的下载加入日志系统等等));
有一个起点和一个终点,起点开始流向我们的「事件」把事件流向到终点,只不过在流向到过程中,可以增加拦截,拦截时可以对事件进行改变,终点只关心它上一个拦截;
我们来用一个 『下载』的能力是演示 RxJava 的核心思想;
首先,我们来看下传统的下载方式:
ini
public void downloadImageAction(View view) {
progressDialog = new ProgressDialog(this);
progressDialog.setTitle("下载图片中...");
progressDialog.show();
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(PATH);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(5000);
int responseCode = httpURLConnection.getResponseCode(); // 才开始 request
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpURLConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Message message = handler.obtainMessage();
message.obj = bitmap;
handler.sendMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private final Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
Bitmap bitmap = (Bitmap) msg.obj;
image.setImageBitmap(bitmap);
if (progressDialog != null) progressDialog.dismiss();
return false;
}
});
传统方式,使用 new Thread 的方式,进行图片的下载,以及通过 Handler 将下载结果发送到主线程,然后进行展示;
整体来看,零零散散,比较麻烦,那么,我们怎么来规范下它呢,我们可以采用 RX 的思维方式;
java
public void rxJavaDownloadImageAction(View view) {
// 起点
Observable.just(PATH) // 内部会分发PATH,第二步
// TODO 第三步
.map(new Function<String, Bitmap>() {
@Override
public Bitmap apply(String s) throws Exception {
URL url = new URL(s);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(5000);
int responseCode = httpURLConnection.getResponseCode(); // 才开始 request
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpURLConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
return null;
}
})
// 给上面分配异步线程
.subscribeOn(Schedulers.io())
// 给下面分配主线程
.observeOn(AndroidSchedulers.mainThread())
// 订阅 起点 和 终点 订阅起来
.subscribe(
// 终点
new Observer<Bitmap>() {
// 订阅开始
@Override
public void onSubscribe(Disposable d) {
// 预备开始要分发
// 第一步
progressDialog = new ProgressDialog(DownloadActivity.this);
progressDialog.setTitle("download run");
progressDialog.show();
}
// 第四步
// 拿到事件
@Override
public void onNext(Bitmap bitmap) {
image.setImageBitmap(bitmap);
}
// 错误事件
@Override
public void onError(Throwable e) {
}
// 第五步
// 完成事件
@Override
public void onComplete() {
if (progressDialog != null) {
progressDialog.dismiss();
}
}
});
}
将被观察者 Observable (被观察者)与 Observer (观察者)通过 subscribe()
方法进行关联起来;
起点分发一个 String 数据,重点观察到的是一个 Bitmap 数据,中间通过 map 等进行变换;
当我们调用 subscribe
方法之后,整体采用 U 型结构来执行订阅和分发;
第一步:先执行
onSubscribe
方法,订阅开始,我们通常在这里展示 loading;第二步:执行
Observable.just(PATH)
逻辑,将 PATH 分发出去;第三步:执行
map()
操作,卡片式拦截,拦截将 String 转换成目标数据 Bitmap;第四步:执行
onNext()
拿到 Bitmap 事件,执行 UI 的展示;第五步:执行
onComplete()
完成事件,我们通常在这里隐藏 loading;
这种 RX 思维,不会让我们整体流程中断,从起点到终点的任何一个过程中,我们都可以添加变化的卡片,例如:我们需要给下载的图片添加一个水印,那么我们只需要再添加一个 map
操作,来将下载下来的 bitmap 转换成带有水印的 bitmap
java
// 添加水印
.map(new Function<Bitmap, Bitmap>() {
@Override
public Bitmap apply(Bitmap bitmap) throws Exception {
Paint paint = new Paint();
paint.setTextSize(88);
paint.setColor(Color.RED);
return drawTextToBitmap(bitmap, "添加水印...",paint, 88 , 88);
}
})
drawTextToBitmap 是一个具体加水印的方法,这里不是重点
ini
private final Bitmap drawTextToBitmap(Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) {
Bitmap.Config bitmapConfig = bitmap.getConfig();
paint.setDither(true); // 获取跟清晰的图像采样
paint.setFilterBitmap(true);// 过滤一些
if (bitmapConfig == null) {
bitmapConfig = Bitmap.Config.ARGB_8888;
}
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
canvas.drawText(text, paddingLeft, paddingTop, paint);
return bitmap;
}
变换封装
java
public final static <UD> ObservableTransformer<UD, UD> transformer() {
return new ObservableTransformer<UD, UD>() {
@Override
public ObservableSource<UD> apply(Observable<UD> upstream) {
return upstream.subscribeOn(Schedulers.io()) // 给上面代码分配异步线程
.observeOn(AndroidSchedulers.mainThread()) // 给下面代码分配主线程;
.map(new Function<UD, UD>() {
@Override
public UD apply(UD ud) throws Exception {
return ud;
}
});
}
};
}
RxJava 配合 Retofit
Retrofit 的基础使用这里就不做介绍了, Retrofit 本质上就是 OKHttp 的封装,并且它也支持了 RxJava,我们具体来看下是如何支持的,我们就以 玩Android 为例子吧;
less
public interface WanAndroidApi {
// 总数据
@GET("project/tree/json")
Observable<ProjectBean> getProject();
// Item 数据
@GET("project/list/{pageIndex}/json")
Observable<ProjectItem> getProjectItem(@Path("pageIndex") int pageIndex, @Query("cid") int cid);
}
接下来我们需要一个 Retrofit 管理类,RetrofitUtils
arduino
public class RetrofitUtils {
private static final String TAG = "HttpUtils";
/**
* 默认环境
*/
public static String BASE_URL = "https://www.wanandroid.com/";
public static void setBaseUrl(String baseUrl) {
BASE_URL = baseUrl;
}
/**
* 根据各种配置创建出 Retrofit
*
* @return 返回创建好的 Retrofit
*/
public static Retrofit getRetrofit() {
// OKHttp客户端
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
// 各种参数配置
OkHttpClient okHttpClient = httpBuilder
.addNetworkInterceptor(new StethoInterceptor())
.readTimeout(10000, TimeUnit.SECONDS)
.connectTimeout(10000, TimeUnit.SECONDS)
.writeTimeout(10000, TimeUnit.SECONDS)
.build();
return new Retrofit.Builder().baseUrl(BASE_URL)
// 请求用 OKhttp
.client(okHttpClient)
// 响应RxJava
// 添加一个 json 解析的工具
.addConverterFactory(GsonConverterFactory.create(new Gson()))
// 添加 Rxjava 处理工具
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
}
然后我们就可以在 Activity 中调用这个 API 来完成网络请求;
scala
public class UseActivity extends AppCompatActivity {
private WanAndroidApi api;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_use);
api = RetrofitUtils.getRetrofit().create(WanAndroidApi.class)
}
public void getProjectAction(View view) {
// 获取网络API
api.getProject()
.subscribeOn(Schedulers.io()) // 上面异步
.observeOn(AndroidSchedulers.mainThread()) // 下面主线程
.subscribe(new Consumer<ProjectBean>() {
@Override
public void accept(ProjectBean projectBean) throws Exception {
Log.d(TAG, "accept: " + projectBean);
}
});
}
}
点击事件中,触发网络请求;
RxJava 配合 Retrofit 的时候,通常用来处理返回结果,也就是对 response 进行 gson,xml 等格式化处理,并将格式化之后的结果返回给主线程进行业务处理;
防抖
抖动说的是:短时间内多次请求,就会造成抖动;
arduino
implementation 'com.jakewharton.rxbinding2:rxbinding:xxxx' // 操作功能防抖
那么如何处理这种抖动呢?我们可以借助于 RxBinding,对于短时间内的多次点击,只响应一次;
scss
RxView.clicks(view)
.throttleFirst(2000, TimeUnit.MILLISECONDS)
.subscribe{...}
通过 throttleFirst
来解决多次点击的问题,2s 内点击多次,只响应一次;
网络嵌套
我们接下来实现联动查询的功能;
java
private void antiShakeActon() {
// 注意:(项目分类)查询的id,通过此id再去查询(项目列表数据)
Button bt_anti_shake = findViewById(R.id.bt_anti_shake);
RxView.clicks(bt_anti_shake)
.throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒钟之内 响应你一次
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
api.getProject() // 查询主数据
.compose(DownloadActivity.rxud())
.subscribe(new Consumer<ProjectBean>() {
@Override
public void accept(ProjectBean projectBean) throws Exception {
for (ProjectBean.DataBean dataBean : projectBean.getData()) {
// 查询item数据
api.getProjectItem(1, dataBean.getId())
.compose(DownloadActivity.rxud())
.subscribe(new Consumer<ProjectItem>() {
@Override
public void accept(ProjectItem projectItem) throws Exception {
Log.d(TAG, "accept: " + projectItem); // 可以UI操作
}
});
}
}
});
}
});
}
可以看到,我们每次都是在 accept
的回调方法中进行下一步的操作,这样的写法看起来太嵌套了,这才两次网络请求,如果是多个的网络请求,那么这样的回调地狱式的处理就要被打了;
RxJava 为了解决回调地狱,保证链式结果的铺平,提供了 flatMap 操作符;
我们使用 flatMap 来看下如何将结构铺平,并解决这个回调地狱的问题;
java
//
private void antiShakeActonUpdate() {
// 注意:项目分类查询的id,通过此id再去查询(项目列表数据)
// 对那个控件防抖动?
Button bt_anti_shake = findViewById(R.id.bt_anti_shake);
RxView.clicks(bt_anti_shake)
.throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒钟之内 响应你一次
// 我只给下面 切换 异步
.observeOn(Schedulers.io())
.flatMap(new Function<Object, ObservableSource<ProjectBean>>() {
@Override
public ObservableSource<ProjectBean> apply(Object o) throws Exception {
return api.getProject(); // 主数据
}
})
.flatMap(new Function<ProjectBean, ObservableSource<ProjectBean.DataBean>>() {
@Override
public ObservableSource<ProjectBean.DataBean> apply(ProjectBean projectBean) throws Exception {
return Observable.fromIterable(projectBean.getData());
}
})
.flatMap(new Function<ProjectBean.DataBean, ObservableSource<ProjectItem>>() {
@Override
public ObservableSource<ProjectItem> apply(ProjectBean.DataBean dataBean) throws Exception {
return api.getProjectItem(1, dataBean.getId());
}
})
.observeOn(AndroidSchedulers.mainThread()) // 给下面切换主线程
.subscribe(new Consumer<ProjectItem>() {
@Override
public void accept(ProjectItem projectItem) throws Exception {
Log.d(TAG, "accept: " + projectItem);
}
});
}
变换之后,清爽了很多了;
所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列;
flatMap()
中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber
的回调方法中;

flatMap 的原理:
- 使用传入的事件对象创建一个 Observable 对象;
- 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
- 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法;
总结:把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat;
doOnNext 运用
假设我们有这样一个需要频繁切换 UI 和非 UI 的场景,例如:新用户注册,注册成功之后更新注册成功的 UI,并立马执行登录,并在登录成功之后更新登录成功的 UI;
我们可以借助 doOnNext()
来实现,本质上就是:在最终的 onNext 之前,做一些其他主线程的更新 UI 处理;
也就是在登录成功的 onNext 之前处理注册成功的 UI;
typescript
public void request2(View view) {
/**
* 一行代码 实现需求
* 需求:
* 还有弹出加载
* * 1.请求服务器注册操作
* * 2.注册完成之后,更新注册UI
* * 3.马上去登录服务器操作
* * 4.登录完成之后,更新登录的UI
*/
RetrofitUtils.getRetrofit().create(IReqeustNetwor.class)
.registerAction(new RegisterRequest()) // 1.请求服务器注册操作
.subscribeOn(Schedulers.io()) // 给上面异步线程
.observeOn(AndroidSchedulers.mainThread()) // 给下面分配主线程
.doOnNext(new Consumer<RegisterResponse>() {
@Override
public void accept(RegisterResponse registerResponse) throws Exception {
// 2.注册完成之后,更新注册UI
Log.d(TAG, "注册完成,更新注册 UI")
}
})
// 3.马上去登录服务器操作
.observeOn(Schedulers.io()) // 给下面分配了异步线程
.flatMap(new Function<RegisterResponse, ObservableSource<LoginResponse>>() { // 4.执行登录的逻辑
@Override
public ObservableSource<LoginResponse> apply(RegisterResponse registerResponse) throws Exception {
Observable<LoginResponse> loginResponseObservable = RetrofitUtils.getRetrofit().create(IReqeustNetwor.class)
.loginAction(new LoginReqeust());
return loginResponseObservable;
}
})
.observeOn(AndroidSchedulers.mainThread()) // 给下面执行主线程
.subscribe(new Observer<LoginResponse>() {
// 一定是主线程,为什么,因为 subscribe 马上调用 onSubscribe
@Override
public void onSubscribe(Disposable d) {
progressDialog = new ProgressDialog(RequestActivity.this);
progressDialog.show();
// UI 操作
disposable = d;
}
@Override
public void onNext(LoginResponse loginResponse) {
// 5.登录完成之后,更新登录的UI
Log.d(TAG, "登录成功,更新登录 UI")
}
@Override
public void onError(Throwable e) {
}
// 6.最终的结束回调
@Override
public void onComplete() {
// 最终章
if (progressDialog != null) {
progressDialog.dismiss();
}
}
});
}
背压
当数据流通过异步的步骤执行时,这些步骤的执行速度可能不一致。也就是说上流数据发送太快,下流没有足够的能力去处理。为了避免这种情况,一般要么缓存上流的数据,要么抛弃数据。但这种处理方式,有时会带来很大的问题;
为此,RxJava带来了backpressure的概念。背压是一种流量的控制步骤,在不知道上流还有多少数据的情形下控制内存的使用,表示它们还能处理多少数据;
支持背压的有 Flowable 类,不支持背压的有 Observable ,Single , Maybe and Completable 类;