背景
使用Retrofit加Rxjava网络请求,配合FastJson进行解析,遇见问题。
案场还原
登录成功,接口返回如下
{
"Status": 200,
"Ticket": "84954b2d5691438591401f0dd415f21d",
"ErrorMessage": "",
"User": {
"Id": 53,
"Mobile": "13002179439",
"Name": "李乐",
"Nick": "李乐",
"TIcon": "https://admin-test.hcjapp.com:50000/webImgServer/upload/office/201806/04/1c1acb47-1126-469d-939b-cfbdad5ec3ac.jpg",
"CompanyId": "D93A2949320A4246AA2B9BFEB3900B84",
"CompanyName": "总部",
"RoomNum": "201",
"CardNum": "E50BB8DD",
"EmployeeNum": "201002",
"IsGym": true
},
"Authority": {}
}
登录失败,接口返回如下
{
"Status": 410,
"Ticket": "-1",
"ErrorMessage": "密码错误!",
"User": "{}",
"Authority": {}
}
可以看见User字段返回类型由一个对象变成了一个String字符串。 如果执行网络请求时还是使用的User对象泛型,当登陆失败返回User类型变为字符串之后就会报解析异常。
解决方式
解决方式一言以蔽之就是自定义json解析工厂类,在其中进行二次解析,当失败时捕获异常
- 首先定义一个泛型类,在同字段不同类型处使用
public class LoginUserEntity<T> extends BaseModel {
private int Status;
private String Ticket;
private String ErrorMessage;
private T User; //登录成功则泛型为User
private AuthorityBean Authority;
}
- 定义登录成功时返回的User字段类型
public class User{
private int Id;
private String Mobile;
private String Name;
private String Nick;
private String TIcon;
private String CompanyId;
private String CompanyName;
private String RoomNum;
private String CardNum;
private String EmployeeNum;
private boolean IsGym;
}
- 定义登录失败时返回的数据类型,这里的User字段就可以直接String
public class LoginUserErrorEntity extends BaseModel {
private int Status;
private String Ticket;
private String ErrorMessage;
private String User; //登录失败,String类型
private AuthorityBean Authority;
}
- 自定义json解析工厂类继承
Converter.Factory
public final class GsonDConverterFactory extends Converter.Factory {
public static GsonDConverterFactory create() {
return create(new Gson());
}
public static GsonDConverterFactory create(Gson gson) {
return new GsonDConverterFactory(gson);
}
private final Gson gson;
private GsonDConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
@Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
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);
}
}
- 实现
Converter<T, RequestBody>
请求体接口
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());
}
}
- 重点来了!自定义类实现响应体,重写convert方法,进行二次解析。当登录成功时,返回code等于200,于是我们使用
LoginUserEntity<User>
类型进行解析,当登录失败返回410时,我们使用LoginUserErrorEntity
进行解析(当然如果这里使用LoginUserEntity应该也是正确的),并抛出我们自定义的异常,一共后面的数据提取。
final 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 {
// 这里的type实际类型是 LoginUserEntity<User> User就是user字段的对象。
LoginUserEntity result = gson.fromJson(response, LoginUserEntity.class);
int code = result.getStatus();
if (code == 200) {
return gson.fromJson(response, type);
} else {
Log.d("HttpManager", "err==:" + response);
LoginUserErrorEntity errResponse = gson.fromJson(response, LoginUserErrorEntity.class);
if (code == 410) {
throw new ResultException(errResponse.getErrorMessage(), code);
} else {
throw new ResultException(errResponse.getErrorMessage(), code);
}
}
} finally {
value.close();
}
}
}
- 附上自定义异常代码
public class ResultException extends IOException {
private String errMsg;
private int errCode;
public ResultException(String errMsg, int errCode){
this.errMsg = errMsg;
this.errCode = errCode;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public int getErrCode() {
return errCode;
}
public void setErrCode(int errCode) {
this.errCode = errCode;
}
}
- 使用我们自定义的json数据解析器
sRetrofit1 = new Retrofit.Builder()
.client(httpClient)
.baseUrl(API_LOGIN)
.addConverterFactory(GsonDConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
并且一定要保证我们的接口返回的是正确的完整的数据类型,如 LoginUserEntity<User>
,否则会报错。
//登录
@POST("api/login/")
@Headers("Content-Type: application/json")
Observable<LoginUserEntity<User>> getAppLogin(@Body RequestBody body);
之后我们就使用retrofit加rxjava进行正常的网络流式请求。 要获取登陆失败时的数据,可以在Subscriber的onError中捕获异常,拿到数据,比如
@Override
public void onError(Throwable e) {
String error = "";
try {
if (e instanceof SocketTimeoutException) {
error = "网络连接超时,请稍后再试...";
} else if (e instanceof ConnectException) {
error = "网络连接超时,请稍后再试...";
} else if (e instanceof UnknownHostException) {
error = "网络连接超时,请稍后再试...";
} else {
if (e instanceof ResultException) {
error = ((ResultException) e).getErrMsg(); //抛出异常,抓取数据,拿到失败的errormsg
} else {
// error = "网络连接超时,请稍后再试...";
}
}
ToolAlert.showCustomShortToast(error); //alert管理
onMyError(error); //自定义的error
Log.e("error", mContext.getPackageName() + "=====Error=======>" + e.toString());
e.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
}
}