巧用RxJava解決網路連線失敗問題及Token失效自動獲取問題
網路連線失敗的處理
看過最前面那篇文章的應該很清楚retryWhen()是什麼了。
我再來總結一下,retryWhen()的直面意思就是:發生錯誤了,接下來該做什麼。
retryWhen()是RxJava的一種錯誤處理機制,當遇到錯誤時,將錯誤傳遞給另一個Observable來決定是否要重新給訂閱這個Observable
延遲重試
來想象一個場景:使用者用的2G網路或者WiFi訊號不穩定,導致網路經常連線失敗,其實這個時候只要多努力一下就可以連線成功了,如果此時彈出錯誤提示,體驗肯定不好,所以這裡就要用到重試機制
判斷錯誤型別
再模擬一個場景:使用者不是手機訊號不好,而是根本就沒開啟網路,此時還傻傻的重試只是浪費電量而已,所以我們可以加一個判斷,打開了網路才重試,沒有開啟網路就繼續傳送失敗的訊息。
加入重試超時
繼續想,重試也不可能永遠進行,一般都會設定一個重試超時的機制。
想一下,沒有哪個APP只有一個介面地址吧 - -#,如果你用的Retrofit那麼每一個介面返回的Observable都要手動加上上面的重試程式碼,如果是我,我肯定報警了……所以我們必須把剛剛寫的重試程式碼封裝成一個類:
public class TryWhenTransaction implements Func1<Observable<? extends Throwable>, Observable<?>> {
private static final String TAG = "TokenAdvancedFragment" ;
/***
* 重試間隔時間
*/
private long mInterval;
public TryWhenTransaction(long interval) {
mInterval = interval;
}
private int retryCount = 0;
@Override
public Observable<?> call(final Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
if (throwable instanceof UnknownHostException) {
//若沒開啟網路則停止重試
return Observable.error(throwable);
} else if (throwable instanceof NullPointerException)
Log.i(TAG, "call: Time:" + new Date(System.currentTimeMillis()) + " thread:" + Thread.currentThread().getName());
//重試三次
if (++retryCount < 3)
return Observable.timer(5, TimeUnit.SECONDS);
else
return Observable.error(new IllegalArgumentException("超過最大次數"));//超過最大次數終止
}
});
}
}
呼叫程式碼:
public void testMethodA() {
Observable.just(token).flatMap(new Func1<Integer, Observable<FakeThing>>() {
@Override
public Observable<FakeThing> call(Integer o) {
//若請求資料得不到響應 則返回一個Error 會啟用retry
Log.i(TAG, "call: first token invalide is:" + o + " thread:" + Thread.currentThread().getName());
return Observable.error(new NullPointerException("請求資料失敗"));
}
}).retryWhen(
new TryWhenTransaction(5))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<FakeThing>() {
@Override
public void onCompleted() {
Log.i(TAG, "onCompleted: ");
}
@Override
public void onError(Throwable e) {
//這邊可以根據錯誤型別來分別處理
if (e instanceof IllegalArgumentException) {
Log.e(TAG, "onError: ", e);
}
}
@Override
public void onNext(FakeThing fakeThing) {
}
});
}
日誌:
token失效解決方案
使用者長時間不訪問app可能導致token的失效,這時如果繼續訪問app可能會導致使用者重新登入降低體驗,所以token失效自動重新獲取可以增強使用者體驗。
上程式碼:
public void testMethodB() {
// 使用token進行網路請求
Observable.just(token).flatMap(new Func1<Integer, Observable<FakeThing>>() {
@Override
public Observable<FakeThing> call(Integer o) {
//若token是1000則為無效token
if (o == 1000) {
Log.i(TAG, "call: first token invalide is:" + o);
return Observable.error(new RuntimeException("TokenError"));
}
Log.i(TAG, "call: after retry request token:" + token + " original o is still:" + o);
return Observable.just(new FakeThing());
}
}).retryWhen(
new Func1<Observable<? extends Throwable>, Observable<?>>() {
@Override
public Observable<?> call(final Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
if (throwable instanceof RuntimeException) {//如果是token錯誤就開啟重試
//重新請求Token 獲取到token為2000
token = 2000;
Log.i(TAG, "call: retry execute token changed is:" + token);
return observable.just(null).delay(1, TimeUnit.SECONDS);
}
return Observable.error(throwable);//其他錯誤就不開啟重試
}
});
}
})
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<FakeThing>() {
@Override
public void onCompleted() {
Log.i(TAG, "onCompleted: ");
}
@Override
public void onError(Throwable e) {
try {
throw e;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void onNext(FakeThing fakeThing) {
}
});
}
上執行截圖:
有木有發現新獲取的Token本來應該是2000再重訂閱的時候顯示卻是1000!其實,之所以造成這個的原因是因為對Obserable的工作機制沒有理解透徹導致的。在建立一個Obserable的時候,引數的內容,固定的步驟就已經決定好了。 即使在過程中引數發生了改變,retry的時候,還是使用原來的值去請求的。可以使用HttpLoggingInterceptor驗證這個。
那總是原始值怎麼辦?看下面
我們需要每次重試的時候,產生一個新的Obserable。這時候我們可以藉助defer操作符來實現,每次呼叫都是一個新的Obserable。
把請求介面的Obserable用Obserable.defer包裹一下即可。
public void testMethodC() {
//使用defer重新包裹請求 可以在retry後使用新(重新獲取的)token進行請求
// 如果不包裹則會使用第一次傳進去的token進行請求
Observable.defer(new Func0<Observable<Integer>>() {
@Override
public Observable<Integer> call() {
//這裡寫請求 由於token過期或者為null傳回錯誤碼-1
return Observable.just(token);
}
}).flatMap(new Func1<Integer, Observable<FakeThing>>() {
@Override
public Observable<FakeThing> call(Integer o) {
if (o == 1000) {
Log.i(TAG, "call: first token invalide is:" + o);
return Observable.error(new RuntimeException("TokenError"));
}
Log.i(TAG, "call: after retry request token:" + token + " original o is still:" + o);
return Observable.just(new FakeThing());
}
}).retryWhen(
new Func1<Observable<? extends Throwable>, Observable<?>>() {
@Override
public Observable<?> call(final Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
if (throwable instanceof RuntimeException) {
//重新請求Token 獲取到token為2000
token = 2000;
Log.i(TAG, "call: retry execute token changed is:" + token);
return observable.just(null);
}
return Observable.error(throwable);
}
});
}
})
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<FakeThing>() {
@Override
public void onCompleted() {
Log.i(TAG, "onCompleted: ");
}
@Override
public void onError(Throwable e) {
try {
throw e;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void onNext(FakeThing fakeThing) {
}
});
}
上截圖:
至於次數限制就自己加仿造本片第一段程式碼。