RxJava 教程-1 简介 原理 线程控制 变换
简介
RxJava 是什么?RxJava 在 GitHub 主页上的自我介绍是RxJava is a Java VM implementation of ReactiveX: a library for composing asynchronous and event-based programs by using observable sequences.RxJava是 ReactiveX 在JVM上的一个实现:一个使用可观测的序列(observable sequences)来组成(composing )异步的(asynchronous )、基于事件(event-based)的程序的库。其实, RxJava 的本质可以压缩为异步这一个词。说到根上,它就是一个实现异步操作的库,而别的定语都是基于这之上的。RxJava 好在哪?一个词:简洁。异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写也难被读懂。 Android 创造的AsyncTask 和Handler ,其实都是为了让异步代码更加简洁。RxJava 的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。假设有这样一个需求:界面上有一个自定义的视图,它的作用是显示多张图片,并能任意增加显示的图片。现在需要将一个给出的目录数组中每个目录下的 png 图片都加载出来并显示在View中。需要注意的是,由于读取图片的这一过程较为耗时,需要放在后台执行,而图片的显示则必须在 UI 线程执行。其中一种方式:
new Thread() {
@Override
public void run() {
super.run();
for (File folder : folders) {
File[] files = folder.listFiles();
for (File file : files) {
if (file.getName().endsWith(".png")) {
final Bitmap bitmap = getBitmapFromFile(file);
runOnUiThread(new Runnable() {
@Override
public void run() {
imageCollectorView.addImage(bitmap);
}
});
}
}
}
}
}.start();
而如果使用 RxJava ,实现方式是这样的:
Observable.from(folders)
.flatMap(new Func1<File, Observable<File>>() {
@Override
public Observable<File> call(File file) {
return Observable.from(file.listFiles());
}
})
.filter(new Func1<File, Boolean>() {
@Override
public Boolean call(File file) {
return file.getName().endsWith(".png");
}
})
.map(new Func1<File, Bitmap>() {
@Override
public Bitmap call(File file) {
return getBitmapFromFile(file);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
imageCollectorView.addImage(bitmap);
}
});
观察一下你会发现, RxJava 的这个实现,是一条从上到下的链式调用,没有任何【嵌套】,这在逻辑的简洁性上是具有优势的。当需求变得复杂时,这种优势将更加明显。另外,如果你的 IDE 是 Android Studio ,其实每次打开某个 Java 文件的时候,你会看到被自动 Lambda 化的预览,这将让你更加清晰地看到程序逻辑。
Observable.from(folders)
.flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
.filter((Func1) (file) -> { file.getName().endsWith(".png") })
.map((Func1) (file) -> { getBitmapFromFile(file) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });
如果你习惯使用 Retrolambda ,你也可以直接把代码写成上面这种简洁的形式。Retrolambda 是 Java 6/7 对 Lambda 表达式的非官方兼容方案,它的向后兼容性和稳定性是无法保障的,因此对于企业项目,使用 Retrolambda 是有风险的。在Flipboard 的 Android 代码中,有一段逻辑非常复杂,包含了多次内存操作、本地文件操作和网络操作,对象分分合合,线程间相互配合相互等待,一会儿排成人字,一会儿排成一字。如果使用常规的方法来实现,肯定是要写得欲仙欲死,然而在使用 RxJava 的情况下,依然只是一条链式调用就完成了。它很长,但很清晰。所以, RxJava 好在哪?就好在简洁,好在那把什么复杂逻辑都能穿成一条线的简洁。
原理
RxJava 的异步实现,是通过一种【扩展的观察者模式】来实现的。RxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。与传统观察者模式不同, RxJava 的事件回调方法除了普通事件 onNext()之外,还定义了两个特殊的事件:onCompleted() 和 onError()。
- onCompleted(): 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的onNext() 发出时,需要触发 onCompleted() 方法作为标志。
- onError(): 事件队列异常。在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。
在一个正确运行的事件序列中, onCompleted() 和 onError()onCompleted() 和 onError() 二者是互斥的,有且只有一个,并且是事件序列中的最后一个。基于以上的概念, RxJava 的基本实现主要有三点:1、创建 观察者Observer,它决定事件触发的时候将有怎样的行为。RxJava 中的 Observer 接口的实现方式:
Observer<String> observer = new Observer<String>(){
@Override
public void onCompleted() {
, 2, 3, 4).subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(number -> {Log.i("bqt", "number:" + number); });
事实上,这种在 subscribe() 之前写上两句 subscribeOn(Scheduler.io()) 和 observeOn(AndroidSchedulers.mainThread()) 的使用方式非常常见,它适用于多数的 『后台线程取数据,主线程显示』的程序策略。
变换
终于要到牛逼的地方了,不管你激动不激动,反正我是激动了。RxJava 提供了对事件序列进行变换的支持,这是它的核心功能之一,也是大多数人说『RxJava 真是太好用了』的最大原因。所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。1、map()首先看一个 map() 的例子。
public class Bean {
public String name;
public int age;
public List<String> courses = Arrays.asList("语文", "数学", "英语");
public Bean() {
}
public Bean(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "name=" + name + " age=" + age;
}
}
打印一组学生的名字
//打印出一组学生的名字
Bean[] beans = {new Bean("包青天", 27), new Bean("白乾涛", 26)};
Observable.from(beans)//
.map(new Func1<Bean, String>() {
@Override
public String call(Bean bean) { //
), new Bean("白乾涛", 26)};Observable.from(beans)//
.map(bean -> {return bean.name;})// 输入类型为 bean,返回类型为String
.subscribe(name -> {Log.i("bqt", name);});
可以看到,map() 方法将参数中的Bean 对象转换成一个 String 对象后返回,而在经过 map() 方法后,事件的参数类型也由 Bean转为了 String 。这种直接变换对象并返回的,是最常见的也最容易理解的变换。不过 RxJava 的变换远不止这样,它不仅可以针对事件对象,还可以针对整个事件队列,这使得 RxJava 变得非常灵活。map() 示意图:2、flatMap()这是一个很有用但非常难理解的变换。还是上面的需求,如果我要打印出每个学生所需要修的所有课程的名称(每个学生只有一个名字,但却有多个课程),该怎么做呢?按照以前的思维,我们可以这样:
Bean[] beans = {new Bean("包青天", 27), new Bean("白乾涛", 26)};
Observable.from(beans)//
.subscribe(bean -> {
List<String> courses = bean.courses;
for (int i = 0 ; i < courses.size() ; i++) {
Log.i("bqt", courses.get(i));
}
});
依然很简单。那么如果我不想在 Subscriber 中使用 for 循环,而是希望 Subscriber 中直接传入单个的课程(String)对象呢(这对于代码复用很重要)?用 map() 显然是不行的,因为 map() 是一对一的转化,而我现在的要求是一对多的转化。那怎么才能把一个 Student 转化成多个 String呢?这个时候,就需要用 flatMap() 了:
Bean[] beans = {new Bean("包青天", 27), new Bean("白乾涛", 26)};
Observable.from(beans)//
.flatMap(new Func1<Bean, Observable<String>>() {
@Override
public Observable<String> call(Bean bean) {
return Observable.from(bean.courses);
}
})
.subscribe(name -> {Log.i("bqt", name);});
flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和map() 不同的是, flatMap() 中返回的是一个 Observable 对象,但是这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中(意思是说,subscribe方法接受的参数不是创建的这个Observable 对象,而是这个Observable 对象发送的事件)。flatMap() 的原理是这样的:
- 1. 使用传入的事件对象创建一个 Observable 对象;
- 2. 并不发送这个 Observable,而是将它激活,于是它开始发送事件;
- 3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable,而这个 Observable 负责将这些事件统一交给Subscriber 的回调方法。
这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。flatMap() 示意图:包青天解释:
- 1、最开始的Observable在被订阅后开始发送事件,此时发送的事件为两个圆形(比如上例中的Bean)
- 2、在被flatMap后,每个圆形都生成了一个对应的Observable 对象,但是并不是直接把这两个Observable发送到Observer中
- 3、而是将这两个Observable对象激活,于是这两个Observable 对象就开始发送事件了
- 4、这两个Observable 对象各自发送了两个事件,此时发送的事件类型都为方形(比如上例中的String)
- 5、这两个Observable 对象一共发送了四个事件,这四个事件又都被汇入到同一个Observable中(就像图中的那块云)
- 6、最后由这个Observable对象负责将这四个事件发送给Observer中
RxJava 教程-1 简介 原理 线程控制 变换的更多相关文章
- 【Android】RxJava的使用(四)线程控制 —— Scheduler
并没有关系的图 前言 经过前几篇的介绍,对RxJava对模式有了一定的理解:由Observable发起事件,经过中间的处理后由Observer消费.(对RxJava还不了解的可以出门左拐)之前的代码中 ...
- RxJava RxPermissions 动态权限 简介 原理 案例 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- RxJava RxAndroid【简介】
资源 RxJava:https://github.com/ReactiveX/RxJava RxAndroid :https://github.com/ReactiveX/RxAndroid 官网:h ...
- RxJava系列1(简介)
RxJava系列1(简介) RxJava系列2(基本概念及使用介绍) RxJava系列3(转换操作符) RxJava系列4(过滤操作符) RxJava系列5(组合操作符) RxJava系列6(从微观角 ...
- LeakCanary 内存泄漏 监测 性能优化 简介 原理 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- java多线程(六)线程控制类
1. 多线程控制类 为了保证多线程的三个特性,Java引入了很多线程控制机制,下面介绍其中常用的几种: l ThreadLocal l 原子类 l Lock类 l Volatile关键字 ...
- Java基础教程:多线程基础——线程池
Java基础教程:多线程基础——线程池 线程池 在正常负载的情况瞎,通过为每一个请求创建一个新的线程来提供服务,从而实现更高的响应性. new Thread(runnable).start() 在生产 ...
- Node.js 教程 01 - 简介、安装及配置
系列目录: Node.js 教程 01 - 简介.安装及配置 Node.js 教程 02 - 经典的Hello World Node.js 教程 03 - 创建HTTP服务器 Node.js 教程 0 ...
- Java并发1——线程创建、启动、生命周期与线程控制
内容提要: 线程与进程 为什么要使用多线程/进程?线程与进程的区别?线程对比进程的优势?Java中有多进程吗? 线程的创建与启动 线程的创建有哪几种方式?它们之间有什么区别? 线程的生命周期与线程控制 ...
随机推荐
- Ubuntu14 或是其他系统当中关于sublimeSFTP超时解决方法
一直都使用Sublime的SFTP功能,感觉还不错,好用,但是最近不知道怎么了,使用不成了,提示超时了,于是在网上找一下解决方法,没有找到,于是自己想着试试看,于在搞了一会,发现只要把配制文件当中的s ...
- java中处理http连接超时的方法
声明一个boolean公共变量,表明当前httpconnection是否得到服务器回应. 你的连接线程中在连接之前置这个变量为false; 另起一个监视线程,拿到那个HttpConnection的连接 ...
- SharePoint 2013 更新多个用户字段(Person or Group)
有时我们需要更新一个用户到Person or Group类型的字段, 当然这个属性允许设置多个用户, 要如何才能添加新的用户到该字段,同时还不影响原始存在的值. 这里我们需要了解 SPFieldUse ...
- IIS短文件名漏洞修补方法之一改注册表一个注意项
1)1.png 为漏洞存在没有做任何修复的时候的扫描 修复:2) 修改注册表键值: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSy ...
- Find The Multiple(poj 1426)
Description Given a positive integer n, write a program to find out a nonzero multiple m of n whose ...
- 使用 getNextException() 来检索已经过批处理的特定元素的异常。 ERRORCODE=-4228, SQLSTATE=null
今天查询了一天发现的问题,用ibatis做批量操作时,报错: [非原子批处理出现故障]使用 getNextException() 来检索已经过批处理的特定元素的异常. ERRORCODE=-4228, ...
- 对依赖倒置原则(DIP)及Ioc、DI、Ioc容器的一些理解(转)
所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体.简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合 ...
- mysql 树形数据,层级数据Managing Hierarchical Data in MySQL
原文:http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/ 引言 大多数用户都曾在数据库中处理过分层数据(hiera ...
- bzoj3192
把堆顶和堆顶接起来,然后用树状数组模拟即可不得不说一开始我竟然把100000*100000当成不超出longint 而忘开int64,无药可救…… ..] of longint; now,n1,n2, ...
- POJ-Common Substrings(后缀数组-长度不小于 k 的公共子串的个数)
题意: 长度不小于 k 的公共子串的个数 分析: 基本思路是计算 A 的所有后缀和 B 的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于 k 的部分全部加起来. 先将两个字符串连起来,中间 ...