Retrofit全攻略——進階篇

最近事比較多,距離上次寫文章已通過去了一個月了。上一篇文章Retrofit全攻略——基礎篇 介紹了Retrofit的基礎用法,這篇文章介紹點進階的用法。css

打印網絡日誌

在開發階段,爲了方便調試,咱們須要查看網絡日誌。由於Retrofit2.0+底層是採用的OKHttp請求的。能夠給OKHttp設置攔截器,用來打印日誌。
首先能夠在app/build.gradle中添加依賴,這是官方的日誌攔截器。java

compile 'com.squareup.okhttp3:logging-interceptor:3.3.0' 

而後在代碼中設置:json

public static Retrofit getRetrofit() { //若是mRetrofit爲空 或者服務器地址改變 從新建立 if (mRetrofit == null) { OkHttpClient httpClient; OkHttpClient.Builder builder=new OkHttpClient.Builder(); //階段分爲開發和發佈階段,當前爲開發階段設置攔截器 if (BuildConfig.DEBUG) { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); //設置攔截器級別 logging.setLevel(HttpLoggingInterceptor.Level.BODY); builder.addInterceptor(logging); } httpClient=builder.build(); //構建Retrofit mRetrofit = new Retrofit.Builder() //配置服務器路徑 .baseUrl(mServerUrl) //返回的數據經過Gson解析 .addConverterFactory(GsonConverterFactory.create()) //配置回調庫,採用RxJava .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //設置OKHttp模板 .client(httpClient) .build(); } return mRetrofit; } 

當處於開發階段的時候,設置監聽日誌的攔截器。攔截有4個級別,分別是api

  1. BODY
  2. HEADERS
  3. BASIC
  4. NONE

其中BODY輸出的日誌是最全的。bash

添加相同的請求參數

爲了更好的管理迭代版本,通常每次發起請求的時候都傳輸當前程序的版本號到服務器。
有些項目咱們每次還會傳用戶id,token令牌等相同的參數。
若是在每一個請求的接口都添加這些參數太繁瑣。Retrofit能夠經過攔截器添加相同的請求參數,無需再每一個接口添加了。服務器

步驟一,本身攔截器微信

public class CommonInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request oldRequest = chain.request(); // 添加新的參數 HttpUrl.Builder authorizedUrlBuilder = oldRequest.url() .newBuilder() .scheme(oldRequest.url().scheme()) .host(oldRequest.url().host()) .addQueryParameter("device_type", "1") .addQueryParameter("version", BuildConfig.VERSION_NAME) .addQueryParameter("token", PreUtils.getString(R.string.token)) .addQueryParameter("userid", PreUtils.getString(R.string.user_id)); // 新的請求 Request newRequest = oldRequest.newBuilder() .method(oldRequest.method(), oldRequest.body()) .url(authorizedUrlBuilder.build()) .build(); return chain.proceed(newRequest); } } 

實現原理就是攔截以前的請求,添加完參數,再傳遞新的請求。這個位置我添加了四個公共的參數。
而後再Retrofit初始化的時候配置。網絡

if (mRetrofit == null) { OkHttpClient httpClient; OkHttpClient.Builder builder=new OkHttpClient.Builder(); //添加公共參數 builder.addInterceptor(new CommonInterceptor()); httpClient=builder.build(); //構建Retrofit mRetrofit = new Retrofit.Builder() //.... .client(httpClient) .build(); } 

處理約定錯誤

除了常見的404,500等異常,網絡請求中咱們每每還會約定些異常,好比token失效,帳號異常等等。app

以token失效爲例,每次請求咱們都須要驗證是否失效,若是在每一個接口都處理一遍錯誤就有點太繁瑣了。ide

咱們能夠統一處理下錯誤。

步驟一,Retrofit初始化時添加自定義轉化器

mRetrofit = new Retrofit.Builder() //配置服務器路徑 baseUrl(mServerUrl) //配置回調庫,採用RxJava .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //配置轉化庫,默認是Gson,這裏修改了。 .addConverterFactory(ResponseConverterFactory.create()) .client(httpClient) .build(); 

步驟二 建立ResponseConverterFactory

步驟一 ResponseConverterFactory這個類是須要咱們本身建立的。

public class ResponseConverterFactory extends Converter.Factory { /** * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and * decoding from JSON (when no charset is specified by a header) will use UTF-8. */ public static ResponseConverterFactory create() { return create(new Gson()); } /** * Create an instance using {@code gson} for conversion. Encoding to JSON and * decoding from JSON (when no charset is specified by a header) will use UTF-8. */ public static ResponseConverterFactory create(Gson gson) { return new ResponseConverterFactory(gson); } private final Gson gson; private ResponseConverterFactory(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); this.gson = gson; } @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { // TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, type); } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); } } 

這裏面咱們自定義了請求和響應時解析JSON的轉換器——GsonRequestBodyConverterGsonResponseBodyConverter

其中GsonRequestBodyConverter 負責處理請求時傳遞JSON對象的格式,不須要額外處理任何事,直接使用默認的GSON解析。代碼我直接貼出來:

final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> { private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); private static final Charset UTF_8 = Charset.forName("UTF-8"); private final Gson gson; private final TypeAdapter<T> adapter; GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public RequestBody convert(T value) throws IOException { Buffer buffer = new Buffer(); Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); JsonWriter jsonWriter = gson.newJsonWriter(writer); adapter.write(jsonWriter, value); jsonWriter.close(); return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); } } 

