前言
直接上數(shù)據(jù)結(jié)構(gòu):
{
"code": 200,
"data": {
"id": "1",
"name": "name1",
"stargazers_count": 1
},
"msg": "請求成功"
}
上面的數(shù)據(jù)結(jié)構(gòu)是一般比較簡單而常見的數(shù)據(jù)結(jié)構(gòu),在正確的情況下我們只關(guān)心data里面的數(shù)據(jù),錯誤的情況下我們關(guān)心code和msg提示,而區(qū)分這兩種情況又要不斷的寫大量的樣板代碼,這不是首選。所以就有了兩種方法:
- 通過定義泛型T來處理
- 自定義ResponseBodyConverter實現(xiàn)統(tǒng)一處理
首先講講泛型T的處理方式,基本如下:
public class BaseDataModel<T> {
private int code;
private T data;
private String msg;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public boolean isSuccessful() {
return code == 200;
}
}
使用也比較簡單,只需要將泛型替換為相應(yīng)的實體類就行。但是這也有一個不好的地方就是一旦項目中對接的數(shù)據(jù)結(jié)構(gòu)變得繁雜的時候,就需要不斷的頂部相應(yīng)的實體了,還是比較麻煩,后面會講到,所以我們還是使用自定義ResponseBodyConverter實現(xiàn)統(tǒng)一處理。
實現(xiàn)
分析
我在使用Retrofit的時候,一般是使用的是GsonConverterFactory,而GsonConverterFactory則是負責(zé)將我們的提交的數(shù)據(jù)進行序列化和將返回的數(shù)據(jù)進行反序列化的,所以只要我們分析完成其中的代碼,看明白他是怎么對數(shù)據(jù)進行反序列化的,那么我們就能做到統(tǒng)一的數(shù)據(jù)轉(zhuǎn)換了。
源碼目錄

從上圖我們可以看到,其實只有三個類,分別是:GsonConverterFactory、GsonRequestBodyConverter和GsonResponseBodyConverter。
- GsonConverterFactory 主入口,負責(zé)聯(lián)通GsonRequestBodyConverter和GsonResponseBodyConverter
- GsonRequestBodyConverter 負責(zé)將注解為
@Body的對象序列化為Json字符串。 - GsonResponseBodyConverter 負責(zé)將返回的json字符串反序列化為對象。
其中GsonResponseBodyConverter的源碼內(nèi)容如下:
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override public T convert(ResponseBody value) throws IOException {
// 這里就是對返回結(jié)果進行處理
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
T result = adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} finally {
value.close();
}
}
}
從上面的源碼可以看到,convert方法中其實就是通過Gson進行看一次序列化而已,而ResponseBody提供了string方法,直接將返回結(jié)果轉(zhuǎn)換為了字符串,那么我們就可以在中間加一道工序,將其中的數(shù)據(jù)進行相關(guān)的判斷處理,再進行返回。
編碼
首先將GsonConverter的源碼copy過來,然后改個名字,如下:

