最近事比較多属划,距離上次寫文章已經(jīng)過(guò)去了一個(gè)月了。上一篇文章Retrofit全攻略——基礎(chǔ)篇 介紹了Retrofit的基礎(chǔ)用法候生,這篇文章介紹點(diǎn)進(jìn)階的用法同眯。
打印網(wǎng)絡(luò)日志
在開(kāi)發(fā)階段,為了方便調(diào)試唯鸭,我們需要查看網(wǎng)絡(luò)日志须蜗。因?yàn)?code>Retrofit2.0+底層是采用的OKHttp
請(qǐng)求的∧扛龋可以給OKHttp設(shè)置攔截器明肮,用來(lái)打印日志。
首先可以在app/build.gradle
中添加依賴缭付,這是官方的日志攔截器柿估。
compile 'com.squareup.okhttp3:logging-interceptor:3.3.0'
然后在代碼中設(shè)置:
public static Retrofit getRetrofit() {
//如果mRetrofit為空 或者服務(wù)器地址改變 重新創(chuàng)建
if (mRetrofit == null) {
OkHttpClient httpClient;
OkHttpClient.Builder builder=new OkHttpClient.Builder();
//階段分為開(kāi)發(fā)和發(fā)布階段,當(dāng)前為開(kāi)發(fā)階段設(shè)置攔截器
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
//設(shè)置攔截器級(jí)別
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logging);
}
httpClient=builder.build();
//構(gòu)建Retrofit
mRetrofit = new Retrofit.Builder()
//配置服務(wù)器路徑
.baseUrl(mServerUrl)
//返回的數(shù)據(jù)通過(guò)Gson解析
.addConverterFactory(GsonConverterFactory.create())
//配置回調(diào)庫(kù)陷猫,采用RxJava
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
//設(shè)置OKHttp模板
.client(httpClient)
.build();
}
return mRetrofit;
}
當(dāng)處于開(kāi)發(fā)階段的時(shí)候秫舌,設(shè)置監(jiān)聽(tīng)日志的攔截器的妖。攔截有4個(gè)級(jí)別,分別是
- BODY
- HEADERS
- BASIC
- NONE
其中BODY
輸出的日志是最全的足陨。
添加相同的請(qǐng)求參數(shù)
為了更好的管理迭代版本嫂粟,一般每次發(fā)起請(qǐng)求的時(shí)候都傳輸當(dāng)前程序的版本號(hào)到服務(wù)器。
有些項(xiàng)目我們每次還會(huì)傳用戶id墨缘,token令牌等相同的參數(shù)星虹。
如果在每個(gè)請(qǐng)求的接口都添加這些參數(shù)太繁瑣。Retrofit可以通過(guò)攔截器添加相同的請(qǐng)求參數(shù)镊讼,無(wú)需再每個(gè)接口添加了搁凸。
步驟一,自己攔截器
public class CommonInterceptor implements Interceptor {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request oldRequest = chain.request();
// 添加新的參數(shù)
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));
// 新的請(qǐng)求
Request newRequest = oldRequest.newBuilder()
.method(oldRequest.method(), oldRequest.body())
.url(authorizedUrlBuilder.build())
.build();
return chain.proceed(newRequest);
}
}
實(shí)現(xiàn)原理就是攔截之前的請(qǐng)求狠毯,添加完參數(shù)护糖,再傳遞新的請(qǐng)求。這個(gè)位置我添加了四個(gè)公共的參數(shù)嚼松。
然后再Retrofit初始化的時(shí)候配置嫡良。
if (mRetrofit == null) {
OkHttpClient httpClient;
OkHttpClient.Builder builder=new OkHttpClient.Builder();
//添加公共參數(shù)
builder.addInterceptor(new CommonInterceptor());
httpClient=builder.build();
//構(gòu)建Retrofit
mRetrofit = new Retrofit.Builder()
//....
.client(httpClient)
.build();
}
處理約定錯(cuò)誤
除了常見(jiàn)的404,500等異常献酗,網(wǎng)絡(luò)請(qǐng)求中我們往往還會(huì)約定些異常寝受,比如token失效,賬號(hào)異常等等罕偎。
以token失效為例很澄,每次請(qǐng)求我們都需要驗(yàn)證是否失效,如果在每個(gè)接口都處理一遍錯(cuò)誤就有點(diǎn)太繁瑣了颜及。
我們可以統(tǒng)一處理下錯(cuò)誤甩苛。
步驟一,Retrofit初始化時(shí)添加自定義轉(zhuǎn)化器
mRetrofit = new Retrofit.Builder()
//配置服務(wù)器路徑
baseUrl(mServerUrl)
//配置回調(diào)庫(kù)俏站,采用RxJava
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
//配置轉(zhuǎn)化庫(kù)讯蒲,默認(rèn)是Gson,這里修改了肄扎。
.addConverterFactory(ResponseConverterFactory.create())
.client(httpClient)
.build();
步驟二 創(chuàng)建ResponseConverterFactory
步驟一 ResponseConverterFactory
這個(gè)類是需要我們自己創(chuàng)建的墨林。
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);
}
}
這里面我們自定義了請(qǐng)求和響應(yīng)時(shí)解析JSON的轉(zhuǎn)換器——GsonRequestBodyConverter
和GsonResponseBodyConverter
。
其中GsonRequestBodyConverter
負(fù)責(zé)處理請(qǐng)求時(shí)傳遞JSON對(duì)象的格式犯祠,不需要額外處理任何事旭等,直接使用默認(rèn)的GSON解析。代碼我直接貼出來(lái):
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
負(fù)責(zé)把響應(yīng)的數(shù)據(jù)轉(zhuǎn)換成JSON格式衡载,這個(gè)我們需要處理一下搔耕。
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解析為異常消息文本 錯(cuò)誤碼可以自己指定.
throw new ResultException(-1024, baseInfo,response);
}
} finally {
}
}
}
這種情況只是應(yīng)用于后臺(tái)接口數(shù)據(jù)統(tǒng)一的情況。比如我們項(xiàng)目的格式是這樣的
{
header : {"message":"token失效","code":"99"}
data : {}
}
當(dāng)code值是1的時(shí)候月劈,表示正確度迂,其它數(shù)字表示錯(cuò)誤。只有正確的時(shí)候data才會(huì)有內(nèi)容猜揪。
這里我用BaseInfo解析這個(gè)JSON:
public class BaseInfo {
/**
* header : {"message":"用戶名或密碼錯(cuò)誤","code":"0"}
* data : {}
*/
private HeaderBean header;
public HeaderBean getHeader() {
return header;
}
public void setHeader(HeaderBean header) {
this.header = header;
}
public static class HeaderBean {
/**
* message : 用戶名或密碼錯(cuò)誤
* 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;
}
}
}
服務(wù)器返回的數(shù)據(jù)實(shí)體對(duì)象全部繼承BaseInfo
只是data內(nèi)容不一樣惭墓。
ResultException
這個(gè)類用于捕獲服務(wù)器約定的錯(cuò)誤類型
/**
* 這個(gè)類用于捕獲服務(wù)器約定的錯(cuò)誤類型
*/
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> {
//對(duì)應(yīng)HTTP的狀態(tài)碼
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;
//出錯(cuò)提示
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(聯(lián)網(wǎng)失敗)";
parseMsg="json parser error(JSON解析失敗)";
unknownMsg="unknown error(未知錯(cuò)誤)";
}
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錯(cuò)誤
HttpException httpException = (HttpException) e;
ex = new ApiException(e, httpException.code());
switch(httpException.code()){
case UNAUTHORIZED:
case FORBIDDEN:
// onPermissionError(ex); //權(quán)限錯(cuò)誤,需要實(shí)現(xiàn)
// break;
case NOT_FOUND:
case REQUEST_TIMEOUT:
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
default:
ex.setDisplayMessage(networkMsg); //均視為網(wǎng)絡(luò)錯(cuò)誤
onNetError(ex);
break;
}
} else if (e instanceof ResultException){ //服務(wù)器返回的錯(cuò)誤
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); //均視為解析錯(cuò)誤
onNetError(ex);
} else {
ex = new ApiException(e, ApiException.UNKNOWN);
ex.setDisplayMessage(unknownMsg); //未知錯(cuò)誤
onNetError(ex);
}
}
static long time;
protected void onNetError(ApiException e){
long currentTime=System.currentTimeMillis();
if(currentTime-time>3000){ //防止連續(xù)反饋
time=currentTime;
UIUtils.showToast("網(wǎng)絡(luò)加載失敗");
}
e.printStackTrace();
onApiError(e);
}
/**
* 錯(cuò)誤回調(diào)
*/
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;
}
});
}
// /**
// * 權(quán)限錯(cuò)誤而姐,需要實(shí)現(xiàn)重新登錄操作
// */
// protected void onPermissionError(ApiException ex){
// ex.printStackTrace();
// }
/**
* 服務(wù)器返回的錯(cuò)誤
*/
protected synchronized void onResultError(ResultException ex){
// if(ex.getErrCode()== XApplication.API_ERROR){
// UIUtils.getContext().onApiError(); //可以用來(lái)處理Token失效
// return ;
// }
if(ConstantValue.TOKEN_ERROR.equals(ex.getBaseInfo().getHeader().getCode())
&&!TextUtils.isEmpty(PreUtils.getString(R.string.token))){ //驗(yàn)證token是否為空是為了防止連續(xù)兩次請(qǐng)求
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;
}
});
}
}
實(shí)際接口請(qǐng)求的代碼腊凶,使用自定義異常回調(diào)的類——AbsAPICallback
就可以統(tǒng)一處理異常:
ApiRequestManager.createApi().problemDetail(dataBean.getId())
.compose(ApiRequestManager.<QuestionDetailInfo>applySchedulers())
.subscribe(new AbsAPICallback<QuestionDetailInfo>() {
@Override
public void onNext(QuestionDetailInfo baseInfo) {
fillData(baseInfo);
}
});