项目上使用Retrofit
作为网络请求框架,最近有个需要对响应结果进行处理的需求,由于让各个模块自己处理响应结果不利于维护,所以需要统一处理,在处理的过程中遇到些问题,在此记录下来。
1.构建Retrofit
// 构建Retrofit代码如下:
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.addNetworkInterceptor(new NetworkInterceptor());
clientBuilder.connectTimeout(6, TimeUnit.SECONDS);
clientBuilder.writeTimeout(6, TimeUnit.SECONDS);
clientBuilder.readTimeout(6, TimeUnit.SECONDS);
Retrofit retrofit = new Retrofit.Builder()
.client(clientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl("https:/xxxxxx")
.build();
从构建Retrofit
代码中可以看到GsonConverterFactory
这个类就是把响应结果转变成我们想要的结果的类,所以如果需要统一处理响应结果的话,应该可以在这里面添加处理。
2.解析Converter
// GsonConverterFactory
public final class GsonConverterFactory extends Converter.Factory {
public static GsonConverterFactory create() {
return create(new Gson());
}
@SuppressWarnings("ConstantConditions")
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);
}
}
GsonConverterFactory
类有两个重要的方法responseBodyConverter()
和requestBodyConverter()
,从这两个方法名称就可以看出,前者是用来处理响应的,后者是用来处理请求的。由于我们只需要处理响应结果,所以查看下GsonResponseBodyConverter
代码。
// GsonResponseBodyConverter
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;
} finally {
value.close();
}
}
}
GsonResonseBodyConverter
类除了构造方法,就只有一个convert()
方法,从convert()
方法的实现可以看出这里就是处理响应的地方,所以如果我们需要统一处理响应结果,在这个方法里添加即可。
3.重写Converter
想要重写Converter
,只能通过复制代码然后新建类的方式来重写。
// 重写GsonConverterFactory
public final class CustomGsonConverterFactory extends Converter.Factory {
public static CustomGsonConverterFactory create() {
return create(new Gson());
}
@SuppressWarnings("ConstantConditions")
public static CustomGsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new CustomGsonConverterFactory(gson);
}
private final Gson gson;
private CustomGsonConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
// 返回重写的GsonResponseConverter
return new CustomGsonResponseBodyConverter<>(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);
}
}
在CustomGsonConverterFactory
的responseBodyConverter()
方法中,我们返回了重写的GsonResponseConverter
。
// 重写GsonResponseConverter
final class CustomGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private static final String TAG = "ResponseBodyConverter";
private final Gson gson;
private final TypeAdapter<T> adapter;
CustomGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string(); // value只能读取一次,一次之后就会关闭,所以需要保存
try {
JSONObject jsonObject = new JSONObject(response);
String code = jsonObject.getString("code");
String description = jsonObject.getString("description");
LogUtil.d(TAG, "convert: code = " + code + ", description = " + description);
handleResponseCode(code);
} catch (JSONException e) {
e.printStackTrace();
}
MediaType contentType = value.contentType();
Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
InputStream inputStream = new ByteArrayInputStream(response.getBytes());
Reader reader = new InputStreamReader(inputStream, charset);
JsonReader jsonReader = gson.newJsonReader(reader);
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();
}
}
private void handleResponseCode(String code) {
// todo 添加处理逻辑
}
}
CustomGsonResponseBodyConverter
中的convert()
方法实现与GsonResponseBodyConverter
中的convert()
方法实现有很大不同,因为我们要通过传进来的参数value
的string()
方法来获取完整的响应结果,但是通过value.string()
获取响应结果后,会把该数据源给关闭,这时候如果还是按照之前的方式value.charStream()
获取数据,是会出现报错closed
的,所以需要先保存结果,然后再通过IO流的方式把结果传给JsonReader
,这一点是特别需要注意的地方。
4.注意事项及总结
统一处理响应结果需要注意的地方就在GsonResponseBodyConverter
的convert()
方法里面和ResponseBody
类的使用。
// ResponseBody部分实现代码
...
/**
* Returns the response as a string decoded with the charset of the Content-Type header. If that
* header is either absent or lacks a charset, this will attempt to decode the response body as
* UTF-8.
*/
public final String string() throws IOException {
return new String(bytes(), charset().name());
}
public final byte[] bytes() throws IOException {
long contentLength = contentLength();
if (contentLength > Integer.MAX_VALUE) {
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
}
BufferedSource source = source();
byte[] bytes;
try {
bytes = source.readByteArray();
} finally {
Util.closeQuietly(source); // 注意这里调用了close
}
if (contentLength != -1 && contentLength != bytes.length) {
throw new IOException("Content-Length and stream length disagree");
}
return bytes;
}
/**
* Returns the response as a character stream decoded with the charset of the Content-Type header.
* If that header is either absent or lacks a charset, this will attempt to decode the response
* body as UTF-8.
*/
public final Reader charStream() {
Reader r = reader;
return r != null ? r : (reader = new InputStreamReader(byteStream(), charset()));
}
public final InputStream byteStream() {
return source().inputStream();
}
...
// Util部分实现代码
...
/**
* Closes {@code closeable}, ignoring any checked exceptions. Does nothing if {@code closeable} is
* null.
*/
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
...
从ResponseBody
代码中可以看出,string()
方法和charStream()
方法最终都需要调用到source()
方法,但是如果先调用string()
方法,在bytes()
方法里面就会把source
给关闭,导致后面调用charStream()
方法时候会报closed
。
这个地方是统一处理响应结果最容易忽略、也是新手容易踩的坑。
总的来说,统一处理响应结果的流程就是重写GsonConverterFactory
类和GsonResponseBodyConverter
类,需要重写的两个方法是GsonConverterFactory
类中的responseBodyConverter()
方法和GsonResponseBodyConverter
类中的convert()
方法。需要注意的就是ResponseBody
中的数据读取。
以上是使用Retrofit
统一处理响应结果的过程和遇到的一些问题,有错误的地方欢迎指正,希望能够帮助到大家,感谢你的阅读!