在经历了OkHttp、Retrofit、RxJava的学习后,终于可以开始写代码rua!
附框架学习笔记:学习笔记| OkHttp+Retrofit+Dagger2+RxJava+MVP架构
由于网络上安利这几款火的不行的框架的博客实在是太多太多太多了,介绍、优缺点之类的废话就不多说了,这里只介绍下关系。
各自职责:Retrofit 负责 请求的数据 和 请求的结果,使用 接口的方式 呈现,OkHttp 负责请求的过程,RxJava 负责异步,各种线程之间的切换。
在build.gradle文件中添加如下配置:
// rxjava
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
// okhttp
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
//gson
implementation 'com.google.code.gson:gson:2.8.5'
有关JWT的知识可以看一下大神的博客:JSON Web Token 入门教程 - 阮一峰
刷新tokenAPI
利用 Observale 的 retryWhen 的方法,识别 token 过期失效的错误信息,此时发出刷新 token 请求的代码块,完成之后更新 token,这时之前的请求会重新执行,但将它的 token 更新为最新的。另外通过代理类对所有的请求都进行处理,完成之后,我们只需关注单个 API 的实现,而不用每个都考虑 token 过期,大大地实现解耦操作。
存储token使用的是SharedPreferences + 单例模式 避免并发请求行为
public class Store {
private SharedPreferences mStore;
// 单例模式
private Store(){
mStore = App.getContext().getSharedPreferences(App.MY_SP_NAME, 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(App.USER_TOKEN_KEY, token).apply();
}
public String getToken() {
return mStore.getString(App.USER_TOKEN_KEY, "");
}
}
我将token请求提取到retrofit service层方便全局调用,使用直接返回一个observable对象,对其订阅在观察者里实现携带token的请求数据操作。并且这里特意没有用lambda表达式写,对于理解会方便很多
/**
* 获取新的Token
*/
private static final String ERROR_TOKEN = "error_token";
private static final String ERROR_RETRY = "error_retry";
public static Observable<String> getNewToken() {
return Observable.defer(new Callable<ObservableSource<String>>() {
@Override
public ObservableSource<String> call() throws Exception {
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
String requestBody = "";
Request request = new Request.Builder()
.url(NetConfig.BASE_GETNEWTOKEN_PLUS)
.header("Authorization", "Bearer " + Store.getInstance().getToken())
.post(RequestBody.create(mediaType, requestBody))
.build();
Log.e("print","发起Token请求");
return Observable.just(client.newCall(request).execute().body().string());
}
})
// Token判断
.flatMap(new Function<String, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(String s) throws Exception {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) {
JSONObject obj = JSON.parseObject(s);
if (obj.getInteger("code") != 20000) {
emitter.onError(new Throwable(ERROR_RETRY));
} else {
String token = obj.getString("result");
Store.getInstance().setToken(token);
emitter.onNext(token);
}
}
});
}
})
// flatMap若onError进入retrywhen,否则onNext()
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
private int mRetryCount = 0;
@Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) {
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
if (mRetryCount++ < 3 && throwable.getMessage().equals(ERROR_TOKEN))
return Observable.error(new Throwable(ERROR_RETRY));
return Observable.error(throwable);
}
});
}
});
}
为了方便大家阅读,我把所有的逻辑都写在了一整个调用链里,整个调用链分为三个部分:
在这里,我选择抽离出项目中的获取用户信息模块进行代码重构演示
获取用户信息API
我选择在将个人信息请求写在了观察者的方法里,再次嵌套一个链式结构
public synchronized void getUserAvator() {
// 创建被观察者的实例
Observable<String> observable = RetrofitService.getNewToken();
// 定义观察者
DisposableObserver<String> observer = new DisposableObserver<String>() {
@Override
public void onNext(String s) {
// 发起用户信息请求
Observable.create((ObservableOnSubscribe<String>) emitter -> {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(NetConfig.BASE_USERDETAIL_PLUS)
.header("Authorization", "Bearer " + Store.getInstance().getToken())
.get()
.build();
String response = client.newCall(request).execute().body().string();
emitter.onNext(response);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(responseString -> {
if (!responseString.contains("result")){
printLog("HomeFragment_getAvatar_subscribe:获取用户信息出错 需要处理");
return;
}
JSONObject jsonObject = JSON.parseObject(responseString);
String path = "";
JSONObject obj = JSON.parseObject(jsonObject.getString("result"));
path = obj.getString("avatar");
Picasso.get()
.load(path)
.placeholder(R.drawable.image_placeholder)
.into(ciHomeImg);
}, throwable -> printLog("HomeFragment_getAvatar_subscribe_onError:" + throwable.getMessage()));
}
@Override
public void onError(Throwable e) {
Log.e("print", "HomeFragment_getUserAvator_onError: " + e.getMessage());
}
@Override
public void onComplete() {
}
};
// 进行订阅
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
这种实现方法实际上每次从服务器获取信息执行顺序是:更新Token → 获取数据信息,这样会向服务器产生过多不必要的请求,加大服务器的负担。所以正确的请求姿势应该是:发起数据请求 → 返回token过期信息 → 发送更新token请求 → 返回new token → 再次发起数据请求,可以有效地减轻服务器的负担,可以在上面的基础上再次封装token service服务达到这样的效果。
在 coding 前参考了很多博客,推荐几篇好的文章
defer操作符实现代码支持链式调用 - Chiclaim
retryWhen操作符实现错误重试机制 - Chiclaim
在 token 过期时,刷新过期 token 并重新发起请求 - 泽毛