声明:本文为 RxJava2 实战知识梳理的心得笔记,非原创,生涩的部分建议看原文。
1.线程切换释疑
- Schedulers.computation():用于计算任务,默认线程数等于处理器的数量。
- Schedulers.from(Executor executor):使用Executor作为调度器,我没用过。
- Schedulers.io( ):用于IO密集型任务,例如访问网络、数据库操作等,也是我们最常使用的。
- Schedulers.newThread( ):为每一个任务创建一个新的线程。
- Schedulers.trampoline( ):当其它排队的任务完成后,在当前线程排队开始执行。
- Schedulers.single():所有任务共用一个后台线程。
2.debounce、filter与switchMap做一个实时搜索
- 使用debounce操作符,当输入框发生变化时,不会立刻将事件发送给下游,而是等待200ms,如果在这段事件内,输入框没有发生变化,那么才发送该事件;反之,则在收到新的关键词后,继续等待200ms。
debounce原理类似于我们在收到请求之后,发送一个延时消息给下游,如果在这段延时时间内没有收到新的请求,那么下游就会收到该消息;而如果在这段延时时间内收到来新的请求,那么就会取消之前的消息,并重新发送一个新的延时消息,以此类推。
而如果在这段时间内,上游发送了onComplete消息,那么即使没有到达需要等待的时间,下游也会立刻收到该消息。 - 使用filter操作符,只有关键词的长度大于0时才发送事件给下游。
- 使用switchMap操作符,这样当发起了abc的请求之后,即使ab的结果返回了,也不会发送给下游,从而避免了出现前面介绍的搜索词和联想结果不匹配的问题。switchMap的原理是将上游的事件转换成一个或多个新的Observable,但是有一点很重要,就是如果在该节点收到一个新的事件之后,那么如果之前收到的时间所产生的Observable还没有发送事件给下游,那么下游就再也不会收到它发送的事件了。
mPublishSubject.debounce(200, TimeUnit.MILLISECONDS).filter(new Predicate<String>() {
@Override
public boolean test(String s) throws Exception {
return s.length() > 0;
}
}).switchMap(new Function<String, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(String query) throws Exception {
return getSearchObservable(query);
}
}).observeOn(AndroidSchedulers.mainThread()).subscribe(mDisposableObserver);
private Observable<String> getSearchObservable(final String query) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
Log.d("SearchActivity", "开始请求,关键词为:" + query);
try {
Thread.sleep(100 + (long) (Math.random() * 500));
} catch (InterruptedException e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
Log.d("SearchActivity", "结束请求,关键词为:" + query);
observableEmitter.onNext("完成搜索,关键词为:" + query);
observableEmitter.onComplete();
}
}).subscribeOn(Schedulers.io());
}
3.zip操作符
它接收多个Observable,以及一个函数,该函数的形参为这些Observable发送的数据,并且要等所有的Observable都发射完会后才会回调该函数。
通过zip操作符,我们就可以实现等待多个网络请求完成再返回的需求。
4.轮询操作
• 固定时延:使用intervalRange操作符,每间隔3s执行一次任务。
• 变长时延:使用repeatWhen操作符实现,第一次执行完任务后,等待4s再执行第二次任务,在第二次任务执行完成后,等待5s,依次递增。
private void startSimplePolling() {
Observable<Long> observable = Observable.intervalRange(0, 5, 0, 3000, TimeUnit.MILLISECONDS).take(5)
.doOnNext(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
//这里使用了doOnNext,因此DisposableObserver的onNext要等到该方法执行完才会回调。
doWork();
}
});
DisposableObserver<Long> disposableObserver = getDisposableObserver();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
}
private void startAdvancePolling() {
Log.d(TAG, "startAdvancePolling click");
Observable<Long> observable = Observable.just(0L).doOnComplete(new Action() {
@Override
public void run() throws Exception {
doWork();
}
}).repeatWhen(new Function<Observable<Object>, ObservableSource<Long>>() {
private long mRepeatCount;
@Override
public ObservableSource<Long> apply(Observable<Object> objectObservable) throws Exception {
//必须作出反应,这里是通过flatMap操作符。
return objectObservable.flatMap(new Function<Object, ObservableSource<Long>>() {
@Override
public ObservableSource<Long> apply(Object o) throws Exception {
if (++mRepeatCount > 4) {
//return Observable.empty(); //发送onComplete消息,无法触发下游的onComplete回调。
return Observable.error(new Throwable("Polling work finished")); //发送onError消息,可以触发下游的onError回调。
}
Log.d(TAG, "startAdvancePolling apply");
return Observable.timer(3000 + mRepeatCount * 1000, TimeUnit.MILLISECONDS);
}
});
}
});
DisposableObserver<Long> disposableObserver = getDisposableObserver();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
}
private DisposableObserver<Long> getDisposableObserver() {
return new DisposableObserver<Long>() {
@Override
public void onNext(Long aLong) {}
@Override
public void onError(Throwable throwable) {
Log.d(TAG, "DisposableObserver onError, threadId=" + Thread.currentThread().getId() + ",reason=" + throwable.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "DisposableObserver onComplete, threadId=" + Thread.currentThread().getId());
}
};
}
private void doWork() {
long workTime = (long) (Math.random() * 500) + 500;
try {
Log.d(TAG, "doWork start, threadId=" + Thread.currentThread().getId());
Thread.sleep(workTime);
Log.d(TAG, "doWork finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
4.1 intervalRange & doOnNext 实现固定时延轮询
该操作符的优势在于:
• 与interval相比,它可以指定第一个发送数据项的时延、指定发送数据项的个数。
• 与range相比,它可以指定两项数据之间发送的时延。
intervalRange的接收参数的含义为:
// Observable.intervalRange(0, 5, 0, 3000, TimeUnit.MILLISECONDS)
• start:发送数据的起始值,为Long型。
• count:总共发送多少项数据。
• initialDelay:发送第一个数据项时的起始时延。
• period:两项数据之间的间隔时间。
• TimeUnit:时间单位。
在轮询操作中一般会进行一些耗时的网络请求,因此我们选择在doOnNext进行处理,它会在下游的onNext方法被回调之前调用,但是它的运行线程可以通过subscribeOn指定,下游的运行线程再通过observerOn切换会主线程,通过打印对应的线程ID可以验证结果。
当要求的数据项都发送完毕之后,最后会回调onComplete方法。
4.2 repeatWhen 实现变长时延轮询
repeatWhen来实现轮询,是因为它为我们提供了重订阅的功能,而重订阅有两点要素:
• 上游告诉我们一次订阅已经完成,这就需要上游回调onComplete函数。
• 我们告诉上游是否需要重订阅,通过repeatWhen的Function函数所返回的Observable确定,如果该Observable发送了onComplete或者onError则表示不需要重订阅,结束整个流程;否则触发重订阅的操作。
repeatWhen的难点在于如何定义它的Function参数:
1.如果输出的Observable发送了onComplete或者onError则表示不需要重订阅,结束整个流程;否则触发重订阅的操作。也就是说,它 仅仅是作为一个是否要触发重订阅的通知,onNext发送的是什么数据并不重要。
2.对于每一次订阅的数据流 Function 函数只会回调一次,并且是在onComplete的时候触发,它不会收到任何的onNext事件。
而当我们不需要重订阅时,有两种方式:
• 返回Observable.empty(),发送onComplete消息,但是DisposableObserver并不会回调onComplete。
• 返回Observable.error(new Throwable(“Polling work finished”)),DisposableObserver的onError会被回调,并接受传过去的错误信息。
private static final String[] MSG_ARRAY = new String[] {
MSG_WAIT_SHORT,
MSG_WAIT_SHORT,
MSG_WAIT_LONG,
MSG_WAIT_LONG
};
private void startRetryRequest() {
Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
int msgLen = MSG_ARRAY.length;
doWork();
//模拟请求的结果,前四次都返回失败,并将失败信息递交给retryWhen。
if (mMsgIndex < msgLen) { //模拟请求失败的情况。
e.onError(new Throwable(MSG_ARRAY[mMsgIndex]));
mMsgIndex++;
} else { //模拟请求成功的情况。
e.onNext("Work Success");
e.onComplete();
}
}
}).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
private int mRetryCount;
@Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
String errorMsg = throwable.getMessage();
long waitTime = 0;
switch (errorMsg) {
case MSG_WAIT_SHORT:
waitTime = 2000;
break;
case MSG_WAIT_LONG:
waitTime = 4000;
break;
default:
break;
}
Log.d(TAG, "发生错误,尝试等待时间=" + waitTime + ",当前重试次数=" + mRetryCount);
mRetryCount++;
return waitTime > 0 && mRetryCount <= 4 ?
Observable.timer(waitTime, TimeUnit.MILLISECONDS) : Observable.error(throwable);
}
});
}
});
DisposableObserver<String> disposableObserver = new DisposableObserver<String>() {
@Override
public void onNext(String value) {
Log.d(TAG, "DisposableObserver onNext=" + value);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "DisposableObserver onError=" + e);
}
@Override
public void onComplete() {
Log.d(TAG, "DisposableObserver onComplete");
}
};
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
}
retryWhen提供了 重订阅 的功能,对于retryWhen来说,它的重订阅触发有两点要素:
- 上游通知retryWhen本次订阅流已经完成,询问其是否需要重订阅,该询问是以onError事件触发的。
- retryWhen根据onError的类型,决定是否需要重订阅,它通过返回一个ObservableSource来通知,如果该ObservableSource返回onComplete/onError,那么不会触发重订阅;如果发送onNext,那么会触发重订阅。
实现retryWhen的关键在于如何定义它的Function参数:
• Function的输入是一个Observable,输出是一个泛型ObservableSource。如果我们接收Observable发送的消息,那么就可以得到上游发送的错误类型,并根据该类型进行响应的处理。
• 如果输出的Observable发送了onComplete或者onError则表示不需要重订阅,结束整个流程;否则触发重订阅的操作。也就是说,它 仅仅是作为一个是否要触发重订阅的通知,onNext发送的是什么数据并不重要。
• 对于每一次订阅的数据流 Function 函数只会回调一次,并且是在onError(Throwable throwable)的时候触发,它不会收到任何的onNext事件。
• 在Function函数中,必须对输入的 Observable进行处理,这里我们使用的是flatMap操作符接收上游的数据。
4.3 retryWhen 和 repeatWhen 对比
retryWhen和repeatWhen最大的不同就是:retryWhen是收到onError后触发是否要重订阅的询问,而repeatWhen是通过onComplete触发。
5. combineLatest 实现的输入表单验证
在下面这个示例中,包含了两个输入框,分别对应用户名和密码,它们的长度要求分别为2~8和4~16,如果两者都正确,那么登录按钮的文案变为“登录”,否则显示“用户名或密码无效”。
mEtName.addTextChangedListener(new EditTextMonitor(mNameSubject));
mEtPassword.addTextChangedListener(new EditTextMonitor(mPasswordSubject));
Observable<Boolean> observable = Observable.combineLatest(mNameSubject,
mPasswordSubject, new BiFunction<String, String, Boolean>() {
@Override
public Boolean apply(String name, String password) throws Exception {
int nameLen = name.length();
int passwordLen = password.length();
return nameLen >= 2 && nameLen <= 8 && passwordLen >= 4
&& passwordLen <= 16;
}
});
DisposableObserver<Boolean> disposable = new DisposableObserver<Boolean>() {
@Override
public void onNext(Boolean value) {
mBtLogin.setText(value ? "登录" : "用户名或密码无效");
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
};
observable.subscribe(disposable);
private class EditTextMonitor implements TextWatcher {
private PublishSubject<String> mPublishSubject;
EditTextMonitor(PublishSubject<String> publishSubject) {
mPublishSubject = publishSubject;
}
@Override
public void afterTextChanged(Editable s) {
mPublishSubject.onNext(s.toString());
}
}
我们首先创建了两个PublishSubject,分别用于用户名和密码的订阅,然后通过combineLatest对这两个PublishSubject进行组合。这样,当任意一个PublishSubject发送事件之后,就会回调combineLatest最后一个函数的apply方法,该方法会取到每个被观察的PublishSubject最后一次发射的数据,我们通过该数据进行验证。
zip和combineLatest接收的参数格式相同, zip和combineLatest的区别在于:
• zip是在其中一个Observable发射数据项后,组合所有Observable最早一个未被组合的数据项,也就是说,组合后的Observable发射的第n个数据项,必然是每个源由Observable各自发射的第n个数据项构成的。
• combineLatest则是在其中一个Observable发射数据项后,组合所有Observable所发射的最后一个数据项(前提是所有的Observable都至少发射过一个数据项)。
6.使用 publish + merge 优化先加载缓存,再读取网络数据的请求过程
为了让大家对这一过程有更深刻的理解,我们介绍”先加载缓存,再请求网络”这种模型的四种实现方式,其中第四种实现可以达到上面我们所说的效果,而前面的三种实现虽然也能够实现相同的需求,并且可以正常工作,但是在某些特殊情况下,会出现意想不到的情况:
• 使用concat实现
• 使用concatEager实现
• 使用merge实现
• 使用publish实现
6.1 准备工作
我们需要准备两个Observable,分别表示 缓存数据源 和 网络数据源,在其中填入相应的缓存数据和网络数据,为了之后演示一些特殊的情况,我们可以在创建它的时候指定它执行的时间:
//模拟缓存数据源。
private Observable<List<NewsResultEntity>> getCacheArticle(final long simulateTime) {
return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
@Override
public void subscribe(ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
try {
Log.d(TAG, "开始加载缓存数据");
Thread.sleep(simulateTime);
List<NewsResultEntity> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
NewsResultEntity entity = new NewsResultEntity();
entity.setType("缓存");
entity.setDesc("序号=" + i);
results.add(entity);
}
observableEmitter.onNext(results);
observableEmitter.onComplete();
Log.d(TAG, "结束加载缓存数据");
} catch (InterruptedException e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
}
});
}
//模拟网络数据源。
private Observable<List<NewsResultEntity>> getNetworkArticle(final long simulateTime) {
return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
@Override
public void subscribe(ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
try {
Log.d(TAG, "开始加载网络数据");
Thread.sleep(simulateTime);
List<NewsResultEntity> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
NewsResultEntity entity = new NewsResultEntity();
entity.setType("网络");
entity.setDesc("序号=" + i);
results.add(entity);
}
observableEmitter.onNext(results);
observableEmitter.onComplete();
Log.d(TAG, "结束加载网络数据");
} catch (InterruptedException e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
}
});
}
6.2 使用 concat 实现
concat是很多文章都推荐使用的方式,因为它不会有任何问题,实现代码如下(注意不发complete事件,第二个不会收到事件):
private void refreshArticleUseContact() {
Observable<List<NewsResultEntity>> contactObservable = Observable.concat(
getCacheArticle(500).subscribeOn(Schedulers.io()),
getNetworkArticle(2000).subscribeOn(Schedulers.io()));
DisposableObserver<List<NewsResultEntity>> disposableObserver =
getArticleObserver();
contactObservable.observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
}
从控制台的输出可以看到,整个过程是先取读取缓存,等缓存的数据读取完毕之后,才开始请求网络,因此整个过程的耗时为两个阶段的相加,即2500ms。
那么,concat操作符的缺点是什么呢?很明显,我们白白浪费了前面读取缓存的这段时间,能不能同时发起读取缓存和网络的请求,而不是等到读取缓存完毕之后,才去请求网络呢?
6.3 使用 concatEager 实现
private void refreshArticleUseConcatEager() {
List<Observable<List<NewsResultEntity>>> observables = new ArrayList<>();
observables.add(getCacheArticle(500).subscribeOn(Schedulers.io()));
observables.add(getNetworkArticle(2000).subscribeOn(Schedulers.io()));
Observable<List<NewsResultEntity>> contactObservable =
Observable.concatEager(observables);
DisposableObserver<List<NewsResultEntity>> disposableObserver =
getArticleObserver();
contactObservable.observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
}
它和concat最大的不同就是多个Observable可以同时开始发射数据,如果后一个Observable发射完成后,前一个Observable还没有发射完数据,那么它会将后一个Observable的数据先缓存起来,等到前一个Observable发射完毕后,才将缓存的数据发射出去。
那么这种实现方式的缺点是什么呢?就是在某些异常情况下,如果读取缓存的时间要大于网络请求的时间,那么就会导致出现“网络请求的结果”等待“读取缓存”这一过程完成后才能传递给下游,白白浪费了一段时间。
6.4 使用 merge 实现
private void refreshArticleUseMerge() {
Observable<List<NewsResultEntity>> contactObservable
= Observable.merge(getCacheArticle(500).subscribeOn(Schedulers.io()),
getNetworkArticle(2000).subscribeOn(Schedulers.io()));
DisposableObserver<List<NewsResultEntity>> disposableObserver
= getArticleObserver();
contactObservable.observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
}
它和concatEager一样,会让多个Observable同时开始发射数据,但是它不需要Observable之间的互相等待,而是直接发送给下游。所以可能网络数据加载完成后,读取的缓存数据会将网络数据覆盖。
6.5 使用 publish 实现
private void refreshArticleUsePublish() {
Observable<List<NewsResultEntity>> publishObservable
= getNetworkArticle(2000)
.subscribeOn(Schedulers.io())
.publish(new Function<Observable<List<NewsResultEntity>>,
ObservableSource<List<NewsResultEntity>>>() {
@Override
public ObservableSource<List<NewsResultEntity>>
apply(Observable<List<NewsResultEntity>> network) throws Exception {
return Observable
.merge(network,getCacheArticle(500)
.subscribeOn(Schedulers.io())
.takeUntil(network));
}
});
DisposableObserver<List<NewsResultEntity>>
disposableObserver = getArticleObserver();
publishObservable.observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
}
这里面一共涉及到了三个操作符,publish、merge和takeUnti,我们先来看一下它能否解决我们之前三种方式的缺陷:
• 读取缓存的时间为500ms,请求网络的时间为2000ms
• 读取缓存的时间为2000ms,请求网络的时间为500ms
如果网络请求先返回时发生了错误(例如没有网络等)导致发送了onError事件,从而使得缓存的Observable也无法发送事件,最后界面显示空白。
针对这个问题,我们需要对网络的Observable进行优化,让其不将onError事件传递给下游。其中一种解决方式是通过使用onErrorResume操作符,它可以接收一个Func函数,其形参为网络发送的错误,而在上游发生错误时会回调该函数。我们可以根据错误的类型来返回一个新的Observable,让订阅者镜像到这个新的Observable,并且忽略onError事件,从而避免onError事件导致整个订阅关系的结束。
这里为了避免订阅者在镜像到新的Observable时会收到额外的时间,我们返回一个Observable.never(),它表示一个永远不发送事件的上游。
private Observable<List<NewsResultEntity>> getNetworkArticle(final long simulateTime) {
return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
@Override
public void subscribe(
ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
try {
Log.d(TAG, "开始加载网络数据");
Thread.sleep(simulateTime);
List<NewsResultEntity> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
NewsResultEntity entity = new NewsResultEntity();
entity.setType("网络");
entity.setDesc("序号=" + i);
results.add(entity);
}
//a.正常情况。
//observableEmitter.onNext(results);
//observableEmitter.onComplete();
//b.发生异常。
observableEmitter.onError(new Throwable("netWork Error"));
Log.d(TAG, "结束加载网络数据");
} catch (InterruptedException e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
}
}).onErrorResumeNext(new Function<Throwable,
ObservableSource<? extends List<NewsResultEntity>>>() {
@Override
public ObservableSource<? extends List<NewsResultEntity>>
apply(Throwable throwable) throws Exception {
Log.d(TAG, "网络请求发生错误throwable=" + throwable);
return Observable.never();
}
});
}
6.5.1 takeUntil
我们给sourceObservable通过takeUntil传入了另一个otherObservable,它表示sourceObservable在otherObservable发射数据之后,就不允许再发射数据了,这就刚好满足了我们前面说的“只要网络源发送了数据,那么缓存源就不应再发射数据”。
之后,我们再用前面介绍过的merge操作符,让两个缓存源和网络源同时开始工作,去取数据。
6.5.2 publish
但是上面有一点缺陷,就是调用merge和takeUntil会发生两次订阅,这时候就需要使用publish操作符,它接收一个Function函数,该函数返回一个Observable,该Observable是对原Observable,也就是上面网络源的Observable转换之后的结果,该Observable可以被takeUntil和merge操作符所共享,从而实现只订阅一次的效果。
7. 使用 timer/interval/delay 实现任务调度
• timer:创建型操作符,用于延时执行任务。
• interval:创建型操作符,用于周期执行任务。
• delay:辅助型操作,用于延时传递数据。
//延迟 1s 后执行一个任务,然后结束
Observable.timer(1000, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.subscribe(disposableObserver);
每隔1s执行一次任务,第一次任务执行前有1s的间隔,执行无限次。这是因为,使用interval操作符时,默认第一次个任务需要延时和指定间隔相同的时间。
Observable.interval(1000, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.subscribe(disposableObserver);
如果希望立即执行第一次任务,那么可以给它提供额外的参数,指定第一次任务的延时:
Observable.interval(0, 1000, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.subscribe(disposableObserver);
每隔 1s 执行一次任务,立即执行第一次任务,只执行五次:
Observable.interval(0, 1000, TimeUnit.MILLISECONDS)
.take(5)
.subscribe(disposableObserver);
delay
当它接受一个时间段时,每当原始的Observable发射了一个数据项时,它就启动一个定时器,等待指定的时间后再将这个数据发射出去,因此表现为发射的数据项进行了平移,但是它只会平移onNext/onComplete,对于onError,它会立即发射出去,并且丢弃之前等待发射的onNext事件。
因为delay不是创建型操作符,所以我们可以用来延迟上游发射过来的数据,下面,让我们实现这个效果:先执行一个任务,等待 1s,再执行另一个任务,然后结束。代码如下:
//先执行一个任务,等待 1s,再执行另一个任务,然后结束
private void startTimeDemo5() {
Log.d(TAG, "startTimeDemo5");
DisposableObserver<Long> disposableObserver = getTimeDemoObserver();
Observable.just(0L).doOnNext(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
Log.d(TAG, "执行第一个任务");
}
}).delay(1000, TimeUnit.MILLISECONDS).subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
}
执行效果为:
8.屏幕旋转导致 Activity 重建时恢复任务
如果我们在AndroidManifest.xml中声明Activity时,没有对android:configChanges进行特殊的声明,那么在屏幕旋转时,会导致Activity的重建,几个关键声明周期的调用情况如下所示:
旋转屏幕前的Activity中的变量都会被销毁,但是有时候我们某些任务的执行不和Activity的生命周期绑定,这时候我们就可以利用Fragment提供的setRetainInstance方法, 如果给Fragment设置了该标志位,那么在屏幕旋转之后,虽然它依附的Activity被销毁了,但是该Fragment的实例会被保留,并且在Activity的销毁过程中,只会调用该Fragment的onDetach方法,而不会调用onDestroy方法。
而在Activity重建时,会调用该Fragment实例的onAttach、onActivityCreated方法,但不会调用onCreate方法。
首先,我们声明一个接口,使得Activity可以监听到Fragment中后台任务的工作进度:
public interface IHolder {
public void onWorkerPrepared(ConnectableObservable<Long> workerFlow);
}
下面,我们来实现WorkerFragment,我们在onCreate中创建了数据源,它每隔1s向下游发送数据,在onResume中,通过前面定义的接口向Activity传递一个ConnectableObservable用于监听。这里最关键的是需要调用我们前面说到的setRetainInstance方法,最后别忘了,在onDetach中将mHolder置为空,否则它就会持有需要被重建的Activity示例,从而导致内存泄漏。
public class WorkerFragment extends Fragment {
public static final String TAG = WorkerFragment.class.getName();
private ConnectableObservable<String> mWorker;
private Disposable mDisposable;
private IHolder mHolder;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof IHolder) {
mHolder = (IHolder) context;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
if (mWorker != null) {
return;
}
Bundle bundle = getArguments();
final String taskName = (bundle != null ? bundle.getString("task_name") : null);
mWorker = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String>
observableEmitter) throws Exception {
for (int i = 0; i < 10; i++) {
String message = "任务名称=" + taskName + ", 任务进度=" + i * 10 + "%";
try {
Log.d(TAG, message);
Thread.sleep(1000);
//如果已经抛弃,那么不再继续任务。
if (observableEmitter.isDisposed()) {
break;
}
} catch (InterruptedException error) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(error);
}
}
observableEmitter.onNext(message);
}
observableEmitter.onComplete();
}
}).subscribeOn(Schedulers.io())
.publish();
mDisposable = mWorker.connect();
}
@Override
public void onResume() {
super.onResume();
if (mHolder != null) {
mHolder.onWorkerPrepared(mWorker);
}
}
@Override
public void onDestroy() {
super.onDestroy();
mDisposable.dispose();
Log.d(TAG, "onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
mHolder = null;
}
}
最后来看Activity,当点击“开始工作任务”后,我们尝试添加WorkerFragment,这时候就会走到WorkerFragment的onCreate方法中启动任务,之后当WorkerFragment走到onResume方法后,就调用onWorkerPrepared,让Activity进行订阅,Activity就可以收到当前任务进度的通知,来更新UI。而在任务执行完毕之后,我们就可以将该Fragment移除了。
public class RotationPersistActivity extends AppCompatActivity implements IHolder {
private static final String TAG = RotationPersistActivity.class.getName();
private Button mBtWorker;
private TextView mTvResult;
private CompositeDisposable mCompositeDisposable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_rotation_persist);
mBtWorker = (Button) findViewById(R.id.bt_start_worker);
mBtWorker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startWorker();
}
});
mTvResult = (TextView) findViewById(R.id.tv_worker_result);
mCompositeDisposable = new CompositeDisposable();
}
@Override
public void onWorkerPrepared(Observable<String> worker) {
DisposableObserver<String> disposableObserver = new DisposableObserver<String>() {
@Override
public void onNext(String message) {
mTvResult.setText(message);
}
@Override
public void onError(Throwable throwable) {
onWorkerFinished();
mTvResult.setText("任务错误");
}
@Override
public void onComplete() {
onWorkerFinished();
mTvResult.setText("任务完成");
}
};
worker.observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
}
private void startWorker() {
WorkerFragment worker = getWorkerFragment();
if (worker == null) {
addWorkerFragment();
} else {
Log.d(TAG, "WorkerFragment has attach");
}
}
private void onWorkerFinished() {
Log.d(TAG, "onWorkerFinished");
removeWorkerFragment();
}
private void addWorkerFragment() {
WorkerFragment workerFragment = new WorkerFragment();
Bundle bundle = new Bundle();
bundle.putString("task_name", "学习RxJava2");
workerFragment.setArguments(bundle);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(workerFragment, WorkerFragment.TAG);
transaction.commit();
}
private void removeWorkerFragment() {
WorkerFragment workerFragment = getWorkerFragment();
if (workerFragment != null) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.remove(workerFragment);
transaction.commit();
}
}
private WorkerFragment getWorkerFragment() {
FragmentManager manager = getSupportFragmentManager();
return (WorkerFragment) manager.findFragmentByTag(WorkerFragment.TAG);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
mCompositeDisposable.clear();
}
}
而在我们的应用场景中,由于WorkerFragment是在后台执行任务:
• 从Activity的角度来看:每次Activity重建时,在Activity中都需要用一个新的Observer实例去订阅WorkerFragment中的数据源,因此我们只能选择通过Hot Observable,而不是Cold Observable来实现WorkerFragment中的数据源,否则每次都会重新执行一遍数据流的代码,而不是继续接收它发送的事件。
• 从WorkerFragment的角度来看,它只是一个任务的执行者,不管有没有人在监听它的进度,它都应该执行任务。
通过Observable.create方法创建的是一个Cold Observable,该Cold Observable每隔1s发送一个事件。我们调用publish方法来将它转换为Hot Observable,之后再调用该Hot Observable的connect方法让其对源Cold Observable进行订阅,这样源Cold Observable就可以开始执行任务了。并且,通过connect方法返回的Disposable对象,我们就可以管理转换后的Hot Observable和源Cold Observable之间的订阅关系。
Disposable的用途在于:在某些时候,我们希望能够停止源Observable任务的执行,例如当该WorkerFragment真正被销毁时,也就是执行了它的onDestroy方法,那么我们就可以通过上面的Disposable取消Hot Observable对源Cold Observable的订阅,而在Cold Observable的循环中,我们判断如果下游(也就是Hot Observable)取消了订阅,那么就不再执行任务
在任务执行之后 removeFragment
让WorkerFragment能正常进行下一次任务的执行,我们需要在发生错误或者任务完成的时候,通过remove的方式销毁WorkerFragment。
9 检测网络状态并自动重试请求
- 在应用启动时,我们会启动定位模块,该定位模块在后台每隔一段时间发起一次定位请求,拿到定位的结果后,我们通过该城市向服务器发起请求,以获取对应城市的天气信息进行展示。
但是在拿到城市之后向服务器请求天气的过程中有可能是处于没有网络的状态,导致无法获取城市的天气信息并刷新界面,因此,我们需要检测网络的状态,在网络重连的时候,读取上一次缓存的城市,向服务器发起请求以获取城市对应天气信息。
9.1 定位模块
我们通过一个后台线程来模拟定位的过程,它每隔一段时间获取一次定位的结果,并将该结果通过mCityPublish发送数据给它的订阅者。
//用于发布定位到的城市结果。
private PublishSubject<Long> mCityPublish;
//模拟定位模块的回调。
private void startUpdateLocation() {
mLocationThread = new Thread() {
@Override
public void run() {
while (true) {
try {
for (long cityId : CITY_ARRAY) {
if (isInterrupted()) {
break;
}
Log.d(TAG, "重新定位");
Thread.sleep(5000);
Log.d(TAG, "定位到城市信息=" + cityId);
mCityPublish.onNext(cityId);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
mLocationThread.start();
}
在mCityPublish发送消息到订阅者收到消息之间,我们还需要做一些特殊的处理:
private Observable<Long> getCityPublish() {
return mCityPublish.distinctUntilChanged()
.doOnNext(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
saveCacheCity(aLong);
}
});
}
这里我们做了两步处理:
• 使用distinctUntilChanged对定位结果进行过滤,如果此次定位的结果和上次定位的结果相同,那么不通知订阅者。
• 使用doOnNext,在返回结果给订阅者之前,先把最新一次的定位结果存储起来,用于在之后网络重连之后进行请求。
9.2 网络状态模块
与定位模块类似,我们也需要一个mNetStatusPublish,其类型为PublishSubject,它在网络状态发生变化时通知订阅者。这里需要注册一个广播,在收到广播之后,我们通过mNetStatusPublish通知订阅者,代码如下:
private void registerBroadcast() {
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mNetStatusPublish != null) {
mNetStatusPublish.onNext(isNetworkConnected());
}
}
};
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, filter);
}
在收到网络状态变化的消息之后:
private Observable<Long> getNetStatusPublish() {
return mNetStatusPublish.filter(new Predicate<Boolean>() {
@Override
public boolean test(Boolean aBoolean) throws Exception {
return aBoolean && getCacheCity() > 0;
}
}).map(new Function<Boolean, Long>() {
@Override
public Long apply(Boolean aBoolean) throws Exception {
return getCacheCity();
}
}).subscribeOn(Schedulers.io());
}
这里我们做了两步处理:
• 使用filter对消息进行过滤,只有在 联网情况并且之前已经定位到了城市 之后才通知订阅者,filter的原理图如下所示,该操作符用于过滤掉一些不需要的数据:
• 使用map,读取当前缓存的城市名,返回给订阅者,map的原理图如下所示,该操作符可以用于执行变换操作。
9.3 网络请求模块
在9.1和9.2中,我们分别用getCityPublish()和getNetStatusPublish()来获取被订阅者,它们分别对应于定位模块和网络状态模块发生变化时所发送的城市数据,下面来看我们通过城市数据获取城市天气信息的代码:
private void startUpdateWeather() {
Observable.merge(getCityPublish(), getNetStatusPublish())
.flatMap(new Function<Long, ObservableSource<WeatherEntity>>() {
@Override
public ObservableSource<WeatherEntity>
apply(Long aLong) throws Exception {
Log.d(TAG, "尝试请求天气信息=" + aLong);
return getWeather(aLong).subscribeOn(Schedulers.io());
}
})
.retryWhen(new Function<Observable<Throwable>,
ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Observable<Throwable>
throwableObservable) throws Exception {
return throwableObservable
.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable)
throws Exception {
Log.d(TAG, "请求天气信息过程中发生错误,进行重订阅");
return Observable.just(0);
}
});
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<WeatherEntity>() {
@Override
public void onSubscribe(Disposable disposable) {
mCompositeDisposable.add(disposable);
}
@Override
public void onNext(WeatherEntity weatherEntity) {
WeatherEntity.WeatherInfo info = weatherEntity.getWeatherinfo();
if (info != null) {
Log.d(TAG, "尝试请求天气信息成功");
StringBuilder builder = new StringBuilder();
builder.append("城市名:")....;
mTvNetworkResult.setText(builder.toString());
}
}
@Override
public void onError(Throwable throwable) {
Log.d(TAG, "尝试请求天气信息失败");
}
@Override
public void onComplete() {
Log.d(TAG, "尝试请求天气信息结束");
}
});
}
private Observable<WeatherEntity> getWeather(long cityId) {
WeatherApi api = new Retrofit.Builder()
.baseUrl("http://www.weather.com.cn/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(WeatherApi.class);
return api.getWeather(cityId);
}
这里我们做了以下几个操作:
• 使用merge合并两个数据源。
• 使用retryWhen进行重订阅,因为在获取到城市,之后转换成城市天气信息的时候有可能发生错误,如果发生了错误,那么整个调用链就结束了,需要重新订阅。
10. 实战讲解 publish & replay & share & refCount & autoConnect
今天,我们来整理以下几个大家容易弄混的概念:
• publish
• reply
• ConnectableObservable
• connect
• share
• refCount
• autoConnect
对于以上这些概念,可以用一幅图来概括:
从图中可以看出,这里面可以供使用者订阅的Observable可以分为四类,下面我们将逐一介绍这几种Observable的特点:
• 第一类:Cold Observable,就是我们通过Observable.create、Observable.interval等创建型操作符生成的Observable。
• 第二类:由Cold Observable经过publish()或者replay(int N)操作符转换成的ConnectableObservable。
• 第三类:由ConnectableObservable经过refCount(),或者由Cold Observable经过share()转换成的Observable。
• 第四类:由ConnectableObservable经过autoConnect(int N)转换成的Observable。
10.1 Cold Observable
Cold Observable就是我们通过Observable.create、Observable.interval等创建型操作符生成的Observable,它具有以下几个特点:
• 当一个订阅者订阅Cold Observable时,Cold Observable会重新开始发射数据给该订阅者。
• 当多个订阅者订阅到同一个Cold Observable,它们收到的数据是相互独立的。
• 当一个订阅者取消订阅Cold Observable后,Cold Observable会停止发射数据给该订阅者,但不会停止发射数据给其它订阅者。
下面,我们演示一个例子,首先我们创建一个Cold Observable:
//直接订阅Cold Observable。
private void createColdSource() {
mConvertObservable = getSource();
}
private Observable<Integer> getSource() {
return Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> observableEmitter)
throws Exception {
try {
int i = 0;
while (true) {
Log.d(TAG, "源被订阅者发射数据=" + i + ",发送线程ID="
+ Thread.currentThread().getId());
mSourceOut.add(i);
observableEmitter.onNext(i++);
updateMessage();
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).subscribeOn(Schedulers.io());
}
在创建两个订阅者,它们可以随时订阅到Cold Observable或者取消对它的订阅:
private void startSubscribe1() {
if (mConvertObservable != null && mDisposable1 == null) {
mDisposable1 = mConvertObservable.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Log.d(TAG, "订阅者1收到数据=" + integer + ",接收线程ID="
+ Thread.currentThread().getId());
mSubscribe1In.add(integer);
updateMessage();
}
});
}
}
private void disposeSubscribe1() {
if (mDisposable1 != null) {
mDisposable1.dispose();
mDisposable1 = null;
mSubscribe1In.clear();
updateMessage();
}
}
private void startSubscribe2() {
if (mConvertObservable != null && mDisposable2 == null) {
mDisposable2 = mConvertObservable.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Log.d(TAG, "订阅者2收到数据=" + integer + ",接收线程ID="
+ Thread.currentThread().getId());
mSubscribe2In.add(integer);
updateMessage();
}
});
}
}
private void disposeSubscribe2() {
if (mDisposable2 != null) {
mDisposable2.dispose();
mDisposable2 = null;
mSubscribe2In.clear();
updateMessage();
}
}
解析:
• 第一步:启动应用,创建Cold Observable,这时候Cold Observable没有发送任何数据。
• 第二步:Observer1订阅Observable,此时Cold Observable开始发送数据,Observer1也可以收到数据,即 一个订阅者订阅 Cold Observable 时, Cold Observable 会开始发射数据给该订阅者
• 第三步:Observer2订阅Observable,此时Observable2也可以收到数据,但是它和Observable1收到的数据是相互独立的,即 当多个订阅者订阅到同一个 Cold Observable ,它们收到的数据是相互独立的。
• 第四步:Observer1取消对Observable的订阅,这时候Observer1收不到数据,并且Observable也不会发射数据给它,但是仍然会发射数据给Observer2,即 当一个订阅者取消订阅 Cold Observable 后,Cold Observable 会停止发射数据给该订阅者,但不会停止发射数据给其它订阅者。
• 第五步:Observer1重新订阅Observable,这时候Observable从0开始发射数据给Observer1,即 一个订阅者订阅 Cold Observable 时, Cold Observable 会重新开始发射数据给该订阅者。
10.2由 Cold Observable 转换的 ConnectableObservable
在了解完Cold Observable之后,我们再来看第二类的Observable,它的类型为ConnectableObservable,它是通过Cold Observable经过下面两种方式生成的:
• .publish()
• .reply(int N)
如果使用.publish()创建,那么订阅者只能收到在订阅之后Cold Observable发出的数据,而如果使用reply(int N)创建,那么订阅者在订阅后可以收到Cold Observable在订阅之前发送的N个数据。
我们先以publish()为例,介绍ConnectableObservable的几个特点:
• 无论ConnectableObservable有没有订阅者,只要调用了ConnectableObservable的connect方法,Cold Observable就开始发送数据。
• connect会返回一个Disposable对象,调用了该对象的dispose方法,Cold Observable将会停止发送数据,所有ConnectableObservable的订阅者也无法收到数据。
• 在调用connect返回的Disposable对象后,如果重新调用了connect方法,那么Cold Observable会重新发送数据。
• 当一个订阅者订阅到ConnectableObservable后,该订阅者会收到在订阅之后,Cold Observable发送给ConnectableObservable的数据。
• 当多个订阅者订阅到同一个ConnectableObservable时,它们收到的数据是相同的。
• 当一个订阅者取消对ConnectableObservable,不会影响其他订阅者收到消息。
下面,我们创建一个ConnectableObservable,两个订阅者之后会订阅到它,而不是Cold Observable:
//.publish()将源Observable转换成为HotObservable,当调用它的connect方法后,
//无论此时有没有订阅者,源Observable都开始发送数据,
//订阅者订阅后将可以收到数据,并且订阅者解除订阅不会影响源Observable数据的发射。
public void createPublishSource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.publish();
mConvertDisposable
= ((ConnectableObservable<Integer>) mConvertObservable).connect();
}
• 第一步:启动应用,通过Cold Observable的publish方法创建ConnectableObservable,并调用ConnectableObservable的connect方法,可以看到,此时虽然ConnectableObservable没有任何订阅者,但是Cold Observable也已经开始发送数据。
• 第二步:Observer1订阅到ConnectableObservable,此时它只能收到订阅之后Cold Observable发射的数据。
• 第三步:Observer2订阅到ConnectableObservable,Cold Observable只会发射一份数据,并且Observer1和Observer2收到的数据是相同的。
• 第三步:Observer1取消对ConnectableObservable的订阅,Cold Observable仍然会发射数据,Observer2仍然可以收到Cold Observable发射的数据。
• 第四步:Observer1重新订阅ConnectableObservable,和第三步相同,它仍然只会收到订阅之后Cold Observable发射的数据。
• 第五步:通过connect返回的Disposable对象,调用dispose方法,此时Cold Observable停止发射数据,并且Observer1和Observer2都收不到数据。
*上面这些现象发生的根本原因在于:现在Observer和Observer2都是订阅到ConnectableObservable,真正产生数据的Cold Observable并不知道他们的存在,和它交互的是ConnectableObservable,ConnectableObservable相当于一个中介,它完成下面两项任务:
• 对于上游:通过connect和dispose方法决定是否要订阅到Cold Observer,也就是决定了Cold Observable是否发送数据。
• 对于下游:将Cold Observable发送的数据转交给它的订阅者。*
10.3由 ConnectableObservable 转换成 Observable
由ConnectableObservable转换成Observable有两种方法,我们分为两节介绍下当订阅到转换后的Observable时的现象:
• .refCount()
• .autoConnect(int N)
10.3.1 ConnectableObservable 由 refCount 转换成 Observable
经过refCount方法,ConnectableObservable可以转换成正常的Observable,我们称为refObservable,这里我们假设ConnectableObservable是由Cold Observable通过publish()方法转换的,对于它的订阅者,有以下几个特点:
• 第一个订阅者订阅到refObservable后,Cold Observable开始发送数据。
• 之后的订阅者订阅到refObservable后,只能收到在订阅之后Cold Observable发送的数据。
• 如果一个订阅者取消订阅到refObservable后,假如它是当前refObservable的唯一一个订阅者,那么Cold Observable会停止发送数据;否则,Cold Observable仍然会继续发送数据,其它的订阅者仍然可以收到Cold Observable发送的数据。
接着上例子,我们创建一个refObservable:
//.share()相当于.publish().refCount(),
//当有订阅者订阅时,源订阅者会开始发送数据,如果所有的订阅者都取消订阅,
//源Observable就会停止发送数据。
private void createShareSource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.publish().refCount();
}
操作分为以下几步:
• 第一步:通过.publish().refCount()创建由ConnectableObservable转换后的refObservable,此时Cold Observable没有发送任何消息。
• 第二步:Observer1订阅到refObservable,Cold Observable开始发送数据,Observer1接收数据。
• 第三步:Observer2订阅到refObservable,它只能收到在订阅之后Cold Observable发送的数据。
• 第四步:Observer1取消订阅,Cold Observable继续发送数据,Observer2仍然能收到数据。
• 第五步:Observer2取消订阅,Cold Observable停止发送数据。
• 第六步:Observer1重新订阅,Cold Observable*重新开始*发送数据。
最后说明一点:订阅到Cold Observable的.publish().refCount()和Cold Observable的share()所返回的Observable是等价的。
10.3.2 ConnectableObservable 由 autoConnect(int N) 转换成 Observable
autoConnect(int N)和refCount很类似,都是将ConnectableObservable转换成普通的Observable,我们称为autoObservable,同样我们先假设ConnectableObservable是由Cold Observable通过publish()方法生成的,它有以下几个特点:
• 当有N个订阅者订阅到refObservable后,Cold Observable开始发送数据。
• 之后的订阅者订阅到refObservable后,只能收到在订阅之后Cold Observable发送的数据。
• 只要Cold Observable开始发送数据,即使所有的autoObservable的订阅和都取消了订阅,Cold Observable也不会停止发送数据,如果想要Cold Observable停止发送数据,那么可以使用autoConnect(int numberOfSubscribers, Consumer connection)中Consumer返回的Disposable,它的作用和ConnectableObservable的connect方法返回的Disposable相同。
其创建方法如下所示:
//.autoConnect在有指定个订阅者时开始让源Observable发送消息,
//但是订阅者是否取消订阅不会影响到源Observable的发射。
private void createAutoConnectSource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.publish()
.autoConnect(1, new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
mConvertDisposable = disposable;
}
});
}
我们进行了如下几步操作:
• 第一步:启动应用,创建autoConnect转换后的autoObservable。
• 第二步:Observer1订阅到autoObservable,此时满足条件,Cold Observable开始发送数据。
• 第三步:Observer2订阅到autoObservable,它只能收到订阅后发生的数据。
• 第四步:Observer1取消订阅,Cold Observable继续发送数据,Observer2仍然可以收到数据。
• 第五步:Observer2取消订阅,Cold Observable仍然继续发送数据。
• 第六步:Observer2订阅到autoObservable,它只能收到订阅后发送的消息了。
• 第七步:调用mConvertDisposable的dispose,Cold Observable停止发送数据。
10.4 publish 和 reply(int N) 的区别
在上面的例子当中,所有总结的特点都是建立在ConnectableObservable是由publish()生成,只所以这么做,是为了方便大家理解,无论是订阅到ConnectableObservable,还是由ConnectableObservable转换的refObservable和autoObservable,使用这两种方式创建的唯一区别就是,订阅者在订阅后,如果是通过publish()创建的,那么订阅者之后收到订阅后Cold Observable发送的数据;而如果是reply(int N)创建的,那么订阅者还能额外收到N个之前Cold Observable发送的数据,我们用下面一个小例子来演示,订阅者订阅到的Observable如下:
//.reply会让缓存源Observable的N个数据项,
//当有新的订阅者订阅时,它会发送这N个数据项给它。
private void createReplySource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.replay(3);
mConvertDisposable
= ((ConnectableObservable<Integer>) mConvertObservable).connect();
}
操作步骤:
• 第一步:启动应用,通过Cold Observable的replay(3)方法创建ConnectableObservable,可以看到,此时虽然ConnectableObservable没有任何订阅者,但是Cold Observable也已经开始发送数据。
• 第二步:Observer1订阅到ConnectableObservable,此时它会先收到之前发射的3个数据,之后收到订阅之后Cold Observable发射的数据。
11.错误发生时不自动停止订阅关系
在RxJava中,如果发生了错误,那么 订阅者会自动停止对上游的订阅关系 ,我们将导致订阅取消的错误分为两种:
• 上游:上游发生错误,并发送onError事件给订阅者。
• 下游:订阅者在onNext中处理时发生了异常。
在RxJava的设计中,如果发生了错误,那么订阅关系就取消了。但是在某些时候,我们希望在错误发生的时候不要取消订阅,因为这样订阅者只有重新通过subscribe方法才能收到消息,类似的场景如监测数据源变化、RxBus的实现等。
我们先用两个简单的例子来演示一下上面提到的两种情况:
11.1 上游发生错误
在上游发生错误的时候,一般通过重订阅的方式来解决。我们可以根据错误的类型判断是否需要重订阅,重订阅的时候使用retryWhen操作符:
private void upErrorIgnore() {
mPublishSubject.map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) throws Exception {
if (integer == 4) {
throw new RuntimeException("retry");
} else if (integer == 8) {
throw new RuntimeException("don't retry");
}
return integer;
}
}).observeOn(AndroidSchedulers.mainThread())
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable)
throws Exception {
//第一步,通过flatMap对错误进行响应。
return throwableObservable
.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
//第二步:根据错误的类型判断是否需要重订阅。
return "retry".equals(throwable.getMessage())
? Observable.just(0) : Observable.empty();
}
});
}
}).subscribe(getNormalObserver());
}
在第四次/第八次点击的是否,我们分别在上游抛出一个异常,这样就会触发retryWhen的回调,在其中我们分为注释中的两部分进行处理,第四次的时候发起重订阅,而第八次则不发起,因此,第九个事件订阅者就收不到了,控制台的输出为:
11.2 订阅者发生错误
但是retryWhen只能处理上游发生错误的情况,对于上面说的第二种情况并不能处理,因此假如是上面介绍的第二种情况:订阅者在onNext处理中发生错误的情况,仍然会解除订阅关系,这是因为在LambdaObserver的源码中,如果在onNext中发生了异常,那么首先会调用onError方法,而onError中会执行取消订阅的操作。
解决办法就是,把 LambdaObserver 代码拷贝出来,注释掉那句,然后继承于它去实现 Observer。
从控制台可以看出,并没有解除订阅关系,在发生错误之后,仍然可以继续收到数据。
12. 刷新过期 token 并重新发起请求
• 在重试之前,需要先去刷新一次token,而不是单纯地等待一段时间再重试。
• 如果有多个请求都出现了因token失效而需要重新刷新token的情况,那么需要判断当前是否有另一个请求正在刷新token,如果有,那么就不要发起刷新token的请求,而是等待刷新token的请求返回后,直接进行重试。
直接上代码了:
12.1 Token 存储模块
一个Store 类,代码简单,省略逻辑
12.2 依赖于 token 的接口
这里,我们用一个简单的getUserObservable来模拟依赖于token的接口,token存储的是获取的时间,为了演示方便,我们设置如果距离上次获取的时间大于2s,那么就认为过期,并抛出token失效的错误,否则调用onNext方法返回接口给下游。
private Observable<String> getUserObservable (final int index, final String token) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
Log.d(TAG, index + "使用token=" + token + "发起请求");
//模拟根据Token去请求信息的过程。
if (!TextUtils.isEmpty(token)
&& System.currentTimeMillis() - Long.valueOf(token) < 2000) {
e.onNext(index + ":" + token + "的用户信息");
} else {
e.onError(new Throwable(ERROR_TOKEN));
}
}
});
}
12.3 完整的请求过程
下面,我们来看一下整个完整的请求过程:
private void startRequest(final int index) {
Observable<String> observable = Observable.defer(
new Callable<ObservableSource<String>>() {
@Override
public ObservableSource<String> call() throws Exception {
String cacheToken = TokenLoader.getInstance().getCacheToken();
Log.d(TAG, index + "获取到缓存Token=" + cacheToken);
return Observable.just(cacheToken);
}
}).flatMap(new Function<String, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(String token) throws Exception {
return getUserObservable(index, token);
}
}).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
private int mRetryCount = 0;
@Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable)
throws Exception {
return throwableObservable.flatMap(
new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
Log.d(TAG, index + ":" + "发生错误=" + throwable +
",重试次数=" + mRetryCount);
if (mRetryCount > 0) {
return Observable.error(new Throwable(ERROR_RETRY));
} else if (ERROR_TOKEN.equals(throwable.getMessage())) {
mRetryCount++;
return TokenLoader.getInstance().getNetTokenLocked();
} else {
return Observable.error(throwable);
}
}
});
}
});
DisposableObserver<String> observer = new DisposableObserver<String>() {
@Override
public void onNext(String value) {
Log.d(TAG, index + ":" + "收到信息=" + value);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, index + ":" + "onError=" + e);
}
@Override
public void onComplete() {
Log.d(TAG, index + ":" + "onComplete");
}
};
observable.subscribeOn(Schedulers.io())
.subscribe(observer);
}
为了方便大家阅读,我把所有的逻辑都写在了一整个调用链里,整个调用链分为四个部分:
• defer:读取缓存中的token信息,这里调用了TokenLoader中读取缓存的接口,而这里使用defer操作符,是为了在重订阅时,重新创建一个新的Observable,以读取最新的缓存token信息,其原理图如下:
• flatMap:通过token信息,请求必要的接口。
• retryWhen:使用重订阅的方式来处理token失效时的逻辑,这里分为三种情况:重试次数到达,那么放弃重订阅,直接返回错误;请求token接口,根据token请求的结果决定是否重订阅;其它情况直接放弃重订阅。
• subscribe:返回接口数据。
最后
关键点在于TokenLoader的实现逻辑,代码如下:
public class TokenLoader {
private static final String TAG = TokenLoader.class.getSimpleName();
private AtomicBoolean mRefreshing = new AtomicBoolean(false);
private PublishSubject<String> mPublishSubject;
private Observable<String> mTokenObservable;
private TokenLoader() {
mPublishSubject = PublishSubject.create();
mTokenObservable = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
Thread.sleep(1000);
Log.d(TAG, "发送Token");
e.onNext(String.valueOf(System.currentTimeMillis()));
}
}).doOnNext(new Consumer<String>() {
@Override
public void accept(String token) throws Exception {
Log.d(TAG, "存储Token=" + token);
Store.getInstance().setToken(token);
mRefreshing.set(false);
}
}).doOnError(new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
mRefreshing.set(false);
}
}).subscribeOn(Schedulers.io());
}
public static TokenLoader getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final TokenLoader INSTANCE = new TokenLoader();
}
public String getCacheToken() {
return Store.getInstance().getToken();
}
public Observable<String> getNetTokenLocked() {
if (mRefreshing.compareAndSet(false, true)) {
Log.d(TAG, "没有请求,发起一次新的Token请求");
startTokenRequest();
} else {
Log.d(TAG, "已经有请求,直接返回等待");
}
return mPublishSubject;
}
private void startTokenRequest() {
mTokenObservable.subscribe(mPublishSubject);
}
}
在retryWhen中,我们调用了getNetTokenLocked来获得一个PublishSubject,为了实现前面说到的下面这个逻辑:
我们使用了一个AtomicBoolean来标记是否有刷新Token的请求正在执行,如果有,那么直接返回一个PublishSubject,否则就先发起一次刷新token的请求,并将PublishSubject作为该请求的订阅者。
这里用到了PublishSubject的特性,它既是作为Token请求的订阅者,同时又作为retryWhen函数所返回Observable的发送方,因为retryWhen返回的Observable所发送的值就决定了是否需要重订阅:
• 如果Token请求返回正确,那么就会发送onNext事件,触发重订阅操作,使得我们可以再次触发一次重试操作。
• 如果Token请求返回错误,那么就会放弃重订阅,使得整个请求的调用链结束。
而AtomicBoolean保证了多线程的情况下,只能有一个刷新Token的请求,在这个阶段内不会触发重复的刷新token请求,仅仅是作为观察者而已,并且可以在刷新token的请求回来之后立刻进行重订阅的操作。在doOnNext/doOnError中,我们将正在刷新的标志位恢复,同时缓存最新的token。
为了模拟上面提到的多线程请求刷新token的情况,我们在发起一个请求500ms之后,立刻发起另一个请求,当第二个请求决定是否要重订阅时,第一个请求正在进行刷新token的操作。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_token);
mBtnRequest = (Button) findViewById(R.id.bt_request);
mBtnRequest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startRequest(0);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
startRequest(1);
}
});
}
控制台的输出如下,可以看到在第二个请求决定是否要重订阅时,它判断到已经有请求,因此只是等待而已。而在第一个请求导致的token刷新回调之后,两个请求都进行了重试,并成功地请求到了接口信息。