使用Retrofit+Gson 處理網(wǎng)絡(luò)數(shù)據(jù)测蹲,后臺(tái)數(shù)據(jù)返回不規(guī)范,因?yàn)榉N種原因后臺(tái)無(wú)法修改篮赢,直接解析會(huì)報(bào)錯(cuò)JsonSyntaxException琉挖,有時(shí)需要我們做容錯(cuò)處理
(當(dāng)然可以解析時(shí)傳String,這時(shí)就需要我們手動(dòng)解析每個(gè)數(shù)據(jù)結(jié)構(gòu),比較麻煩)
數(shù)據(jù)不規(guī)范的奇葩問(wèn)題
問(wèn)題1:
int類型為空時(shí)寥茫,返回了“”矾麻,這時(shí)會(huì)報(bào)錯(cuò) java.lang.NumberFormatException: empty String
問(wèn)題2:
原始字段原來(lái)為String,后期修改為其他數(shù)據(jù)類型,但是未通知客戶端
(在解析數(shù)據(jù)的時(shí)候射富,無(wú)用字段最好去除)
問(wèn)題3:
有數(shù)據(jù)時(shí)返回對(duì)象,無(wú)數(shù)據(jù)時(shí)返回字符串 “”
有數(shù)據(jù)
{
"code": "0",
"message": "",
"result": {
"age":10
},
}
無(wú)數(shù)據(jù)
{
"code": "0",
"message": "",
"result": ""
}
問(wèn)題4 同一個(gè)字段名字限次,code 不同柴灯,返回不同的類型
這個(gè)沒(méi)有什么好辦法赠群,只能用object接收,根據(jù)code 手動(dòng)解析
為了解決這幾個(gè)問(wèn)題查描,我們需要了解retrofit 和gson的解析原理冬三,找到正確位置來(lái)添加我們的容錯(cuò)代碼
當(dāng)前分析的Gson版本號(hào)為2.8.5
Gson gson = new Gson();
gson.fromJson(str,BaseTest.class);
首先,創(chuàng)建gson初始化時(shí)敌蚜,會(huì)添加解析TypeAdapterFactory窝爪,后面主要使用它來(lái)解析數(shù)據(jù)
下面步入正題,看Gson.formJson()內(nèi)部纷跛,最終調(diào)用
public <T> T fromJson(JsonReader reader, Type typeOfT)
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
...
try {
reader.peek();
isEmpty = false;
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
return object;
} catch (EOFException e) {
...
}
根據(jù)typeToken獲取解析的adapter (gson初始化是默認(rèn)添加的TypeAdapterFactory獲取)
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
...
try {
...
for (TypeAdapterFactory factory : factories) {
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
} finally {
...
}
}
常用factory生成類:TypeAdapters 各種基礎(chǔ)解析器(int String...)
其中自定義類factory 為ReflectiveTypeAdapterFactory
會(huì)解析整個(gè)類忽舟,拿到具體數(shù)據(jù)類型叮阅,進(jìn)而調(diào)用上一步的方法gson. getAdapter()解析基礎(chǔ)類型
gson提供了自定義解析器的方法
方法1: 直接定義factoty
Gson gson = new Gson().newBuilder()
.registerTypeAdapterFactory(new MyTypeAdapterFactory<Test>()).create();
public class MyTypeAdapterFactory<T> implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (rawType == String.class) {
return (TypeAdapter<T>) new MyAdapter();
}if(rawType == int.class){
return (TypeAdapter<T>)IntTypeAdapter;
}
return null;
}
}
方法2: 定義adapter
gson = new GsonBuilder()
.setLenient()
.registerTypeAdapter(long.class, LongTypeAdapter)
.registerTypeAdapter(Long.class, LongTypeAdapter)
.registerTypeAdapter(int.class, IntTypeAdapter)
.registerTypeAdapter(Integer.class, IntTypeAdapter)
.registerTypeAdapter(String.class,StringTypeAdapter)
.create();
到此浩姥,我們可以解決 問(wèn)題1 問(wèn)題2
- int 字端,但后臺(tái)返回“”兜挨,這時(shí)會(huì)報(bào)錯(cuò) java.lang.NumberFormatException: empty String
private TypeAdapter<Number> IntTypeAdapter = new TypeAdapter<Number>() {
@Override
public void write(JsonWriter out, Number value) throws IOException {
out.value(value);
}
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
try {
String result = in.nextString();
if ("".equals(result)) {
return null;
}
return Integer.parseInt(result);
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
};
- String 字端眯分,但后臺(tái)返回了對(duì)象
private TypeAdapter<String> StringTypeAdapter = new TypeAdapter<String>() {
@Override
public void write(JsonWriter out, String value) throws IOException {
LogUtils.e("write");
out.value(value);
}
@Override
public String read(JsonReader in) throws IOException {
// LogUtils.e("read");
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
// number類的也會(huì)執(zhí)行這個(gè)方法弊决,但是number返回null 會(huì)報(bào)錯(cuò),所以把Number排除
if(in.peek() != JsonToken.STRING && in.peek() != JsonToken.NUMBER){
// LogUtils.e("typeAdapter","stringTypeAdapter 不是String = "+in.peek());
//不是String与倡,直接跳過(guò)昆稿,不解析
in.skipValue();
return null;
}
String result = in.nextString();
// LogUtils.e("typeAdapter","stringTypeAdapter ="+result);
return result;
}};
問(wèn)題3 通過(guò)自定義一個(gè) ReflectiveTypeAdapterFactory可能也可以實(shí)現(xiàn)溉潭,但是比較麻煩
代碼中使用retrofit+gson,查看報(bào)錯(cuò)位置
解析過(guò)程中okhttpCall ---> callback.onFailure(OkHttpCall.this, e);
java.lang.IllegalStateException: Expected STRING but was BEGIN_OBJECT at line 1 column 353 path $.data....
下面看一下retrofit+gson實(shí)現(xiàn)解析的過(guò)程
當(dāng)前分析的retrofit版本號(hào)為2.6.0
首先retrofit 入口處
Retrofit.Builder builder = new Retrofit.Builder();
...
//自定義解析器
builder.addConverterFactory(GsonConverterFactory.create(gson));
retrofitsingleton = builder.build();
return retrofitsingleton.create(clazz);
Retrofit.create()方法動(dòng)態(tài)代理
public <T> T create(final Class<T> service) {
...
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override public @Nullable Object invoke(Object proxy, Method method,
@Nullable Object[] args) throws Throwable {
...
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
繼續(xù)跟進(jìn) loadServiceMethod(method).invoke()方法
ServiceMethod ---HttpServiceMethod.invoke()
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
Call adapt(call args) 最終返回的是okhttpCall
最終調(diào)用 okhttpCall.enqueue()岛抄,可以看到解析數(shù)據(jù)位置
@Override public void enqueue(final Callback<T> callback) {
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
//***********************
//***解析數(shù)據(jù)位置
//***********************
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
//***********************
//***失敗調(diào)用callback.onFailure(OkHttpCall.this, e);
//***********************
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great
}
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great
}
}
});
}
接下來(lái)看具體解析過(guò)程 response=parseResponse(rawResponse)
Converter<ResponseBody, T> responseConverter;
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
...
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
···
}
可以看到解析為 Converter<ResponseBody, T> responseConverter;
responseConverter 在 Retrofit build時(shí)初始化 (BuiltInConverters)
retrofit提供了自定義方法:
public Builder addConverterFactory(Converter.Factory factory) {
需要我們自定義ConverterFactory
可以參考 api 'com.squareup.retrofit2:converter-gson:2.6.0'中
GsonConverterFactory
下面通過(guò)自定義ConverterFactory夫椭,解決第三個(gè)問(wèn)題
3.有數(shù)據(jù)時(shí)返回的是對(duì)象氯庆,無(wú)數(shù)據(jù)的時(shí)候返回了“”
{
"code": "0",
"message": "",
"result": [{
"age":10
}],
}
{
"code": "0",
"message": "",
"result": ""
}
簡(jiǎn)單處理,當(dāng)獲取數(shù)據(jù)為“”修改為 result = null
package com.nucarf.base.retrofit.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
public final class GsonConverterFactory 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 GsonConverterFactory 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.
*/
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static GsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new GsonConverterFactory(gson);
}
private final Gson gson;
private GsonConverterFactory(Gson gson) {
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, adapter);
}
@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);
}
}
package com.nucarf.base.retrofit.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import retrofit2.Converter;
/**
* author : li
* date : 2020/10/13/1:43 PM
*/
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.get("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());
}
}
package com.nucarf.base.utils;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import okhttp3.ResponseBody;
import retrofit2.Converter;
package com.nucarf.base.utils;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import okhttp3.ResponseBody;
import retrofit2.Converter;
/**
* author : li
* date : 2020/10/13/1:42 PM
*/
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 {
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;
} catch (JsonSyntaxException e) {//當(dāng)catch到這個(gè)錯(cuò)誤說(shuō)明gson解析錯(cuò)誤
return null;
} finally {
value.close();
}
}
}
Retrofit.Builder builder = new Retrofit.Builder();
...
//調(diào)用
builder.addConverterFactory(GsonConverterFactory.create(gson));
return builder.build().create(clazz);
問(wèn)題4 同一個(gè)字段名字洞豁,code 不同丈挟,返回不同的類型
只能用object接收,可以在baseresult中 解析
public class BaseResult<T> {
private String code;
private T result;
private Object message;//字段名字曙咽,code 不同返回不同的類型
public Object getMessage() {
return message;
}
public void setMessage(String message) {
this.msg = message;
}
//手動(dòng)解析
public boolean isSuccessed() {
try {
if (code.equals(RetrofitConfig.STATUS_NCARF_SUCCESS)||code.equals(RetrofitConfig.STATUS_NCARF_DEVIC_ERROR)) {
if (getMessage() instanceof LinkedTreeMap) {
Gson gson = new Gson();
String json = gson.toJson(getMessage());
MessageBean messageBean = gson.fromJson(json,MessageBean.class);
···
}
return true;
} else {
if (getMessage() instanceof String && !TextUtils.isEmpty(getMessage().toString())) {
ToastUtils.show_middle_pic(R.mipmap.icon_toast_error, getMessage() + "", 0);
}
···
return false;
}
} catch (Exception e) {
ToastUtils.show_middle_pic(R.mipmap.icon_toast_error, "網(wǎng)絡(luò)錯(cuò)誤", 0);
return false;
}
}
}
參考
https://blog.csdn.net/u012422440/article/details/48860893
http://www.reibang.com/p/f723a5ac6e37
https://blog.csdn.net/u013064618/article/details/53486604?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param