RxJava系列番外篇:一个RxJava解决复杂业务逻辑的案例
之前写过一系列RxJava的文章,也承诺过会尽快有RxJava2的介绍。无奈实际项目中还未真正的使用RxJava2,不敢妄动笔墨。所以这次还是给大家分享一个使用RxJava1解决问题的案例,希望对大家在使用RxJava的时候有一点点启发。对RxJava还不了解的同学可以先去看看我之前的RxJava系列文章:
- RxJava系列1(简介)
- RxJava系列2(基本概念及使用介绍)
- RxJava系列3(转换操作符)
- RxJava系列4(过滤操作符)
- RxJava系列5(组合操作符)
- RxJava系列6(从微观角度解读RxJava源码)
- RxJava系列7(最佳实践)
业务场景
拿MinimalistWeather这个开源的天气App来举例:
进入App首页后,首先我们需要从数据库中获取当前城市的天气数据,如果数据库中存在天气数据则在UI页面上展示天气数据;如果数据库中未存储当前城市的天气数据,或者已存储的天气数据的发布时间相比现在已经超过了一小时,并且网络属于连接状态则调用API从服务端获取天气数据。如果获取到到的天气数据发布时间和当前数据库中的天气数据发布时间一致则丢弃掉从服务端获取到的天气数据,如果不一致则更新数据库并且在页面上展示最新的天气信息。(同时天气数据源是可配置的,可选择是小米天气数据源还是Know天气数据源)
解决方案
首先我们需要创建一个从数据库获取天气数据的Observable observableForGetWeatherFromDB
,同时我们也需要创建一个从API获取天气数据的Observable observableForGetWeatherFromNetWork
;为了在无网络状态下免于创建observableForGetWeatherFromNetWork
我们在这之前需要首先判断下网络状态。最后使用contact
操作符将两个Observable合并,同时使用distinct
和takeUntil
操作符来过滤筛选数据以符合业务需求,然后结合subscribeOn
和observeOn
做线程切换。上述这一套复杂的业务逻辑如果使用传统编码方式将是极其复杂的。下面我们来看看使用RxJava如何清晰简洁的来实现这个复杂的业务:
Observable<Weather> observableForGetWeatherData;
//首先创建一个从数据库获取天气数据的Observable
Observable<Weather> observableForGetWeatherFromDB = Observable.create(new Observable.OnSubscribe<Weather>() {
@Override
public void call(Subscriber<? super Weather> subscriber) {
try {
Weather weather = weatherDao.queryWeather(cityId);
subscriber.onNext(weather);
subscriber.onCompleted();
} catch (SQLException e) {
throw Exceptions.propagate(e);
}
}
});
if (!NetworkUtils.isNetworkConnected(context)) {
observableForGetWeatherData = observableForGetWeatherFromDB;
} else {
//接着创建一个从网络获取天气数据的Observable
Observable<Weather> observableForGetWeatherFromNetWork = null;
switch (configuration.getDataSourceType()) {
case ApiConstants.WEATHER_DATA_SOURCE_TYPE_KNOW:
observableForGetWeatherFromNetWork = ApiClient.weatherService.getKnowWeather(cityId)
.map(new Func1<KnowWeather, Weather>() {
@Override
public Weather call(KnowWeather knowWeather) {
return new KnowWeatherAdapter(knowWeather).getWeather();
}
});
break;
case ApiConstants.WEATHER_DATA_SOURCE_TYPE_MI:
observableForGetWeatherFromNetWork = ApiClient.weatherService.getMiWeather(cityId)
.map(new Func1<MiWeather, Weather>() {
@Override
public Weather call(MiWeather miWeather) {
return new MiWeatherAdapter(miWeather).getWeather();
}
});
break;
}
assert observableForGetWeatherFromNetWork != null;
observableForGetWeatherFromNetWork = observableForGetWeatherFromNetWork
.doOnNext(new Action1<Weather>() {
@Override
public void call(Weather weather) {
Schedulers.io().createWorker().schedule(() -> {
try {
weatherDao.insertOrUpdateWeather(weather);
} catch (SQLException e) {
throw Exceptions.propagate(e);
}
});
}
});
//使用concat操作符将两个Observable合并
observableForGetWeatherData = Observable.concat(observableForGetWeatherFromDB, observableForGetWeatherFromNetWork)
.filter(new Func1<Weather, Boolean>() {
@Override
public Boolean call(Weather weather) {
return weather != null && !TextUtils.isEmpty(weather.getCityId());
}
})
.distinct(new Func1<Weather, Long>() {
@Override
public Long call(Weather weather) {
return weather.getRealTime().getTime();//如果天气数据发布时间一致,我们再认为是相同的数据从丢弃掉
}
})
.takeUntil(new Func1<Weather, Boolean>() {
@Override
public Boolean call(Weather weather) {
return System.currentTimeMillis() - weather.getRealTime().getTime() <= 60 * 60 * 1000;//如果天气数据发布的时间和当前时间差在一小时以内则终止事件流
}
});
}
observableForGetWeatherData.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Weather>() {
@Override
public void call(Weather weather) {
displayWeatherInformation();
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_LONG).show();
}
});
上面的代码看起来比较复杂,我们采用Lambda表达式简化下代码:
Observable<Weather> observableForGetWeatherData;
//首先创建一个从数据库获取天气数据的Observable
Observable<Weather> observableForGetWeatherFromDB = Observable.create(new Observable.OnSubscribe<Weather>() {
@Override
public void call(Subscriber<? super Weather> subscriber) {
try {
Weather weather = weatherDao.queryWeather(cityId);
subscriber.onNext(weather);
subscriber.onCompleted();
} catch (SQLException e) {
throw Exceptions.propagate(e);
}
}
});
if (!NetworkUtils.isNetworkConnected(context)) {
observableForGetWeatherData = observableForGetWeatherFromDB;
} else {
//接着创建一个从网络获取天气数据的Observable
Observable<Weather> observableForGetWeatherFromNetWork = null;
switch (configuration.getDataSourceType()) {
case ApiConstants.WEATHER_DATA_SOURCE_TYPE_KNOW:
observableForGetWeatherFromNetWork = ApiClient.weatherService.getKnowWeather(cityId)
.map(knowWeather -> new KnowWeatherAdapter(knowWeather).getWeather());
break;
case ApiConstants.WEATHER_DATA_SOURCE_TYPE_MI:
observableForGetWeatherFromNetWork = ApiClient.weatherService.getMiWeather(cityId)
.map(miWeather -> new MiWeatherAdapter(miWeather).getWeather());
break;
}
assert observableForGetWeatherFromNetWork != null;
observableForGetWeatherFromNetWork = observableForGetWeatherFromNetWork
.doOnNext(weather -> Schedulers.io().createWorker().schedule(() -> {
try {
weatherDao.insertOrUpdateWeather(weather);
} catch (SQLException e) {
throw Exceptions.propagate(e);
}
}));
//使用concat操作符将两个Observable合并
observableForGetWeatherData = Observable.concat(observableForGetWeatherFromDB, observableForGetWeatherFromNetWork)
.filter(weather -> weather != null && !TextUtils.isEmpty(weather.getCityId()))
.distinct(weather -> weather.getRealTime().getTime())//如果天气数据发布时间一致,我们再认为是相同的数据从丢弃掉
.takeUntil(weather -> System.currentTimeMillis() - weather.getRealTime().getTime() <= 60 * 60 * 1000);//如果天气数据发布的时间和当前时间差在一小时以内则终止事件流
}
observableForGetWeatherData.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(weather -> displayWeatherInformation(),
throwable -> Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_LONG).show());
小技巧
在上述的实现中有几点是我们需要注意的:
为什么我需要在判断网络那块整个if else?这样看起来很不优雅,我们通过RxJava符完全可以实现同样的操作啊!之所以这样做是为了在无网络状况下去创建不必要的Observable
observableForGetWeatherFromNetWork
;更新数据库的操作不应该阻塞更新UI,因此我们在
observableForGetWeatherFromNetWork
的doOnNext
中需要通过Schedulers.io().createWorker()
去另起一条线程,以此保证更新数据库不会阻塞更新UI的操作。有同学可能会问为什么不在
doOnNext
之后再调用一次observeOn
把更新数据库的操作切换到一条新的子线程去操作呢?其实一开始我也是这样做的,后来想想不对。整个Observable的事件传递处理就像是在一条流水线上完成的,虽然我们可以通过observeOn
来指定子线程去处理更新数据库的操作,但是只有等这条子线程完成了更新数据库的任务后事件才会继续往后传递,这样就阻塞了更新UI的操作。对此有疑问的同学可以去看看我之前关于RxJava源码分析的文章或者自己动手debug看看。
问题
最后给大家留个两个问题:
- 上述代码是最佳实现方案吗?还有什么更加合理的做法?
- 我们在
observableForGetWeatherData
中使用distinct
和takeUntil
过滤筛选天气数据的时候网络请求会不会已经发出去了?这样做还有意义吗?
欢迎大家留言讨论。
本文中的代码在MinimalistWeather中的
WeatherDataRepository
类中有同样的实现,文章中为了更完整的将整个实现过程呈现出来,对代码做了部分改动。如果你喜欢我的文章,就关注下我的知乎专栏或者在 GitHub 上添个 Star 吧!
RxJava系列番外篇:一个RxJava解决复杂业务逻辑的案例的更多相关文章
- Hadoop系列番外篇之一文搞懂Hadoop RPC框架及细节实现
@ 目录 Hadoop RPC 框架解析 1.Hadoop RPC框架概述 1.1 RPC框架特点 1.2 Hadoop RPC框架 2.Java基础知识回顾 2.1 Java反射机制与动态代理 2. ...
- c#入门系列——番外篇:vs的安装与使用
vs的安装 1.安装条件 vs全称visual studio 它是一个开发平台,不仅可以用于c#开发,别的也可以.安装vs前,首先需要一个安装包.安装包可以在网上下载.没有购买版权的 ...
- python之爬虫--番外篇(一)进程,线程的初步了解
整理这番外篇的原因是希望能够让爬虫的朋友更加理解这块内容,因为爬虫爬取数据可能很简单,但是如何高效持久的爬,利用进程,线程,以及异步IO,其实很多人和我一样,故整理此系列番外篇 一.进程 程序并不能单 ...
- 《手把手教你》系列技巧篇(三十一)-java+ selenium自动化测试- Actions的相关操作-番外篇(详解教程)
1.简介 上一篇中,宏哥说的宏哥在最后提到网站的反爬虫机制,那么宏哥在自己本地做一个网页,没有那个反爬虫的机制,谷歌浏览器是不是就可以验证成功了,宏哥就想验证一下自己想法,于是写了这一篇文章,另外也是 ...
- 《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
1.简介 前边几篇文章是宏哥自己在本地弄了一个单选和多选的demo,然后又找了网上相关联的例子给小伙伴或童鞋们演示了一下如何自动化测试,这一篇宏哥在网上找了一个问卷调查,给小伙伴或童鞋们来演示一下.上 ...
- 《手把手教你》系列基础篇(八十)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试-番外篇(详解教程)
1.简介 经过前边几篇知识点的介绍,今天宏哥就在实际测试中应用一下前边所学的依赖测试.这一篇主要介绍在TestNG中一个类中有多个测试方法的时候,多个测试方法的执行顺序或者依赖关系的问题.如果不用de ...
- [uboot] (番外篇)uboot之fdt介绍
http://blog.csdn.net/ooonebook/article/details/53206623 以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为 ...
- [uboot] (番外篇)uboot之fdt介绍 (转)
以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为例 [uboot] uboot流程系列:[project X] tiny210(s5pv210)上电启动流程(B ...
- (八)羽夏看C语言——C番外篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
随机推荐
- 基于netcore实现mongodb和ElasticSearch之间的数据实时同步的工具(Mongo2Es)
基于netcore实现mongodb和ElasticSearch之间的数据实时同步的工具 支持一对一,一对多,多对一和多对多的数据传输方式. 一对一 - 一个mongodb的collection对应一 ...
- 《深入理解计算机系统》第7章:重定位PC相对引用的理解
在第七章<链接>中的静态链接有对符号进行重定位PC相对引用的处理,书上对应的还有公式,但不是很好理解.现做实验对公式进行理解(公式内容如有兴趣可以参考原文)
- Http最常见的错误代码
1XX 表示消息 2XX 表示成功 3XX 表示重定向 4XX 表示请求错误 5XX 表示服务器端错误 我们最常见的就是: 404(页面找不到),这个错误代码是由于我们输入的网址不对造成的,浏览器找不 ...
- 用IDEA在Tomcat上部署项目
其实每次在需要运行的jsp页面右键=>run也是可以运行的,但是会出现下面这样 正常应该Run==>Edit Con-- 这时候将看到这个页面,千万不要在Defaults中招Tomcat配 ...
- Mycat 分片规则详解--数据迁移及节点扩容
使用的是 Mycat 提供的 dataMigrate 脚本进行对数据进行迁移和节点扩容,目前支持的 Mycat 是1.6 版本,由于 Mycat 是由 Java 编写的因此在做数据迁移及节点扩容时需要 ...
- 关于JAVA开发工具IDEA使用
安装IntelliJ IDEA 一.安装JDK 1 下载最新的jdk,这里下的是jdk-8u66 2 将jdk安装到默认的路径C:\Program Files\Java目录下 二.安装IntelliJ ...
- Java基础学习笔记九 Java基础语法之this和super
构造方法 我们对封装已经有了基本的了解,接下来我们来看一个新的问题,依然以Person为例,由于Person中的属性都被private了,外界无法直接访问属性,必须对外提供相应的set和get方法.当 ...
- EM算法的直观描述
解决含有隐变量的问题有三种方法,其中第三种方法就是通常所说的em算法.下面以统计学习方法中给出的三硬币问题为例来分别描述这三种方法.(a,b,c三硬币抛出来为正的概率分别为pai,p,q,每轮抛硬币先 ...
- java web 初学
我希望在本学期本堂课上学会使用java web 框架 精通mvc架构模式 学会通过框架和数据库对产品进行构造与编写. 我计划每周用16小时的时间进行学习java web 一周4学时上课时间 周一到周五 ...
- 冲刺NO.10
Alpha冲刺第十天 站立式会议 项目进展 项目核心功能逐步构建完成,测试工作也已开始.主要对部分功能组合进行测试以测试系统可用性. 问题困难 项目的主要困难在这个时间点主要存在于测试工作中,测试工作 ...