1 .背景
做過app開發(fā)的都知道,一般公認的接口數(shù)據(jù)格式如下
{
"status": 200,
"data": {
"sex": "男",
"userId": 123456,
"userName": "張三"
},
"msg": "登錄成功"
}
當?shù)卿涘e誤的時候,返回的數(shù)據(jù)格式如下:
{
"status": 400,
"data": {},//或者null
"msg": "賬號不存在"
}
Android開發(fā)人員一般會在項目框架中統(tǒng)一處理解析后臺返回的數(shù)據(jù),而不需要每個接口手動解析數(shù)據(jù)了.比如,我們用Retrofit框架,請求接口時候,定義如下:
//登錄
@POST("login")
Observable<Response<LoginBean>> toLogin(@Body RequestBody body);
定義全局統(tǒng)一的接收數(shù)據(jù)的javaBean
public class Response<T> {
private int status; //狀態(tài)碼 0:失敗 1:成功
private String msg; // 顯示的信息
private T messageList; // 業(yè)務(wù)數(shù)據(jù)
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMsg() {
return msg == null ? "未知原因" : msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getResults() {
return messageList;
}
public void setResults(T results) {
this.messageList = results;
}
@Override
public String toString() {
return "Response{" +
"status=" + status +
", msg='" + msg + '\'' +
", results=" + messageList.toString() +
'}';
}
}
全局統(tǒng)一處理網(wǎng)絡(luò)請求,根據(jù)狀態(tài)碼區(qū)分業(yè)務(wù).
前面說了一堆正常操作,現(xiàn)在問題來了.....當?shù)卿浾_的時候,后臺返回的數(shù)據(jù)是正常的,但是失敗的時候,返回的數(shù)據(jù)如下
{
"messageList": [
{
"exceptionClass": "com.zdcx.base.common.exception.AppException",
"messageBody": "用戶已存在",
"messageForDeveloper": "MST00002",
"messageId": "MST00002",
"messageInstanceId": "3CVHZF33WFCC7KAFL5JZCLoHUY",
"messageLevel": "ERROR",
"messageSubject": "用戶已存在",
"path": "/api/cust/sms"
}
],
"status": 400
}
什么? messageList在正確的時候是個對象,在失敗的時候,確是個數(shù)組???你讓我怎么接??于是乎,找后臺,讓他們修改為統(tǒng)一的格式...遇到后臺好還好,不好的,比如我們的,一句話: 框架就是這樣封裝的,我改不了.....我內(nèi)心一萬頭草泥馬呼嘯而過....好吧,你不解決,我自己解決吧.....解決方案如下
2.方案一
retrofit接口中統(tǒng)一用Object接收,這兒在框架中可以統(tǒng)一處理錯誤的情況,但是status=200的時候,就得自己手動解析成對應(yīng)的javaBean了..
//登錄
@POST("login")
Observable<Response<Object>> toLogin(@Body RequestBody body);
但是這樣還得在回調(diào)中每次手動解析javaBean,也很麻煩啊...能不能像標準格式一樣,我只關(guān)心正確的業(yè)務(wù)數(shù)據(jù),回調(diào)過去直接是解析好的JavaBean呢?于是乎,方案二出來了
3.方案二: 使用OkHttp中的Interceptor
通過攔截器,攔截后臺返回的數(shù)據(jù),然后我們只需要判斷status,如果200,則直接返回response,如果不是,則拋出異常,此時異常會回調(diào)在OnError(本人項目是Rxjava+retrofit)中.攔截器代碼如下:
public abstract class ResponseBodyInterceptor implements Interceptor {
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
Request request = chain.request();
String url = request.url().toString();
Response response = chain.proceed(request);
ResponseBody responseBody = response.body();
if (responseBody != null) {
long contentLength = responseBody.contentLength();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.getBuffer();
if ("gzip".equals(response.headers().get("Content-Encoding"))) {
GzipSource gzippedResponseBody = new GzipSource(buffer.clone());
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
}
MediaType contentType = responseBody.contentType();
Charset charset;
if (contentType == null || contentType.charset(StandardCharsets.UTF_8) == null) {
charset = StandardCharsets.UTF_8;
} else {
charset = contentType.charset(StandardCharsets.UTF_8);
}
if (charset != null && contentLength != 0L) {
return intercept(response,url, buffer.clone().readString(charset));
}
}
return response;
}
abstract Response intercept(@NotNull Response response,String url, String body);
}
我們只需要繼承該攔截器,然后處理自己的業(yè)務(wù)邏輯就行了,示例如下:
/**
* Created by admin
* Created Time: 2020/3/5 16:22
* Description: 自己解析錯誤信息,并構(gòu)造成標準json格式 body就是后臺返回的json
* PS: 怎么解析根據(jù)自己業(yè)務(wù)來,我是解析我后臺給我的錯誤數(shù)據(jù)....
*/
public class HandleErrorInterceptor extends ResponseBodyInterceptor {
@Override
Response intercept(@NotNull Response response, String url, String body) {
try {
JSONObject jsonObject = new JSONObject(body);
int status = jsonObject.optInt("status");
if (status != 200) {
String errorMsg = jsonObject.getJSONArray("messageList").getJSONObject(0).getString("messageBody");
throw new MyException(status, errorMsg);
}
} catch (JSONException e) {
e.printStackTrace();
}
return response;
}
}
MyException的代碼如下
public class MyException extends RuntimeException {
private int httpCode;
private String errMsg;
public int getHttpCode() {
return httpCode;
}
public String getErrMsg() {
return errMsg;
}
public MyException(int httpCode, String message) {
super(message);
this.httpCode = httpCode;
this.errMsg = message;
}
}
為啥繼承RuntimeException 而不是HTTPException或者直接Exception呢? 其實我本來是想繼承HTTPException的,但是發(fā)現(xiàn)編譯器直接報錯....這就需要了解RuntimeException 和Exception的區(qū)別了,不懂的同學(xué)可以查一下...最終OkHttp拋出的該異常,會回調(diào)在OnError中,代碼如下:
/**
* 統(tǒng)一的網(wǎng)絡(luò)請求
*
* @param observable 被觀察者
* @param <T> 網(wǎng)絡(luò)返回的數(shù)據(jù)
* @param compositeDisposable 用于取消網(wǎng)絡(luò)請求
*/
public <T, K extends BasePresenter> void request(Observable<Response<T>> observable,
final CompositeDisposable compositeDisposable, final BaseView<K> view,
final CallBackListener<T> listener) {
if (observable == null || compositeDisposable == null || view == null || listener == null) {
return;
}
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response<T>>() {
@Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onNext(Response<T> response) {
//回調(diào)成功,業(yè)務(wù)邏輯省略....
}
@Override
public void onError(Throwable e) {
// 此處會回調(diào)剛才我們自定義MyException.....
if (e instanceof MyException) {
listener.onError(((MyException) e).getErrMsg());
if (((MyException) e).getHttpCode() == 403) {
toLogin();
}
return;
}
listener.onError(e.getMessage());
}
@Override
public void onComplete() {
}
});
}
到此,問題就解決了....此外,自定義攔截器中包含有請求的url,我們可以根據(jù)url來定向的修改某個接口的數(shù)據(jù)啊,從此徹底擺脫與后臺的各種撕逼吧...永遠不要跟有些人爭吵,這樣只會拉低你的智商....學(xué)會自己動手解決各種問題吧.