在我上一篇講Retrofit+RxJava在MVP模式中優雅地處理異常(一)中,發現很是多網友發郵箱給我表示期待個人下一篇文章,正好趁着清明假期。我就寫寫平時我在使用RxJava+Retrofit怎麼去靈活地處理一些場景。比方說一些比較常見的場景:php
我本身平時對代碼的簡潔性要求很是高,因此retrofit+rxjava正好切中了個人痛點,這也是激發我寫這篇文章的緣由,我想要與你們一塊兒交流進步,可以看看個人代碼演示樣例css
(可以選擇先忽略,等看完這篇文章再回頭來看)java
/** * @author whaoming * github:https://github.com/whaoming * created at 2017/2/14 15:59 * Description:數據請求的管理類 */
public class HttpMethods {
//retrofit相應的接口
private ApiService myService;
//構造方法私有
private HttpMethods() {
List<Interceptor> interceptors = new ArrayList<>();
Map<String,String> headers = new HashMap<>();
headers.put("userid",25);
TokenGetInterceptor tokenGetInterceptor = new TokenGetInterceptor(headers);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
AESInterceptor aesInterceptor = new AESInterceptor();
//建立一個http頭部處理器攔截器(這裏主要處理server返回token的捕獲)
interceptors.add(tokenGetInterceptor );
//日誌打印攔截器
interceptors.add(loggingInterceptor );
//數據的加密與解密攔截器
interceptors.add(aesInterceptor);
RetrofitHelper.getInstance().init(ConstantValue.SERVER_URL,interceptors );
//建立service
myService = RetrofitHelper.getInstance().createService(ApiService.class);
}
//依據id用戶一個用戶的信息
public Observable<UserCommonInfo> getUserInfoById(int userid){
return Direct2.create(myService.getUserInfoById(userid),new TokenProviderImpl());
}
}
/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/whaoming * TODO: 依照建立者模式的思想。把一個訪問server的操做規格化 */
public class Direct {
public static<T> Observable<T> create(Observable<Result<T>> resurce,TokenProvider tokenProvider){
return resurce
//解析固定格式json
.map(new ResultParseInterceptor<T>())
//處理token過時,tokenProvider爲當發現token過時時候詳細的處理方式
.retryWhen(new TokenExpireInterceptor(tokenProvider))
//捕獲整個請求過程當中的錯誤
.onErrorResumeNext(new ErrorInterceptor<T>())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
}
}
相對來講。retrofit+rxjava的學習成本仍是比較高的。git
舉個樣例,就拿數據打印來講,假設使用okHttp的話,可以直接在回調裏面打印server返回的json數據,但是放在retrofit中。因爲retrofit會本身主動幫你封裝成相應的bean,這使得數據解析這個過程不可見。須要經過retrofit的攔截器才幹實現,因此攔截器對於retrofit來講,是一個很是很是重要的東西。github
還記得剛開始使用retrofit的時候,就被這個功能嚇到了,大哥我僅僅是想簡單地打印下server給了我什麼數據,爲何要這麼麻煩啊。。!只是後面也愈來愈理解retrofit這樣作的緣由了,(我的愚見)這樣使得所有的操做都規範化。用我本身的話說。就是retrofit告訴你,僅僅要你想要」入侵」數據發送和解析的過程,不管是什麼操做,你就得給我使用攔截器。那麼事實上說難也不難。僅僅是幾行代碼而已:算法
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
try {
String text = URLDecoder.decode(message, "utf-8");
Log.d("OKHttp", text);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
Log.d("OKHttp", message);
}
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient =builder.build();
mRetrofit = new Retrofit.Builder()
.baseUrl(baseURL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
token機制我相信大多數client都必須要有的一個東西,這裏咱們這個攔截器的工做是爲每個請求加入頭部,還有攔截server返回的頭信息裏面是否包括token,有的話取出並存在本地。先上代碼:json
/** * Created by Mr.W on 2017/2/6. * E-maiil:122627018@qq.com * github:https://github.com/whaoming * TODO: 攔截server返回的token並進行保存,並且在發起請求的時候本身主動爲頭部加入token */
public class TokenGetInterceptor implements Interceptor {
private Map<String,String> headers = null;
public TokenGetInterceptor(Map<String,String> headers){
this.headers = headers;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request newRequest;
if (headers!=null || !Account.isShortCookieEmpty()) {
Request.Builder builder = chain.request().newBuilder();
if(headers!=null){
for(Map.Entry<String,String> item : headers.entrySet()){
//加入一些其它頭部信息,好比appid,userid等。由外部傳入
builder.addHeader(item.getKey(),item.getValue());
}
}
if (!Account.isShortCookieEmpty()) {
builder.addHeader("token", Account.getShortCookie());
}
newRequest = builder.build();
} else {
newRequest = chain.request().newBuilder()
.build();
}
Response response = chain.proceed(newRequest);
if (response.header("token") != null) {
//發現短token。保存到本地
Account.updateSCookie(response.header("token"));
}
String long_token = response.header("long_token");
if (long_token != null) {
//發現長token,保存到本地
Account.updateLCookie(long_token);
}
return response;
}
}
/** 什麼是長token,短token? 區分長token與短token的緣由是因爲倆種token的算法與生效時間不同。當發現短token過時的時候,client會帶上長token向server再次獲取短token。而後再又一次發起請求。固然每個系統的token機制均可能不同。這裏也可以看出retrofit可以很是靈活地處理很是多種狀況 */
那麼關於整個流程token的維護。包括髮現token過時以後,怎麼請求新token。怎麼又一次發起請求。這些操做retrofit要配合rxjava來實現。後面關於rxjava我會說到。markdown
在這裏先簡單講一下個人加密機制,主要是經過rsa+aes,也就是client表單提交的數據,經過aes加密,而後aes的key再經過client本地保存的公鑰進行加密(此公鑰由server經過rsa算法生成,打包的時候保存在client本地)。把加密以後的key放在請求頭裏面,一塊兒發送給server。網絡
攔截器的代碼例如如下:app
/** * @author whaoming * github:https://github.com/whaoming * created at 2017/2/6 10:13 * Description:對錶單提交的數據進行aes加密 */
public class AESInterceptor implements Interceptor {
public String key = "123456789aaaaaaa";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
try {
Request newRequest = null;
if (request.body() instanceof FormBody) {
//發現表單數據
FormBody formBody = (FormBody) request.body();
FormBody.Builder formBuilder = new FormBody.Builder();
String keyMI = null;
for (int i = 0; i < formBody.size(); i++) {
if (formBody.name(i).equals("param")) {
//對提交的表單數據進行加密
String json = AESUtil.encrypt(formBody.value(i), key);
if (!TextUtils.isEmpty(json)) {
formBuilder.add("data", json);
//對aes的key經過rsa公鑰加密
RSAPublicKey pk = RSAKeyProvider.loadPublicKeyByStr(AppContext.getPublicKeyStore());
keyMI = RSAUtils.encryptByPublicKey(key,pk);
}
}else{
formBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
}
}
FormBody newFormBody = formBuilder.build();
Request.Builder builder = request.newBuilder();
if(!TextUtils.isEmpty(keyMI)){
//將加密後的aes的key放在頭部
builder.header("key",keyMI);
}
newRequest = builder
.method(request.method(), newFormBody)
.removeHeader("Content-Length")
.addHeader("Content-Length", newFormBody.contentLength() + "")
.build();
}
Response response = chain.proceed(newRequest == null ? request : newRequest);
String result = response.body().string();
return response.newBuilder().body(ResponseBody.create(response.body().contentType(), result)).build();
}catch (Exception e){
e.printStackTrace();
}
return chain.proceed(request);
}
}
(ps:強烈建議讀第一篇文章後再繼續往下看:Retrofit+RxJava在MVP模式中優雅地處理異常(一))
這裏事實上就是第一篇博文的內容,傳送門:Retrofit+RxJava在MVP模式中優雅地處理異常(一)
這裏事實上也是在第一篇中講過的內容。主要就是利用RxJava的onErrorResumeNext操做符來作錯誤的攔截,可以使整個網絡訪問過程的錯誤都在一個地方解析。從而大大下降view層的工做量,並且使得view層與m層耦合度大大下降。靈活性提升,代碼量大大下降。
/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/122627018 * TODO: 異常解析的一個攔截器 */
public class ErrorInterceptor<T> implements Func1<Throwable, Observable<T>> {
@Override
public Observable<T> call(Throwable throwable) {
throwable.printStackTrace();
//ExceptionProvider:一個錯誤解析器
return Observable.error(ExceptionProvider.handleException(throwable));
}
}
這裏的處理邏輯事實上還蠻複雜的,看看下圖(畫的比較醜,不要介意)
在這裏可以使用RxJava的retryWhen操做符。先看看server返回的數據格式:
/** * 這是server返回數據的一個固定格式 * @author Mr.W */
public class Result<T> {
public int state;
public String error;
public T infos;
}
那麼一個主要的流程是這種:
因此retryWhen就可以在攔截錯誤的時候發揮做用,可以這樣理解retryWhen。當發現onError事件的時候,在retryWhen內部:
當發現錯誤碼爲500的時候。調用傳入的接口(此接口用於token的又一次獲取)
/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/122627018 * TODO: 短token過時的處理 */
public class TokenExpireInterceptor implements Func1<Observable<? extends Throwable>, Observable<?>> { TokenProvider tokenProvider; public TokenExpireInterceptor(TokenProvider tokenProvider){ this.tokenProvider = tokenProvider; } @Override public Observable<?> call(Observable<?
extends Throwable> observable) { return observable.flatMap(new Func1<Throwable, Observable<?
>>() { @Override public Observable<?
> call(Throwable throwable) { if(throwable instanceof ServerException){ ServerException ex = (ServerException)throwable; if(ex.getCode() == 500){ //發現token過時標識,調用獲取token的接口 return tokenProvider.getToken(); } } return Observable.error(throwable); } }); } } /** * token又一次獲取的接口 * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/122627018 */ public interface TokenProvider { Observable<String> getToken(); }
這樣就可以很是完美的處理了token過時的情景。關於token過時的處理
好了。到這裏咱們總結一下上面咱們說到的點,那麼事實上每個點都是我本身的項目中實際使用到的,可以看看如下這個業務邏輯:
可以看出。在發出網絡請求的時候的邏輯,都是由Retrofit的攔截器來實現的。那麼在處理請求結果的時候,都是由RxJava來實現的。因此,整個邏輯就很是清晰很是舒服了
/** * Created by Mr.W on 2017/2/14. * E-maiil:122627018@qq.com * github:https://github.com/whaoming * TODO: 依照建立者模式的思想,把一個處理請求結果的操做流程化 */
public class Direct {
public static<T> Observable<T> create(Observable<Result<T>> resurce,TokenProvider tokenProvider){
return resurce
//解析固定格式json
.map(new ResultParseInterceptor<T>())
//處理token過時,tokenProvider爲詳細的處理方式
.retryWhen(new TokenExpireInterceptor(tokenProvider))
//檢查是否有錯誤
.onErrorResumeNext(new ErrorInterceptor<T>())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
}
}
/** * @author whaoming * github:https://github.com/whaoming * created at 2017/2/14 15:59 * Description:數據請求的管理類,負責建立請求 */
public class HttpMethods {
//retrofit相應的接口
private ApiService myService;
//構造方法私有
private HttpMethods() {
List<Interceptor> interceptors = new ArrayList<>();
Map<String,String> headers = new HashMap<>();
headers.put("userid",25);
TokenGetInterceptor tokenGetInterceptor = new TokenGetInterceptor(headers);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
AESInterceptor aesInterceptor = new AESInterceptor();
//建立一個http頭部處理器攔截器(這裏主要處理server返回token的捕獲)
interceptors.add(tokenGetInterceptor );
//日誌打印攔截器
interceptors.add(loggingInterceptor );
//數據的加密與解密攔截器
interceptors.add(aesInterceptor);
RetrofitHelper.getInstance().init(ConstantValue.SERVER_URL,interceptors );
//建立service
myService = RetrofitHelper.getInstance().createService(ApiService.class);
}
//依據id用戶一個用戶的信息
public Observable<UserCommonInfo> getUserInfoById(int userid){
return Direct2.create(myService.getUserInfoById(userid),new TokenProviderImpl());
}
}
歡迎你們私信我交流一下,你們也可以看看下個人我的項目,關於我平時的一些文章分享到的技術,我基本都集成在上面:github地址
歡迎star哦!
!