當(dāng)一次網(wǎng)絡(luò)請求(比如說請求購物車的數(shù)據(jù),這時(shí)是需要驗(yàn)證用戶身份的標(biāo)識的,例如cookie或者token)
想到的三種方法:
1.最開始沒用rxjava之前就是用的這種,但是感覺實(shí)在累贅。當(dāng)token失效后重新請求登錄接口,當(dāng)?shù)卿洺晒笸ㄖ鹊腁ctivity重新加載數(shù)據(jù)。這樣需要對每個(gè)接口都進(jìn)行token是否失效的判斷。
2.使用Intercept(參考這篇文章,但是Okhttpclien3.0刪除了ErrorHandler)onErrorResumeNext操作符實(shí)現(xiàn)app與服務(wù)器間token機(jī)制
http://blog.csdn.net/johnny901114/article/details/51533586
在intercept方法中拿到返回的json字符串,然后判斷token是否失效,如果失效,那么重新登錄,但是這兒需要注意的是因?yàn)樾枰^續(xù)往下傳遞請求,登錄接口的請求必須是同步的?。╬s:后來朋友想了另一個(gè)辦法,在intercept中拋出異常,這兒就需要用到第三種方法了)
3.使用retryWhen操作符
(關(guān)于retryWhen這篇博客講的非常好http://m.itdecent.cn/p/023a5f60e6d0)
最開始我的理解有問題。我的代碼是這樣的。
ApiClient.getInstance().getLiveApi().getFollow(cookie)
.flatMap(new Func1<FollowLive, Observable<FollowLive>>() {
@Override
public Observable<FollowLive > call(FollowLive followLive) {
if (myCourse.status == 205) {
return Observable.error(new Exception("kkkk"));
}
return Observable.just(followLive);
}
})
.retryWhen(new RetryWithDelay(3, 1000))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<FollowLive >() {
@Override
public void call(FollowLive response) {
fillData(response);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.i("===========k", throwable.toString());
}
});
然后我在retryWhen中進(jìn)行了重新登錄獲取到了最新的cookie,結(jié)果顯示沒有獲取到正確的數(shù)據(jù),我猜,難道retryWhen只重試了flatMap?當(dāng)然不是,我抓包得到的結(jié)果是,重新進(jìn)行了網(wǎng)絡(luò)請求,但是并沒有使用新的cookie,為什么呢,cookies作為一個(gè)成員變量,他的值變化了??!
然后我寫了個(gè)just的例子測試了下!
str = "aaa";
Observable.just(str).map(new Func1<String, String>() {
@Override
public String call(String s) {
Log.i("====", "s == " + s);
if ("aaa".equals(s)) throw new RuntimeException(s);
return s + "123";
}
}).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.zipWith(Observable.range(1, 4), new Func2<Throwable, Integer, Integer>() {
@Override
public Integer call(Throwable throwable, Integer i) {
str = "ggg";
return i;
}
}).flatMap(new Func1<Integer, Observable<? extends Long>>() {
@Override
public Observable<? extends Long> call(Integer retryCount) {
return Observable.timer(1, TimeUnit.SECONDS);
}
});
}
}).subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.i("====k", "s = " + s);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.i("====", "throwable = " + throwable.getMessage());
}
});
結(jié)果是
aaa
aaa
aaa
...
what?
為啥???為什么后面不打印ggg呢?
看這里吧。
關(guān)于retryWhen的issue
https://github.com/ReactiveX/RxJava/issues/4840
也就說retryWhen每次重試的都是Source Observable!而Observable.just(str)已經(jīng)創(chuàng)建完成,每次傳遞給map操作符的都是創(chuàng)建時(shí)候用的那個(gè)str,網(wǎng)絡(luò)請求的那個(gè)類似,當(dāng)下次重試的時(shí)候使用的是已經(jīng)創(chuàng)建好的Observable(而這個(gè)Observable創(chuàng)建的時(shí)候使用的是空的cookie)為了保證使用最新的cookie,使用defer操作符,原理類似于fromCallable.
當(dāng)然也可以這樣寫:
Observable.just(null).flatMap(new Func1<Object, Observable<FollowLive>>() {
@Override
public Observable<FollowLive> call(Object o) {
return ApiClient.getInstance().getLiveApi().getFollow(cookie);
}
})
再來說第二條提到的那個(gè)拋出異常的方法。
具體實(shí)現(xiàn)就是Intercept和RetryWhen結(jié)合,在Intercept中進(jìn)行token是否失效的判斷,如果token失效那么就直接拋出異常,然后在retryWhen中進(jìn)行重新登錄,并給token設(shè)置最新的值。這樣就避免了同步請求的問題。不過需要注意:
1.因?yàn)橹匦碌卿浭钱惒秸埱?,所以需要對retryWhen中的重試進(jìn)行限制,即重新請求原先接口需要延遲(用timer操作符)
2.對重新登錄次數(shù)進(jìn)行限制
3.最好自定義拋出的異常,這樣方便在Subscriber的onError方法或者retryWhen中進(jìn)行判斷是否是token失效,萬一后續(xù)還有其他問題需要在Intercept中處理呢。
4.最大的弊端是對所有的接口都進(jìn)行了token是否失效的判斷(因?yàn)镮ntercept會是全局的),所以在Intercept中對token是否失效那兒的判斷可以自行處理,比如說用戶是否登錄?這樣的判斷。
2017.1.16更新:使用BlockingObservable阻塞操作符可以避免了這種狀況:
比如說登錄接口返回較慢(慢的超過了retryCount*retryDelayMillis),那么會一直重試登錄接口和follow數(shù)據(jù)接口,直到超過重試次數(shù),如果這時(shí)候仍然沒有獲取到數(shù)據(jù),才會拋出異常,具體的看代碼。
其他參考:https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.e4b4absxs
pps:這是我現(xiàn)在使用的一套方法,有什么不對的地方希望大家可以留言改進(jìn),謝謝!
參考代碼:https://github.com/xturbofan/TokenDemo