一般来说从服务端获取的数据格式是固定的:
{
"code":0,
"message":"成功",
"data":{"测试内容"}
}
或者
{
"code":0,
"message":"成功",
"data":[{},{},{}]
}
一般我们也会事先创建一个类来实例化
public class HttpData {
private static final int SUCCESS_CODE = 0;
private int code;
private String message;
private T data;
public boolean isSuccess() {
return code == SUCCESS_CODE;
}
public int getCode() {
return code;
}
public void setCode(int mCode) {
code = mCode;
}
public String getMessage() {
return message;
}
public void setMessage(String mMessage) {
message = mMessage;
}
public T getData() {
return data;
}
public void setData(T mData) {
data = mData;
}
}
而习惯于使用Retrofit2 + Rxjava2的开发者知道Retrofit2可以通过addConverterFactory(GsonConverterFactory.create()) 来将后台返回的json数据自动转化为实体类
mRetrofit = new Retrofit.Builder()
.client(mOkHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(CashierApi.TEST_URL)
.build();
这很方便,但是也有问题,就比如上面自定义的HttpData类,其中的data即可能是JSONObject,也可能是JSONArray,一般后台是沟通好的,返回什么格式都会事先定好,不会弄错。但是还是会出现一些意外的情况,当你使用JSONObject的时候返回JSONArray,或者相反的情况,这个时候就会发生错误。
同样的,每一次网络请求获取数据之后都要通过code值来判断是否请求成功,这样过于繁琐,那么是否能封装一下呢?
addDisposable(mDataManager.getHotGoodsList(shopId, presale, categoryId)
.compose(RxUtils.rxSchedulerHelper())
.compose(RxUtils.handleHttpData())
.subscribe(new Consumer>() {
@Override
public void accept(List mHotGoodsWraps) throws Exception {
}
}, new Consumer() {
@Override
public void accept(Throwable mThrowable) throws Exception {
}
}));
打开GsonConverterFactory
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit2.converter.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;
/**
* A {@linkplain Converter.Factory converter} which uses Gson for JSON.
*
* Because Gson is so flexible in the types it supports, this converter assumes that it can handle
* all types. If you are mixing JSON serialization with something else (such as protocol buffers),
* you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance}
* last to allow the other converters a chance to see their types.
*/
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 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);
}
}
可以看到responseBodyConverter和requestBodyConverter两个方法,这里我们主要看responseBodyConverter。打开GsonResponseBodyConverter类:
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit2.converter.gson;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.IOException;
import okhttp3.ResponseBody;
import retrofit2.Converter;
final class GsonResponseBodyConverter implements Converter {
private final Gson gson;
private final TypeAdapter adapter;
GsonResponseBodyConverter(Gson gson, TypeAdapter 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();
}
}
}
GsonResponseBodyConverter类很简单,在convert方法中的操作就是对后台数据的json解析,所以我们在这里面改就可以了,先上修改后的代码
@Override
public T convert(ResponseBody value) throws IOException {
try {
String response = value.string();
Log.i("CustomGsonResponse", "convert: " + response);
JSONObject mJSONObject = null;
try {
mJSONObject = new JSONObject(response);
} catch (JSONException mE) {
mE.printStackTrace();
}
int code = mJSONObject.optInt("code", -1);
if (code == Constants.NETWORD_SUCCESS_CODE) {
MediaType mediaType = value.contentType();
Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
InputStream inputStream = new ByteArrayInputStream(response.getBytes());
JsonReader jsonReader = gson.newJsonReader(new InputStreamReader(inputStream, charset));
T result = adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} else {
String message = mJSONObject.optString("message");
value.close();
throw new ApiException(code, message);
}
} finally {
value.close();
}
}
简单解释一下,由于ResponseBody的数据只能被读取一次,所以在读取之后就要把它存储起来:
MediaType mediaType = value.contentType();
Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
InputStream inputStream = new ByteArrayInputStream(response.getBytes());
JsonReader jsonReader = gson.newJsonReader(new InputStreamReader(inputStream, charset));
这就是这段代码的作用。
然后判断code的值是否是访问成功了
if (code == Constants.NETWORD_SUCCESS_CODE) {
} else {
String message = mJSONObject.optString("message");
value.close();
throw new ApiException(code, message);
}
如果是的话就直接返回解析后的数据,如果不是的话就返回异常。
ApiException这个异常是我们自定义的:
public class ApiException extends RuntimeException {
private int code;
public ApiException(String message) {
super(message);
}
public ApiException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
public void setCode(int mCode) {
code = mCode;
}
}
我们在这里抛出异常,这个异常会在后面获得网络请求结果的onError中获得,然后我们再创建一个CommonSubscriber类:
public abstract class CommonSubscriber extends ResourceSubscriber {
private BaseView mBaseView;
private boolean isHideLoading = false;
public CommonSubscriber() {
}
public CommonSubscriber(BaseView mBaseView) {
this.mBaseView = mBaseView;
}
public CommonSubscriber(BaseView mBaseView, boolean isHideLoading) {
this.mBaseView = mBaseView;
this.isHideLoading = isHideLoading;
}
@Override
public void onComplete() {
if (mBaseView != null && isHideLoading) {
mBaseView.hideLoadingView();
}
}
@Override
public void onError(Throwable mThrowable) {
if (mBaseView == null) {
return;
}
if (isHideLoading) {
mBaseView.hideLoadingView();
}
if (mThrowable instanceof ApiException) {
if (!StringUtils.isTextEmpty(mThrowable.getMessage())) {
mBaseView.showErrorMsg(mThrowable.getMessage());
}
} else if (mThrowable instanceof HttpException) {
mBaseView.showErrorMsg("数据加载失败");
} else if (mThrowable instanceof SocketTimeoutException) {
mBaseView.showErrorMsg("网络访问超时");
} else {
mThrowable.printStackTrace();
}
}
}
可以看到在onError方法中,我们判断异常的类型,然后通过不同的类型进行相关的处理,这样就能省下很大一块功夫,在使用的时候直接处理数据就可以了:
public void getHotGoodsList(int shopId, int presale, int categoryId) {
addDisposable(mDataManager.getHotGoodsList(shopId, presale, categoryId)
.compose(RxUtils.rxSchedulerHelper())
.compose(RxUtils.handleHttpData())
.subscribeWith(new CommonSubscriber>(mView) {
@Override
public void onNext(List mHotGoodsWraps) {
mView.onSuccessGetHotGoodsList(mHotGoodsWraps);
}
}));
}
当然,实际开发中有各种不同的情况,我们可以进行相关的处理来修改代码。最后附上要新建的几个Gson解析类:
CustomGsonConverterFactory:
package com.hongyue.cashregister.ui.widget.gsonconverter;
import android.util.Log;
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;
/**
* Created by xkai on 2018/7/26.
*/
public class CustomGsonConverterFactory 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 CustomGsonConverterFactory 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 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 responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter> adapter = gson.getAdapter(TypeToken.get(type));
Log.i("main", "responseBodyConverter: " + type.toString());
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 CustomGsonRequestBodyConverter<>(gson, adapter);
}
}
CustomGsonRequestBodyConverter:
package com.hongyue.cashregister.ui.widget.gsonconverter;
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;
/**
* Created by xkai on 2018/7/26.
*/
public class CustomGsonRequestBodyConverter implements Converter {
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 adapter;
CustomGsonRequestBodyConverter(Gson gson, TypeAdapter 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());
}
}
CustomGsonResponseBodyConverter:
package com.hongyue.cashregister.ui.widget.gsonconverter;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.hongyue.cashregister.constant.Constants;
import com.hongyue.cashregister.model.bean.HttpData;
import com.hongyue.cashregister.model.http.exception.ApiException;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import static okhttp3.internal.Util.UTF_8;
/**
* Created by xkai on 2018/7/26.
*/
public class CustomGsonResponseBodyConverter implements Converter {
private final Gson gson;
private final TypeAdapter adapter;
CustomGsonResponseBodyConverter(Gson gson, TypeAdapter adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
try {
String response = value.string();
Log.i("CustomGsonResponse", "convert: " + response);
JSONObject mJSONObject = null;
try {
mJSONObject = new JSONObject(response);
} catch (JSONException mE) {
mE.printStackTrace();
}
int code = mJSONObject.optInt("code", -1);
if (code == Constants.NETWORD_SUCCESS_CODE) {
MediaType mediaType = value.contentType();
Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
InputStream inputStream = new ByteArrayInputStream(response.getBytes());
JsonReader jsonReader = gson.newJsonReader(new InputStreamReader(inputStream, charset));
T result = adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} else {
String message = mJSONObject.optString("message");
value.close();
throw new ApiException(code, message);
}
} finally {
value.close();
}
}
}
使用的时候用CustomGsonConverterFactory替换GsonConverterFactory就可以了
mRetrofit = new Retrofit.Builder()
.client(mOkHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(CustomGsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(CashierApi.TEST_URL)
.build();