為了模擬不同的數(shù)據(jù)接口,硬編碼了以下三種數(shù)據(jù)結(jié)構(gòu)。
- 數(shù)據(jù)結(jié)構(gòu)·1
{
"code": 200,
"message": "成功,但是沒有數(shù)據(jù)",
"data": []
}
- 數(shù)據(jù)結(jié)構(gòu)·2
{
"code": -1,
"message": "這里是接口返回的:錯誤的信息,拋出錯誤信息提示!",
"data": []
}
- 數(shù)據(jù)結(jié)構(gòu)·3
{
"code": 401,
"message": "這里是接口返回的:權(quán)限不足,請重新登錄!",
"data": []
}
從上面三種數(shù)據(jù)結(jié)構(gòu)分析,我們需要在code為200的情況下直接序列化data,其它情況下拋出code和msg,所以這里我們還需要定義一個Exception類來承載錯誤信息。
創(chuàng)建Exception類
Exception類如下,主要是對一些和后端約定的狀態(tài)碼進行轉(zhuǎn)義和包裹。
public class NetErrorException extends IOException {
private Throwable exception;
private int mErrorType = NO_CONNECT_ERROR;
private String mErrorMessage;
/*無連接異常*/
public static final int NoConnectError = 1;
/**
* 數(shù)據(jù)解析異常
*/
public static final int PARSE_ERROR = 0;
/**
* 無連接異常
*/
public static final int NO_CONNECT_ERROR = 1;
/*網(wǎng)絡(luò)連接超時*/
public static final int SocketTimeoutError = 6;
/**
* 無法連接到服務(wù)
*/
public static final int ConnectExceptionError = 7;
/**
* 服務(wù)器錯誤
*/
public static final int HttpException = 8;
/**
* 登陸失效
*/
public static final int LOGIN_OUT = 401;
/**
* 其他
*/
public static final int OTHER = -99;
/**
* 沒有網(wǎng)絡(luò)
*/
public static final int UNOKE = -1;
/**
* 無法找到
*/
public static final int NOT_FOUND = 404;
/*其他*/
public NetErrorException(Throwable exception, int mErrorType) {
this.exception = exception;
this.mErrorType = mErrorType;
}
public NetErrorException(String message, Throwable cause) {
super(message, cause);
}
public NetErrorException(String message, int mErrorType) {
super(message);
this.mErrorType = mErrorType;
this.mErrorMessage = message;
}
@Override
public String getMessage() {
if (!TextUtils.isEmpty(mErrorMessage)) {
return mErrorMessage;
}
switch (mErrorType) {
case PARSE_ERROR:
return "數(shù)據(jù)解析異常";
case NO_CONNECT_ERROR:
return "無連接異常";
case OTHER:
return mErrorMessage;
case UNOKE:
return "當(dāng)前無網(wǎng)絡(luò)連接";
case ConnectExceptionError:
return "無法連接到服務(wù)器,請檢查網(wǎng)絡(luò)連接后再試!";
case HttpException:
try {
if (exception.getMessage().equals("HTTP 500 Internal Server Error")) {
return "服務(wù)器發(fā)生錯誤!";
}
} catch (Exception e) {
e.printStackTrace();
}
if (exception.getMessage().contains("Not Found"))
return "無法連接到服務(wù)器,請檢查網(wǎng)絡(luò)連接后再試!";
return "服務(wù)器發(fā)生錯誤";
}
try {
return exception.getMessage();
} catch (Exception e) {
return "未知錯誤";
}
}
/**
* 獲取錯誤類型
*/
public int getErrorType() {
return mErrorType;
}
}
統(tǒng)一訂閱處理
由于這里使用的是RxJava,需要自定義一個Subscriber來對Convert拋出的Exception進行捕獲,也需要對其它Exception進行捕獲和包裹,防止發(fā)生錯誤后直接崩潰,代碼不多,如下:
public abstract class ApiSubscriber<T> extends ResourceSubscriber<T> {
@Override
public void onError(Throwable e) {
NetErrorException error = null;
if (e != null) {
// 對不是自定義拋出的錯誤進行解析
if (!(e instanceof NetErrorException)) {
if (e instanceof UnknownHostException) {
error = new NetErrorException(e, NetErrorException.NoConnectError);
} else if (e instanceof JSONException || e instanceof JsonParseException) {
error = new NetErrorException(e, NetErrorException.PARSE_ERROR);
} else if (e instanceof SocketTimeoutException) {
error = new NetErrorException(e, NetErrorException.SocketTimeoutError);
} else if (e instanceof ConnectException) {
error = new NetErrorException(e, NetErrorException.ConnectExceptionError);
} else {
error = new NetErrorException(e, NetErrorException.OTHER);
}
} else {
error = new NetErrorException(e.getMessage(), NetErrorException.OTHER);
}
}
// 回調(diào)抽象方法
onFail(error);
}
/**
* 回調(diào)錯誤
*/
protected abstract void onFail(NetErrorException error);
}
修改HandlerErrorGsonResponseBodyConverter
這一步其實沒什么難度,只是在convert方法中提前將ResponseBody.string()取出來,通過最簡單的JSONObject來判斷code,為200則返回data,否則拋出自定義錯誤。詳細代碼如下:
final class HandlerErrorGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
/**模擬的假數(shù)據(jù)*/
private final List<String> mockResult;
private final Random random;
HandlerErrorGsonResponseBodyConverter(TypeAdapter<T> adapter) {
this.random = new Random();
this.adapter = adapter;
mockResult = new ArrayList<>();
mockResult.add("{\"code\":200,\"message\":\"成功,但是沒有數(shù)據(jù)\",\"data\":[]}");
mockResult.add("{\"code\":-1,\"message\":\"這里是接口返回的:錯誤的信息,拋出錯誤信息提示!\",\"data\":[]}");
mockResult.add("{\"code\":401,\"message\":\"這里是接口返回的:權(quán)限不足,請重新登錄!\",\"data\":[]}");
}
@Override
public T convert(ResponseBody value) throws IOException {
// 這里就是對返回結(jié)果進行處理
String jsonString = value.string();
try {
// 這里為了模擬不同的網(wǎng)絡(luò)請求,所以采用了本地字符串的格式然后進行隨機選擇判斷結(jié)果。
int resultIndex = random.nextInt(mockResult.size() + 1);
if (resultIndex == mockResult.size()) {
return adapter.fromJson(jsonString);
} else {
// 這里模擬不同的數(shù)據(jù)結(jié)構(gòu)
jsonString = mockResult.get(resultIndex);
Log.e("TAG", "這里進行了返回結(jié)果的判斷");
// ------------------ JsonObject 只做了初略的判斷,具體情況自定
JSONObject object = new JSONObject(jsonString);
int code = object.getInt("code");
if (code != 200) {
throw new NetErrorException(object.getString("message"), code);
}
return adapter.fromJson(object.getString("data"));
}
} catch (JSONException e) {
e.printStackTrace();
throw new NetErrorException("數(shù)據(jù)解析異常", NetErrorException.PARSE_ERROR);
} finally {
value.close();
}
}
}
如何調(diào)用
主要是有以下兩個地方:
- 創(chuàng)建Retrofit的時候?qū)?code>addConverterFactory換成自定義的
HandlerErrorGsonConverterFactory.create() - RxJava的subscribeWith換成自定義的ApiSubscriber<T>。
部分代碼如下:
......
retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(HandlerErrorGsonConverterFactory.create()) // 這里使用的是用自己自定義的轉(zhuǎn)換器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
service = retrofit.create(GitHubService.class);
......
mResultTv.setText("創(chuàng)建請求................\n");
disposable = service.listRxJava2FlowableRepos("aohanyao", "owner")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new ApiSubscriber<List<Repo>>() {
@Override
public void onNext(List<Repo> repos) {
mResultTv.append("請求成功,repoCount:" + repos.size() + ":\n");
for (Repo repo : repos) {
mResultTv.append("repoName:" + repo.getName() + " star:" + repo.getStargazers_count() + "\n");
}
}
@Override
protected void onFail(NetErrorException error) {
mResultTv.append("請求失敗" + error.getMessage() + "................\n");
}
@Override
public void onComplete() {
mResultTv.append("請求成功................\n");
}
});
演示

結(jié)束
這里只寫了對返回狀態(tài)碼和錯誤的統(tǒng)一處理,并未對不同的數(shù)據(jù)結(jié)構(gòu)進行處理,下一篇將對不同的數(shù)據(jù)結(jié)構(gòu)進行統(tǒng)一處理。
微信公眾號
