一 简介
RxJava是实现异步操作的库 那么在有很多异步成熟实现的基础上 我们为什么还要使用RxJava呢?
异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写也难被读懂。 Android 创造的 AsyncTask eventBus 和Handler ,其实都是为了让异步代码更加简洁。RxJava 的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁
二 原理
RxJava使用的是扩展的观察者模式
传统的观察者模式:注册观察者 当被观察者发生变化的时候(发生事件) 能够及时的通知观察者 观察者做出自己的反应 也就是 被观察者-->观察者-->订阅 /注册-->事件 一般传统的观察者 只有点击 和触摸事件 也就是过程事件处理onNext (也就是 onclick()和onEvent())
扩展的观察者模式:在传统观察者模式的基础上 添加了完成事件 和错误事件处理(onComplete ()和onError()) RxJava不仅将事件进行独立的处理 还可以将所有事件堪称一个队列 进行统一的管理 。
规定: 当没有onNext事件进入队列的时候 就触发onComplete ()
当事件处理过程中发生异常的时候 就出触发onError() 同时 事件队列 不允许事件在进行进出 也就是队列被终止了
具体实现原理在后面
三 实现
RxJava的使用和Builder很像 属于链式编程 介绍几个之后常见的名词
Observable:被观察者,也就是消息的发送者
Observer: 观察者,消息的接收者
Subscriber:订阅者,观察者的另一种表示
Scheduler:调度器,进行线程切换
1.首先 搭建环境 添加Rxjava的依赖库
compile 'io.reactivex:rxjava:1.2.1'
compile 'io.reactivex:rxandroid:1.2.1'
2.下面我们一起来做一个小demo
2.1 创建一个观察者 (一般我们在考虑的时候 先找中介 告诉中介我们需要什么 具体中介怎么做事情我们是不管的 )
方式一:使用Observer
// 创建观察者
Observer<String> observable=new Observer<String>() {
@Override
public void onCompleted() {
// 当被观察者事件对列正常完成 没有其他事件进入的时候
Log.d(tag, "onCompleted");
}
@Override
public void onError(Throwable e) {
// 当被观察者队列中事件执行过程中 出现错误的时候
Log.d(tag, "onError");
}
@Override
public void onNext(String s) {
// 事件的执行 就相当于我们在onClick方法中重写的事件一样
Log.d(tag, "Item: " + s);
}
};
方式二:使用Subscriber(Observer的子类)
Subscriber<String> subscriber=new Subscriber<String>() {
@Override
public void onCompleted() {
// 当被观察者事件对列正常完成 没有其他事件进入的时候
Log.d(tag, "onCompleted");
}
@Override
public void onError(Throwable e) {
// 当被观察者队列中事件执行过程中 出现错误的时候
Log.d(tag, "onError");
}
@Override
public void onNext(String s) {
// 事件的执行 就相当于我们在onClick方法中重写的事件一样
Log.d(tag, "Item: " + s);
}
@Override
public void onStart() {
super.onStart();
}
};
subscriber.unsubscribe();
我们可以看到 对于subscriber 属于observer的子类(但是一般由observer声明的订阅者 也会转成Subscriber) 相对于父类来说 他添加了onstart方法 该方法在观察者被调用 但是 onnext()方法还没有执行前调用 一般在事件没有发送前 做一些准备工作 比如数据的清零或者重置 该方法并不是抽象方法 但是需要注意的是 该方法一般执行在订阅所在的线程 并不能制定线程来执行 所以如果是对UI的操作(比如 进度条等操作) 一般不在该方法进行。如果需要指定的线程来做准备工作,可以使用 doOnSubscribe() 方法
两者的区别:
Subscriber可以调用unSubscribe()方法 取消订阅
2.2 创建被订阅者(被观察者)
// 创建被订阅者 在传入参数的时候就可以看出来 是将Observer转化成对应的子类
Observable<String> observable= Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
// 调用订阅者的方法
subscriber.onStart();
subscriber.onNext("hello world");
subscriber.onNext("hello world111111");
subscriber.onError(new Throwable("失败了"));
subscriber.onCompleted();
}
});
}
当Observable被订阅的时候 就会调用call方法 事件会被依次执行
// 订阅observable.subscribe(subscriber);
除了上述的create方法 Observable 对象的时候 Rxjava海提供了快速创建的方式
Create — 通过调用观察者的方法从头创建一个Observable
Defer — 在观察者订阅之前不创建这个Observable,为每一个观察者创建一个新的Observable
Empty/Never/Throw — 创建行为受限的特殊Observable
From — 将其它的对象或数据结构转换为Observable
Interval — 创建一个定时发射整数序列的Observable
Just — 将对象或者对象集合转换为一个会发射这些对象的Observable
Range — 创建发射指定范围的整数序列的Observable
Repeat — 创建重复发射特定的数据或数据序列的Observable
Start — 创建发射一个函数的返回值的Observable
Timer — 创建在一个指定的延迟之后发射单个数据的Observable ..
一般根据需要进行选择
2.3 订阅
讲了这么多 还没有讲两者链接起来 者怎么整呢? 使用订阅方法 讲订阅者何被订阅者 连接起来 才能实现他们之间的互动
为了练时编程的方便 将格式定义成了
// 订阅
observable.subscribe(subscriber); 这么看来 好像反了 被订阅者订阅了订阅者 但是为了连式编程 的方便 只能定义成这样 那么我们就跑跑吧
Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):
// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public Subscription subscribe(Subscriber subscriber) {
subscriber.onStart();
onSubscribe.call(subscriber);
return subscriber;
}
可以看到,subscriber() 做了3件事:
调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中,Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().
10-13 16:34:52.043 6025-6025/? D/MainActivity: Item: hello world
10-13 16:34:52.043 6025-6025/? D/MainActivity: onCompleted
ok
除了完整定义 还有不完整定义
除了 subscribe(Observer) 和 subscribe(Subscriber) ,subscribe() 还支持不完整定义的回调,RxJava 会自动根据定义创建出Subscriber 。形式如下:
Action1<String> onNextAction = new Action1<String>() {
// onNext()
@Override
public void call(String s) {
Log.d(tag, s);
}
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
// onError()
@Override
public void call(Throwable throwable) {
// Error handling
}
};
Action0 onCompletedAction = new Action0() {
// onCompleted()
@Override
public void call() {
Log.d(tag, "completed");
}
};
// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()
observable.subscribe(onNextAction);
// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);
简单解释一下这段代码中出现的 Action1 和 Action0。 Action0 是 RxJava 的一个接口,它只有一个方法 call(),这个方法是无参无返回值的;由于 onCompleted() 方法也是无参无返回值的,因此 Action0 可以被当成一个包装对象,将 onCompleted() 的内容打包起来将自己作为一个参数传入 subscribe() 以实现不完整定义的回调。这样其实也可以看做将 onCompleted() 方法作为参数传进了subscribe(),相当于其他某些语言中的『闭包』。 Action1 也是一个接口,它同样只有一个方法 call(T param),这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和 onError(Throwable error) 也是单参数无返回值的,因此 Action1可以将 onNext(obj) 和 onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然 Action0 和 Action1在 API 中使用最广泛,但 RxJava 是提供了多个 ActionX 形式的接口 (例如 Action2, Action3) 的,它们可以被用以包装不同的无返回值的方法。
就是使用Action 实现 将方法进行包装 生成不完整的订阅者
说了一大半天 练时编程呢 让我们连起来 吧
String[] words={"aa","cc","bb"};
// 将数组转化成被订阅者对象 然后注册 订阅者 使用不完整定义定义订阅者对象
Observable.from(words).subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.d(tag,s);
}
});
跑跑呗
10-13 16:46:51.213 18046-18046/com.example.rxdemo D/MainActivity: aa
10-13 16:46:51.213 18046-18046/com.example.rxdemo D/MainActivity: cc
10-13 16:46:51.213 18046-18046/com.example.rxdemo D/MainActivity: bb
10-13 16:46:51.233 18046-18046/com.example.rxdemo D/Atlas: Validating map...
这个结果真的是运行出来的 不是我自己瞎编的 然而 并没有什么卵用
四 异步的实现(线程控制 Scheduler)
4.1 在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。也就是说,如果只用上面的方法,实现出来的只是一个同步的观察者模式。观察者模式本身的目的就是『后台处理,前台回调』的异步机制,因此异步对于 RxJava 是至关重要的。而要实现异步,则需要用到 RxJava 的另一个概念: Scheduler 。
Scheduler 我在前面大概提了一下 是调度器 实现线程的切换 总而言之 就是完成线程的控制 就成了
RxJava的线程原则:
在不制定线程的情况下 RxJava遵循的是线程不变的原则 也就是 订阅在那个线程 那么就在那个线程执行 如果需要切换线程 就需要使用Scheduler 进行线程的切换
Scheduler的使用:
RxJava已经内置了几个常用的Scheduler 能够解决大部分场景的使用 介绍一下子
Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。
Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
另外, Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。
有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。
subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
observeOn(): 指定 Subscriber 所运行.observeOn(AndroidSchedulers.mainThread()) // 指定事件的回调发生在主线程的线程。或者叫做事件消费的线程。
/**
* 指定线程运行
*/
private void demo2() {
Observable.just(1,2,3,4) //将整形数据转化成被订阅者对象
.subscribeOn(Schedulers.io()) // 注册线程控制发生在io线程 (使用线程控制 调用io进行输入输出)
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Action1<Integer>() { //创建订阅者 使用不完整定义(有一个参数 没有返回值 )
@Override
public void call(Integer integer) {
Log.d("demo2","num="+integer);
}
});
}
结果我就不说了 注释写得很清楚
上面这段代码中,由于 subscribeOn(Schedulers.io()) 的指定,被创建的事件的内容 1、2、3、4 将会在 IO 线程发出;而由于observeOn(AndroidScheculers.mainThread()) 的指定,因此 subscriber 数字的打印将发生在主线程 。事实上,这种在subscribe() 之前写上两句 subscribeOn(Scheduler.io()) 和 observeOn(AndroidSchedulers.mainThread()) 的使用方式非常常见,它适用于多数的 『后台线程取数据,主线程显示』的程序策略。
不说扯淡的例子了 说个比较实用的例子 我们都知道图片的加载比较好费时间 有人说了 不是有图片加载框架吗 什么picasso啦 imageLoader了 等等 可以完全ok 自己用着玩吧 但是 底层他们都是需要请求网络加载图片的 一般使用的Asynctask进行实现的 那么我之前也说个 Rxjava最大的用处就是异步 那么 我们底层完全可以根据Rxjava来实现 来个小例子
Observable.create(new Observable.OnSubscribe<Drawable>() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void call(Subscriber<? super Drawable> subscriber) {
int drawableRes = R.mipmap.ic_launcher;
Drawable drawable = getTheme().getDrawable(drawableRes);
// 设置加载完成前的默认现实图片
subscriber.onNext(drawable);
// 模拟加载图片
SystemClock.sleep(2000);
drawable=getTheme().getDrawable(R.mipmap.icon_baoxiudan_wxdd);
subscriber.onNext(drawable);
// 图片加载完成后进行图片的替换
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Observer<Drawable>() {
@Override
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
}
});
结果 我就不说了 要不会显得太没档次
4.2 变换
--1. map() 将String类型转化成Bitmap类型的对象然后将对象进行返回 (事务对象直接变换 最常见 也最容易 )
demo:
Observable.just(new File(Environment.getExternalStorageDirectory(),"Pictures/JPEG_20160803_003209.jpg").getAbsolutePath())
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String path) {
return getBitmapFromPath(path);//根据路径获取bitMap图片
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
imageView.setImageBitmap(bitmap); //将图片显示在指定的控件中
}
});
关于返回值的问题:
ActionX 是没有返回值的java的接口实现类 (Action0 没有参数 Action1有一个参数 我前面已经说过了 不过我自己又忘记了)
FunX 是有返回值的java接口实现类
--2.flatMap()
需求:输出一个数据中所有学生对应的课程信息
使用map()实现
ArrayList<String> course=new ArrayList<String>();
course.add("1");
course.add("2");
course.add("3");
Student student1=new Student(123,"zhang",18,course);
Student student2=new Student(124,"zhang1",19,course);
student=new Student[2];
student[0]=student1;
student[1]=student2;
Observable.from(student)
.map(new Func1<Student, Student>() {
@Override
public Student call(Student student) {
return student;
}
})
.subscribe(new Subscriber<Student>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Student s) {
// TODO 遍历循环输出对应课程
Log.d(tag,"===="+s);
}
});
遍历 很麻烦 我们就像 能不能不这么烦 直接传递给我课程信息 然后我们直接就输出了 遍历干嘛
乡亲们 不要着急 我们使用flatmap()实现一下
Observable.from(student)
.flatMap(new Func1<Student, Observable<String>>() {
//第一个参数便是传入的参数类型 第二个参数是想要得到的参数类型 在当前例子中我们需要得到的是各门学科的名称
@Override
public Observable<String> call(Student student) {
return Observable.from(student.getCourses());
//获取学科数据 转化成被订阅者类型 具体为什么能够直接取出学科中的数据 请看原理
}
})
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String name) {
Log.d(tag,name);
}
});
上面例子使用起来是不是就简单了 大兄弟 那下面我们看看原理
flatMap() 的原理是这样的:
1. 使用传入的事件对象创建一个 Observable 对象;
2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。
来点高级的:
一般可以在flatmap中进行异步操作 所以可以进行网络请求吧 这就涉及到Retrofit了 我们可以使用retrofit+RxJava来代替ok和EventBus
具体代码我站在下面了 从别的地方站过来的 因为我还没学习Retrofit 尴尬了
- networkClient.token() // 返回 Observable<String>,在订阅时请求 token,并在响应后发送 token
- .flatMap(new Func1<String, Observable<Messages>>() {
- @Override
- public Observable<Messages> call(String token) {
- // 返回 Observable<Messages>,在订阅时请求消息列表,并在响应后发送请求到的消息列表
- return networkClient.messages();
- }
- })
- .subscribe(new Action1<Messages>() {
- @Override
- public void call(Messages messages) {
- // 处理显示消息列表
- showMessages(messages);
- }
- });
下面在看一个省事的throttleFirst():
RxView.clicks(imageView)
.throttleFirst(500, TimeUnit.MILLISECONDS)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Log.d(tag,"间隔0.5s点击有作用 用来过滤点击的");
}
});
throttleFirst():是用来过滤事件的 在制定间隔时间内的事件时不给予处理的 常用来做都送处理 妈妈再也不用担心恶作剧的频繁点击了
对于事件序列的处理还有很多 在后续的文中我在介绍(但愿还有后续)
4.3 变换的原理
在上面的例子中我们都是对事件的序列进行的处理 看似葛优特点 但其实都是对事件序列的管理 也就是重新处理事件 然后在发送
在RxJava中他们都是 通过一个共同的方法实现的 lift(操作) 核心代码(不用说 又是从别的地方拿来的):
- // 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
- // 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
- public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
- return Observable.create(new OnSubscribe<R>() {
- @Override
- public void call(Subscriber subscriber) {
- Subscriber newSubscriber = operator.call(subscriber);
- newSubscriber.onStart();
- onSubscribe.call(newSubscriber);//这个onSubscribe是新的的Observable的 通知给原来的Observable的Observable
- }
- });
- }
改方法的返回值是Observable 原来我们有一个Observable 但是通过这个方法之后我们就有了两个Observable 我们成为 原来的 和新的(能理解吧 不能理解就走吧)
简单来说:
在新的Observable中 通过opertor.call方法 将创建新的Subscriber(在opertor中包含着用户定义的变换 那么这样就相当于将新的变化插入到新的Subscriber中了),并且和旧的Subscriber建立联系 那么此时 newSubscriber就可以向原来的observable进行订阅
注意啦注意啦 父老乡亲们:
讲述 lift() 的原理只是为了让你更好地了解 RxJava ,从而可以更好地使用它。然而不管你是否理解了 lift() 的原理,RxJava 都不建议开发者自定义 Operator 来直接使用 lift(),而是建议尽量使用已有的 lift() 包装方法(如map() flatMap() 等)进行组合来实现需求,因为直接使用 lift() 非常容易发生一些难以发现的错误。
4.4 compose (对 observable整体的转换)
都是转换 那么 lift 和compose的区别是什么呢
lift:对事件序列的改变
compose:针对 observable本身的转换
有的乡亲大哥就说了 貌似没有什么卵用
那么我们看一个需求 : 如果我们需要对一个observable进行多个转换 ? 老张头说:直接使用多个lift() 不久成了 反正每个的返回值都是Observable 多次转换没毛病
如果我们需要对多个observable进行多个相同的转换? 老李头说:直接封装方法 往里面传observable不久成了 有瑕疵 这样好像限制了observable的灵 活性 那怎么办 找小舅子好像不靠谱 那么这个就需要compose上阵了
来个例子:
- public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
- @Override
- public Observable<String> call(Observable<Integer> observable) {
- return observable
- .lift1()
- .lift2()
- .lift3()
- .lift4();
- }
- }
- ...
- Transformer liftAll = new LiftAllTransformer();
- observable1.compose(liftAll).subscribe(subscriber1);
- observable2.compose(liftAll).subscribe(subscriber2);
- observable3.compose(liftAll).subscribe(subscriber3);
- observable4.compose(liftAll).subscribe(subscriber4);
5.线程控制scheduler(调度器)二
在4里面我们白活了事件序列的变换 什么map flatmap 以及他们的基本原理 我们还白活了一下 如何制定线程运行 比如图片加载等 忘了的乡亲们 回头瞅瞅去 别干瞪眼
(可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程) 结合之前的map()和 flatmap()我们了解了原理 那我们就像能不能产生多次线程的变换呢
observableOn()指定的是subscriber(订阅者)的线程 那么当然这个订阅者 不一定是subscribe()方法调用的注册者 (通过之前我们讲述 lift原理可知 ) 所以observeOn()执行的subscriber并不是Observable所对应的subscriber 而是他的下一级 或者说是我们上面说的新的subscriber 那么我们就可以这样说 : 如果需要切换线程 那么我们只需要调用observeOn()就可以啦
subscribeOn() 和 observeOn() 都做了线程切换的工作。不同的是, subscribeOn()的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。
5.1扩展
最近项目太忙了 未完待续...