目錄介紹
- 01.網絡請求異常分類
- 02.開發中注意問題
- 03.原始的處理方式
- 04.如何減少代碼耦合性
- 05.異常統一處理步驟
- 06.完成版代碼展示
好消息
- 博客筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
- 鏈接地址:https://github.com/yangchong2...
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!
01.網絡請求異常分類
-
網絡請求異常大概有哪些?
- 第一種:訪問接口異常,比如404,500等異常,出現這類異常,Retrofit會自動拋出異常。
- 第二種:解析數據異常,數據體發生變化可能會導致這個問題。
- 第三種:其他類型異常,比如服務器響應超時異常,鏈接失敗異常,網絡未連接異常等等。
- 第四種:網絡請求成功,但是服務器定義了異常狀態,比如token失效,參數傳遞錯誤,或者統一給提示(這個地方比較拗口,比如購物app,你購買n件商品請求接口成功,code爲200,但是服務器發現沒有這麼多商品,這個時候就會給你一個提示,然後客戶端拿到這個進行吐司)
02.開發中注意問題
-
在獲取數據的流程中,訪問接口和解析數據時都有可能會出錯,我們可以通過攔截器在這兩層攔截錯誤。
- 1.在訪問接口時,我們不用設置攔截器,因爲一旦出現錯誤,Retrofit會自動拋出異常。比如,常見請求異常404,500,503等等。爲了方便後期排查問題,這個可以在debug環境下打印日誌就可以。
- 2.在解析數據時,我們設置一個攔截器,判斷Result裏面的code是否爲成功,如果不成功,則要根據與服務器約定好的錯誤碼來拋出對應的異常。比如,token失效後跳轉登錄頁面,禁用同賬號登陸多臺設備,缺少參數,參數傳遞異常等等。
- 3.除此以外,爲了我們要儘量避免在View層對錯誤進行判斷,處理,我們必須還要設置一個攔截器,攔截onError事件,然後使用ExceptionUtils,讓其根據錯誤類型來分別處理。
03.原始的處理方式
-
最簡單的處理方式,直接對返回的throwable進行類型判斷處理
//請求,對throwable進行判斷 ServiceHelper.getInstance() .getModelResult(param1, param2) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Model>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { if(e instanceof HttpException){ //獲取對應statusCode和Message HttpException exception = (HttpException)e; String message = exception.response().message(); int code = exception.response().code(); }else if(e instanceof SSLHandshakeException){ //接下來就是各種異常類型判斷... }else if(e instanceof ...){ }... } @Override public void onNext(Model model) { if(model.getCode != CODE_SUCCESS){ int code = model.getCode(); switch (code){ case CODE_TOKEN_INVALID: ex.setDisplayMessage("重新登陸"); break; case CODE_NO_OTHER: ex.setDisplayMessage("其他情況"); break; case CODE_SHOW_TOAST: ex.setDisplayMessage("吐司服務器返回的提示"); break; case CODE_NO_MISSING_PARAMETER: ex.setDisplayMessage("缺少參數,用log記錄服務器提示"); break; default: ex.setDisplayMessage(message); break; } }else{ //正常處理邏輯 } } });
04.如何減少代碼耦合性
-
爲了不改變以前的代碼結構,那麼如何做才能夠徹底解耦呢?一般情況下使用Retrofit網絡請求框架,會有回調方法,如下所示:
package retrofit2; public interface Callback<T> { void onResponse(Call<T> var1, Response<T> var2); void onFailure(Call<T> var1, Throwable var2); }
-
不管以前代碼封裝與否,都希望一句代碼即可實現網絡請求攔截處理邏輯。那麼這個時候,我是怎麼處理的呢?
public class ResponseData<T> { private int code; private String message; private T t; public int getCode() { return code; } public String getMessage() { return message; } public T getT() { return t; } } new Callback<ResponseData<HomeBlogEntity>>(){ @Override public void onResponse(Call<ResponseData<HomeBlogEntity>> call, Response<ResponseData<HomeBlogEntity>> response) { int code = response.body().getCode(); String message = response.body().getMessage(); HomeBlogEntity t = response.body().getT(); if (code!= CODE_SUCCESS){ //網絡請求成功200,不過業務層執行服務端制定的異常邏輯 ExceptionUtils.serviceException(code,message); } else { //網絡請求成功,業務邏輯正常處理 } } @Override public void onFailure(Call call, Throwable throwable) { ExceptionUtils.handleException(throwable); } };
05.異常統一處理步驟
-
第一步:定義請求接口網絡層失敗的狀態碼
/**
*/
private static final int BAD_REQUEST = 400;
private static final int UNAUTHORIZED = 401;
private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int METHOD_NOT_ALLOWED = 405;
private static final int REQUEST_TIMEOUT = 408;
private static final int CONFLICT = 409;
private static final int PRECONDITION_FAILED = 412;
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;
```
-
第二步,接口請求成功,業務層失敗,服務端定義異常狀態碼
- 比如,登錄過期,提醒用戶重新登錄;
- 比如,添加商品,但是服務端發現庫存不足,這個時候接口請求成功,服務端定義業務層失敗,服務端給出提示語,客戶端進行吐司
- 比如,請求接口,參數異常或者類型錯誤,請求code爲200成功狀態,不過給出提示,這個時候客戶端用log打印服務端給出的提示語,方便快遞查找問題
- 比如,其他情況,接口請求成功,但是服務端定義業務層需要吐司服務端返回的對應提示語
/** * 服務器定義的狀態嗎 * 比如:登錄過期,提醒用戶重新登錄; * 添加商品,但是服務端發現庫存不足,這個時候接口請求成功,服務端定義業務層失敗,服務端給出提示語,客戶端進行吐司 * 請求接口,參數異常或者類型錯誤,請求code爲200成功狀態,不過給出提示,這個時候客戶端用log打印服務端給出的提示語,方便快遞查找問題 * 其他情況,接口請求成功,但是服務端定義業務層需要吐司服務端返回的對應提示語 */ /** * 完全成功 */ private static final int CODE_SUCCESS = 0; /** * Token 失效 */ public static final int CODE_TOKEN_INVALID = 401; /** * 缺少參數 */ public static final int CODE_NO_MISSING_PARAMETER = 400400; /** * 其他情況 */ public static final int CODE_NO_OTHER = 403; /** * 統一提示 */ public static final int CODE_SHOW_TOAST = 400000;
-
第三步,自定義Http層的異常和服務器定義的異常類
public class HttpException extends Exception { private int code; private String displayMessage; public HttpException(Throwable throwable, int code) { super(throwable); this.code = code; } public void setDisplayMessage(String displayMessage) { this.displayMessage = displayMessage; } public String getDisplayMessage() { return displayMessage; } public int getCode() { return code; } } public class ServerException extends RuntimeException { public int code; public String message; public int getCode() { return code; } public void setCode(int code) { this.code = code; } @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
-
第四步,統一處理異常邏輯如下所示
/** * 這個可以處理服務器請求成功,但是業務邏輯失敗,比如token失效需要重新登陸
*/
public static void serviceException(int code , String content){
if (code != CODE_SUCCESS){
ServerException serverException = new ServerException();
serverException.setCode(code);
serverException.setMessage(content);
handleException(serverException);
}
}
/**
* 這個是處理網絡異常,也可以處理業務中的異常
* @param e e異常
*/
public static void handleException(Throwable e){
HttpException ex;
//HTTP錯誤 網絡請求異常 比如常見404 500之類的等
if (e instanceof retrofit2.HttpException){
retrofit2.HttpException httpException = (retrofit2.HttpException) e;
ex = new HttpException(e, ErrorCode.HTTP_ERROR);
switch(httpException.code()){
case BAD_REQUEST:
case UNAUTHORIZED:
case FORBIDDEN:
case NOT_FOUND:
case METHOD_NOT_ALLOWED:
case REQUEST_TIMEOUT:
case CONFLICT:
case PRECONDITION_FAILED:
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
//均視爲網絡錯誤
default:
ex.setDisplayMessage("網絡錯誤"+httpException.code());
break;
}
} else if (e instanceof ServerException){
//服務器返回的錯誤
ServerException resultException = (ServerException) e;
int code = resultException.getCode();
String message = resultException.getMessage();
ex = new HttpException(resultException, ErrorCode.SERVER_ERROR);
switch (code){
case CODE_TOKEN_INVALID:
ex.setDisplayMessage("token失效");
//下面這裏可以統一處理跳轉登錄頁面的操作邏輯
break;
case CODE_NO_OTHER:
ex.setDisplayMessage("其他情況");
break;
case CODE_SHOW_TOAST:
ex.setDisplayMessage("吐司");
break;
case CODE_NO_MISSING_PARAMETER:
ex.setDisplayMessage("缺少參數");
break;
default:
ex.setDisplayMessage(message);
break;
}
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException){
ex = new HttpException(e, ErrorCode.PARSE_ERROR);
//均視爲解析錯誤
ex.setDisplayMessage("解析錯誤");
}else if(e instanceof ConnectException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//均視爲網絡錯誤
ex.setDisplayMessage("連接失敗");
} else if(e instanceof java.net.UnknownHostException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//網絡未連接
ex.setDisplayMessage("網絡未連接");
} else if (e instanceof SocketTimeoutException) {
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//網絡未連接
ex.setDisplayMessage("服務器響應超時");
} else {
ex = new HttpException(e, ErrorCode.UNKNOWN);
//未知錯誤
ex.setDisplayMessage("未知錯誤");
}
String displayMessage = ex.getDisplayMessage();
//這裏直接吐司日誌異常內容,注意正式項目中一定要注意吐司合適的內容
ToastUtils.showRoundRectToast(displayMessage);
}
```
-
第五步,如何調用
@Override public void onError(Throwable e) { //直接調用即可 ExceptionUtils.handleException(e); }
06.完成版代碼展示
-
如下所示
public class ExceptionUtils { /* * 在使用Retrofit+RxJava時,我們訪問接口,獲取數據的流程一般是這樣的:訂閱->訪問接口->解析數據->展示。
*
* 在獲取數據的流程中,訪問接口和解析數據時都有可能會出錯,我們可以通過攔截器在這兩層攔截錯誤。
* 1.在訪問接口時,我們不用設置攔截器,因爲一旦出現錯誤,Retrofit會自動拋出異常。
* 2.在解析數據時,我們設置一個攔截器,判斷Result裏面的code是否爲成功,如果不成功,則要根據與服務器約定好的錯誤碼來拋出對應的異常。
* 3.除此以外,爲了我們要儘量避免在View層對錯誤進行判斷,處理,我們必須還要設置一個攔截器,攔截onError事件,然後使用ExceptionHandler,讓其根據錯誤類型來分別處理。
*/
/**
* 對應HTTP的狀態碼
*/
private static final int BAD_REQUEST = 400;
private static final int UNAUTHORIZED = 401;
private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int METHOD_NOT_ALLOWED = 405;
private static final int REQUEST_TIMEOUT = 408;
private static final int CONFLICT = 409;
private static final int PRECONDITION_FAILED = 412;
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;
/**
* 服務器定義的狀態嗎
* 比如:登錄過期,提醒用戶重新登錄;
* 添加商品,但是服務端發現庫存不足,這個時候接口請求成功,服務端定義業務層失敗,服務端給出提示語,客戶端進行吐司
* 請求接口,參數異常或者類型錯誤,請求code爲200成功狀態,不過給出提示,這個時候客戶端用log打印服務端給出的提示語,方便快遞查找問題
* 其他情況,接口請求成功,但是服務端定義業務層需要吐司服務端返回的對應提示語
*/
/**
* 完全成功
*/
private static final int CODE_SUCCESS = 0;
/**
* Token 失效
*/
public static final int CODE_TOKEN_INVALID = 401;
/**
* 缺少參數
*/
public static final int CODE_NO_MISSING_PARAMETER = 400400;
/**
* 其他情況
*/
public static final int CODE_NO_OTHER = 403;
/**
* 統一提示
*/
public static final int CODE_SHOW_TOAST = 400000;
/**
* 這個可以處理服務器請求成功,但是業務邏輯失敗,比如token失效需要重新登陸
* @param code 自定義的code碼
*/
public static void serviceException(int code , String content){
if (code != CODE_SUCCESS){
ServerException serverException = new ServerException();
serverException.setCode(code);
serverException.setMessage(content);
handleException(serverException);
}
}
/**
* 這個是處理網絡異常,也可以處理業務中的異常
* @param e e異常
*/
public static void handleException(Throwable e){
HttpException ex;
//HTTP錯誤 網絡請求異常 比如常見404 500之類的等
if (e instanceof retrofit2.HttpException){
retrofit2.HttpException httpException = (retrofit2.HttpException) e;
ex = new HttpException(e, ErrorCode.HTTP_ERROR);
switch(httpException.code()){
case BAD_REQUEST:
case UNAUTHORIZED:
case FORBIDDEN:
case NOT_FOUND:
case METHOD_NOT_ALLOWED:
case REQUEST_TIMEOUT:
case CONFLICT:
case PRECONDITION_FAILED:
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
//均視爲網絡錯誤
default:
ex.setDisplayMessage("網絡錯誤"+httpException.code());
break;
}
} else if (e instanceof ServerException){
//服務器返回的錯誤
ServerException resultException = (ServerException) e;
int code = resultException.getCode();
String message = resultException.getMessage();
ex = new HttpException(resultException, ErrorCode.SERVER_ERROR);
switch (code){
case CODE_TOKEN_INVALID:
ex.setDisplayMessage("重新登陸");
break;
case CODE_NO_OTHER:
ex.setDisplayMessage("其他情況");
break;
case CODE_SHOW_TOAST:
ex.setDisplayMessage("吐司");
break;
case CODE_NO_MISSING_PARAMETER:
ex.setDisplayMessage("缺少參數");
break;
default:
ex.setDisplayMessage(message);
break;
}
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException){
ex = new HttpException(e, ErrorCode.PARSE_ERROR);
//均視爲解析錯誤
ex.setDisplayMessage("解析錯誤");
}else if(e instanceof ConnectException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//均視爲網絡錯誤
ex.setDisplayMessage("連接失敗");
} else if(e instanceof java.net.UnknownHostException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//網絡未連接
ex.setDisplayMessage("網絡未連接");
} else if (e instanceof SocketTimeoutException) {
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//網絡未連接
ex.setDisplayMessage("服務器響應超時");
} else {
ex = new HttpException(e, ErrorCode.UNKNOWN);
//未知錯誤
ex.setDisplayMessage("未知錯誤");
}
String displayMessage = ex.getDisplayMessage();
//這裏直接吐司日誌異常內容,注意正式項目中一定要注意吐司合適的內容
ToastUtils.showRoundRectToast(displayMessage);
}
}
```
其他介紹
01.關於博客彙總鏈接
02.關於我的博客
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/...
- 簡書:http://www.jianshu.com/u/b7b2...
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
- 開源中國:https://my.oschina.net/zbj161...
- 泡在網上的日子:http://www.jcodecraeer.com/me...
- 郵箱:[email protected]
- 阿里雲博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xi...
- 掘金:https://juejin.im/user/593943...