java token 有效期_RxJava2 实战知识梳理(14) - 在 token 过期时,刷新过期 token 并重新发起请求...

RxJava2 实战系列文章

一、应用背景

首先要感谢简友 楠柯壹梦 提供的实战案例,这篇文章的例子是基于他提出的需要在token失效时,刷新token并重新请求接口的应用场景所想到的解决方案。如果大家有别的案例或者在实际中遇到什么问题也可以私信我,让我们一起完善这系列的文章。

有时候,我们的某些接口会依赖于用户的token信息,像我们项目当中的资讯评论列表、或者账户的书签同步都会依赖于用户token信息,但是token往往会有一定的有效期,那么我们在请求这些接口返回token失效的时候,就需要刷新token再重新发起一次请求,这个流程图可以归纳如下:

这个应用的场景和 RxJava2 实战知识梳理(6) - 基于错误类型的重试请求 中介绍的场景很类似,之前提到的错误类型就指的是token失效,但是相比之前的例子,我们增加了额外的两个需求:

在重试之前,需要先去刷新一次token,而不是单纯地等待一段时间再重试。

如果有多个请求都出现了因token失效而需要重新刷新token的情况,那么需要判断当前是否有另一个请求正在刷新token,如果有,那么就不要发起刷新token的请求,而是等待刷新token的请求返回后,直接进行重试。

本文的代码可以通过 RxSample 的第十四章获取。

二、示例讲解

2.1 Token 存储模块

首先,我们需要一个地方来缓存需要的Token,这里用SharedPreferences来实现,有想了解其内部实现原理的同学可以看这篇文章:Android 数据存储知识梳理(3) - SharedPreference 源码解析。

public class Store {

private static final String SP_RX = "sp_rx";

private static final String TOKEN = "token";

private SharedPreferences mStore;

private Store() {

mStore = Utils.getAppContext().getSharedPreferences(SP_RX, Context.MODE_PRIVATE);

}

public static Store getInstance() {

return Holder.INSTANCE;

}

private static final class Holder {

private static final Store INSTANCE = new Store();

}

public void setToken(String token) {

mStore.edit().putString(TOKEN, token).apply();

}

public String getToken() {

return mStore.getString(TOKEN, "");

}

}

2.2 依赖于 token 的接口

这里,我们用一个简单的getUserObservable来模拟依赖于token的接口,token存储的是获取的时间,为了演示方便,我们设置如果距离上次获取的时间大于2s,那么就认为过期,并抛出token失效的错误,否则调用onNext方法返回接口给下游。

private Observable getUserObservable (final int index, final String token) {

return Observable.create(new ObservableOnSubscribe() {

@Override

public void subscribe(ObservableEmitter 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));

}

}

});

}

2.3 完整的请求过程

下面,我们来看一下整个完整的请求过程:

private void startRequest(final int index) {

Observable observable = Observable.defer(new Callable>() {

@Override

public ObservableSource call() throws Exception {

String cacheToken = TokenLoader.getInstance().getCacheToken();

Log.d(TAG, index + "获取到缓存Token=" + cacheToken);

return Observable.just(cacheToken);

}

}).flatMap(new Function>() {

@Override

public ObservableSource apply(String token) throws Exception {

return getUserObservable(index, token);

}

}).retryWhen(new Function, ObservableSource>>() {

private int mRetryCount = 0;

@Override

public ObservableSource> apply(Observable throwableObservable) throws Exception {

return throwableObservable.flatMap(new Function>() {

@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 observer = new DisposableObserver() {

@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()).observeOn(AndroidSchedulers.mainThread()).subscribe(observer);

}

为了方便大家阅读,我把所有的逻辑都写在了一整个调用链里,整个调用链分为四个部分:

defer:读取缓存中的token信息,这里调用了TokenLoader中读取缓存的接口,而这里使用defer操作符,是为了在重订阅时,重新创建一个新的Observable,以读取最新的缓存token信息,其原理图如下:

defer 原理图

flatMap:通过token信息,请求必要的接口。

retryWhen:使用重订阅的方式来处理token失效时的逻辑,这里分为三种情况:重试次数到达,那么放弃重订阅,直接返回错误;请求token接口,根据token请求的结果决定是否重订阅;其它情况直接放弃重订阅。

subscribe:返回接口数据。

2.4 TokenLoader 的实现

关键点在于TokenLoader的实现逻辑,代码如下:

public class TokenLoader {

private static final String TAG = TokenLoader.class.getSimpleName();

private AtomicBoolean mRefreshing = new AtomicBoolean(false);

private PublishSubject mPublishSubject;

private Observable mTokenObservable;

private TokenLoader() {

mPublishSubject = PublishSubject.create();

mTokenObservable = Observable.create(new ObservableOnSubscribe() {

@Override

public void subscribe(ObservableEmitter e) throws Exception {

Thread.sleep(1000);

Log.d(TAG, "发送Token");

e.onNext(String.valueOf(System.currentTimeMillis()));

}

}).doOnNext(new Consumer() {

@Override

public void accept(String token) throws Exception {

Log.d(TAG, "存储Token=" + token);

Store.getInstance().setToken(token);

mRefreshing.set(false);

}

}).doOnError(new Consumer() {

@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 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刷新回调之后,两个请求都进行了重试,并成功地请求到了接口信息。

2.5 操作符

本文中用到的操作符的官方解释链接如下:

关于retryWhen的更详细的解释,推荐大家可以看一下之前的 RxJava2 实战知识梳理(6) - 基于错误类型的重试请求,它是这篇文章的基础。

更多文章,欢迎访问我的 Android 知识梳理系列:

你可能感兴趣的:(java,token,有效期)