GsonResponseBodyConverter負責把響應的數據轉換成JSON格式,這個咱們須要處理一下。

public class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { private final Gson gson; private final Type type; GsonResponseBodyConverter(Gson gson, Type type) { this.gson = gson; this.type = type; } @Override public T convert(ResponseBody value) throws IOException { String response = value.string(); try { Log.i("YLlibrary", "response>>> "+response); //ResultResponse 只解析result字段 BaseInfo baseInfo = gson.fromJson(response, BaseInfo.class); if (baseInfo.getHeader().getCode().equals("1")) { //正確 return gson.fromJson(response, type); } else { //ErrResponse 將msg解析爲異常消息文本 錯誤碼能夠本身指定. throw new ResultException(-1024, baseInfo,response); } } finally { } } } 

這種狀況只是應用於後臺接口數據統一的狀況。好比咱們項目的格式是這樣的

{
          header : {"message":"token失效","code":"99"} data : {} } 

當code值是1的時候,表示正確,其它數字表示錯誤。只有正確的時候data纔會有內容。

這裏我用BaseInfo解析這個JSON:

public class BaseInfo { /** * header : {"message":"用戶名或密碼錯誤","code":"0"} * data : {} */ private HeaderBean header; public HeaderBean getHeader() { return header; } public void setHeader(HeaderBean header) { this.header = header; } public static class HeaderBean { /** * message : 用戶名或密碼錯誤 * code : 0 */ private String message; private String code; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } } } 

服務器返回的數據實體對象所有繼承BaseInfo 只是data內容不同。

ResultException這個類用於捕獲服務器約定的錯誤類型

/** * 這個類用於捕獲服務器約定的錯誤類型 */ public class ResultException extends RuntimeException { private int errCode = 0; private BaseInfo info; private String response; public ResultException(int errCode, BaseInfo info,String response) { super(info.getHeader().getMessage()); this.info=info; this.errCode = errCode; this.response=response; } public String getResponse() { return response; } public void setResponse(String response) { this.response = response; } public int getErrCode() { return errCode; } public BaseInfo getBaseInfo(){ return info; } } 

最後定義Retrofit處理異常的代碼

public abstract class AbsAPICallback<T> extends Subscriber<T> { //對應HTTP的狀態碼 private static final int UNAUTHORIZED = 401; private static final int FORBIDDEN = 403; private static final int NOT_FOUND = 404; private static final int REQUEST_TIMEOUT = 408; private static final int INTERNAL_SERVER_ERROR = 500; private static final int BAD_GATEWAY = 502; private static final int SERVICE_UNAVAILABLE = 503; private static final int GATEWAY_TIMEOUT = 504; //出錯提示 private final String networkMsg; private final String parseMsg; private final String unknownMsg; protected AbsAPICallback(String networkMsg, String parseMsg, String unknownMsg) { this.networkMsg = networkMsg; this.parseMsg = parseMsg; this.unknownMsg = unknownMsg; } public AbsAPICallback(){ networkMsg="net error(聯網失敗)"; parseMsg="json parser error(JSON解析失敗)"; unknownMsg="unknown error(未知錯誤)"; } ProgressBar progressBar; public AbsAPICallback(ProgressBar progressBar){ this(); this.progressBar=progressBar; } @Override public void onError(Throwable e) { Throwable throwable = e; //獲取最根源的異常 while(throwable.getCause() != null){ e = throwable; throwable = throwable.getCause(); } ApiException ex; if (e instanceof HttpException){ //HTTP錯誤 HttpException httpException = (HttpException) e; ex = new ApiException(e, httpException.code()); switch(httpException.code()){ case UNAUTHORIZED: case FORBIDDEN: // onPermissionError(ex); //權限錯誤,須要實現 // break; case NOT_FOUND: case REQUEST_TIMEOUT: case GATEWAY_TIMEOUT: case INTERNAL_SERVER_ERROR: case BAD_GATEWAY: case SERVICE_UNAVAILABLE: default: ex.setDisplayMessage(networkMsg); //均視爲網絡錯誤 onNetError(ex); break; } } else if (e instanceof ResultException){ //服務器返回的錯誤 ResultException resultException = (ResultException) e; onResultError(resultException); } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException){ ex = new ApiException(e, ApiException.PARSE_ERROR); ex.setDisplayMessage(parseMsg); //均視爲解析錯誤 onNetError(ex); } else { ex = new ApiException(e, ApiException.UNKNOWN); ex.setDisplayMessage(unknownMsg); //未知錯誤 onNetError(ex); } } static long time; protected void onNetError(ApiException e){ long currentTime=System.currentTimeMillis(); if(currentTime-time>3000){ //防止連續反饋 time=currentTime; UIUtils.showToast("網絡加載失敗"); } e.printStackTrace(); onApiError(e); } /** * 錯誤回調 */ protected void onApiError(ApiException ex){ Log.i("YLLibrary","onApiError"); if(progressBar!=null) UIUtils.runOnUiThread(new Runnable() { @Override public void run() { progressBar.setVisibility(View.GONE); progressBar=null; } }); } // /** // * 權限錯誤,須要實現從新登陸操做 // */ // protected void onPermissionError(ApiException ex){ // ex.printStackTrace(); // } /** * 服務器返回的錯誤 */ protected synchronized void onResultError(ResultException ex){ // if(ex.getErrCode()== XApplication.API_ERROR){ // UIUtils.getContext().onApiError(); //能夠用來處理Token失效 // return ; // } if(ConstantValue.TOKEN_ERROR.equals(ex.getBaseInfo().getHeader().getCode()) &&!TextUtils.isEmpty(PreUtils.getString(R.string.token))){ //驗證token是否爲空是爲了防止連續兩次請求 PreUtils.putString(R.string.user_id,null); PreUtils.putString(R.string.token,null); PreUtils.putString(R.string.orgDistrict,null); if(BaseActivity.runActivity!=null){ Intent intent = new Intent(UIUtils.getContext(), LoginActivity.class); if(BaseActivity.runActivity instanceof MainActivity){ MainActivity activity= (MainActivity) BaseActivity.runActivity; int tabIndex=activity.getCurrentTab(); //activity.switchCurrentTab(0); activity.startActivityForResult(intent,tabIndex+10); }else { BaseActivity.runActivity.startActivity(intent); } } } Log.i("YLLibrary","resultError"); if(ex.getBaseInfo()!=null&&!TextUtils.isEmpty(ex.getBaseInfo().getHeader().getMessage())) UIUtils.showToast(ex.getBaseInfo().getHeader().getMessage()); ApiException apiException = new ApiException(ex, ex.getErrCode()); onApiError(apiException); } @Override public void onCompleted() { Log.i("YLLibrary","onCompleted"); if(progressBar!=null) UIUtils.runOnUiThread(new Runnable() { @Override public void run() { progressBar.setVisibility(View.GONE); progressBar=null; } }); } } 

實際接口請求的代碼,使用自定義異常回調的類——AbsAPICallback就能夠統一處理異常:

ApiRequestManager.createApi().problemDetail(dataBean.getId())
                .compose(ApiRequestManager.<QuestionDetailInfo>applySchedulers())
                .subscribe(new AbsAPICallback<QuestionDetailInfo>() { @Override public void onNext(QuestionDetailInfo baseInfo) { fillData(baseInfo); } }); 

更多精彩內容,關注微信公衆帳號「老於的筆記」,若是做品對您有所幫助,隨意打賞

做者:於連林520wcf 連接:https://www.jianshu.com/p/a7c2ef4e0fae 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。