基础通用版
1,通用的OkGo数据解析方法:
public abstract class JsonCallback<T> extends AbsCallback<T> {
private Type type;
private Class<T> clazz;
public JsonCallback() {
}
public JsonCallback(Type type) {
this.type = type;
}
public JsonCallback(Class<T> clazz) {
this.clazz = clazz;
}
// convertResponse方法中定义的是Gson解析换换逻辑
@Override
public T convertResponse(okhttp3.Response response) throws Throwable {
ResponseBody body = response.body();
if (body == null) return null;
T data = null;
Gson gson = new Gson();
JsonReader jsonReader = new JsonReader(body.charStream());
if (type != null) {
data = gson.fromJson(jsonReader, type);
} else if (clazz != null) {
data = gson.fromJson(jsonReader, clazz);
} else {
Type genType = getClass().getGenericSuperclass();
Type type = ((ParameterizedType) genType).getActualTypeArguments()[0];
data = gson.fromJson(jsonReader, type);
}
return data;
}
}
上面的方法已经可以满足项目的需求了,可以用来直接转换类似如下的几种格式:
1,数据类型A-最外层数据类型是JsonObject,data 数据也是JsonObject
{
"code":0,
"msg":"请求成功",
"data":{
"id":123456,
"name":"张三",
"age":18
}
}
2,数据类型B-最外层数据类型是JsonObject,data 数据是JsonArray
{
"code":0,
"msg":"请求成功",
"data":[
{
"id":123456,
"name":"张三",
"age":18
},
{
"id":123456,
"name":"张三",
"age":18
},
{
"id":123456,
"name":"张三",
"age":18
}
]
}
3,数据类型C-没有固定的 msg、code 字段格式包装,服务器任意返回对象
"data":{
"id":123456,
"name":"张三",
"age":18
}
4,数据类型D-最外层数据类型是JsonArray,内部数据是JsonObject
[
{
"id":123456,
"name":"张三",
"age":18
},
{
"id":123456,
"name":"张三",
"age":18
},
{
"id":123456,
"name":"张三",
"age":18
}
]
使用方法如下(例如接口返回格式如上面的格式1):
1,根据返回值json结构定义对应的实体类
public class UserResponse {
public int code;
public String msg;
public User data;
}
2,执行OkGo网络请求:
OkGo.<UserResponse>get("https://wanandroid.com/banner/json")
.tag(this)
.execute(new JsonCallback<UserResponse>() {
@Override
public void onSuccess(Response<UserResponse> response) {
}
});
针对不同的数据返回格式,主要区别就是定义的实体类不一样,调用方法完全一致。
高端定制版
场景: 大多数情况,服务器返回的数据格式都是有个统一的规范的,就像上面的类型格式A和B那样(A和B也算统一规范,因为基础包装一致),那么我们可以使用泛型,分离基础包装与实际数据,这样子需要定义两个javabean,一个全项目通用的LzyResponse,一个单纯的业务模块需要的数据。
这种方法只适合服务器返回有固定数据格式的情况,如果服务器的数据格式不统一,不建议使用该种方式。
比如通过和公司后台协商,统一了服务器端返回json的格式,如后台接口返回的数据格式只可能为如下两类:
{"code":"0","message":"success","data":{}}
{"code":"0","message":"success","data":[]}
我们真正需要的是data所包含的数据部分,如果继续按照通用版的方式来定义实体类则是这样的:
public class UserResponse {
public int code;
public String message;
public User data;
}
当使用其它接口的时候又得根据该接口返回的json字符串重新定义一个XXXResponse的实体类,并将data的类型改成XXX,如下:
public class Result<T> {
public int code;
public String message;
public T data;
}
很明显code,和message被重复定义了多次,显的有点重复,感觉不是很优雅,因此我们可以通过泛型将code和message字段抽取到一个全项目通用的LzyResponse的类中,这样我们只需要编写data字段所对应的实体类即可,更专注于我们的业务逻辑。如下:
public class LzyResponse<T> {
public int code;
public String message;
public T data;
}
那么对于data字段是User时则可以写为 Result< User > ,当是个列表的时候为 Result<List< User >>,其它同理。
优化的JsonCallback解析类如下:(需根据实际的项目进行修改,最下面的数据成功返回时的code,和对不同errcode的异常抛出)
public abstract class JsonCallback2<T> extends AbsCallback<T> {
private Type type;
private Class<T> clazz;
public JsonCallback2() {
}
public JsonCallback2(Type type) {
this.type = type;
}
public JsonCallback2(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public T convertResponse(Response response) throws Throwable {
if (type == null) {
if (clazz == null) {
// 如果没有通过构造函数传进来,就自动解析父类泛型的真实类型(有局限性,继承后就无法解析到)
Type genType = getClass().getGenericSuperclass();
type = ((ParameterizedType) genType).getActualTypeArguments()[0];
} else {
return parseClass(response, clazz);
}
}
if (type instanceof ParameterizedType) {
return parseParameterizedType(response, (ParameterizedType) type);
} else if (type instanceof Class) {
return parseClass(response, (Class<?>) type);
} else {
return parseType(response, type);
}
}
private T parseClass(Response response, Class<?> rawType) throws Exception {
if (rawType == null) return null;
ResponseBody body = response.body();
if (body == null) return null;
JsonReader jsonReader = new JsonReader(body.charStream());
if (rawType == String.class) {
//noinspection unchecked
return (T) body.string();
} else if (rawType == JSONObject.class) {
//noinspection unchecked
return (T) new JSONObject(body.string());
} else if (rawType == JSONArray.class) {
//noinspection unchecked
return (T) new JSONArray(body.string());
} else {
T t = Convert.fromJson(jsonReader, rawType);
response.close();
return t;
}
}
private T parseType(Response response, Type type) throws Exception {
if (type == null) return null;
ResponseBody body = response.body();
if (body == null) return null;
JsonReader jsonReader = new JsonReader(body.charStream());
// 泛型格式如下: new JsonCallback<任意JavaBean>(this)
T t = Convert.fromJson(jsonReader, type);
response.close();
return t;
}
private T parseParameterizedType(Response response, ParameterizedType type) throws Exception {
if (type == null) return null;
ResponseBody body = response.body();
if (body == null) return null;
JsonReader jsonReader = new JsonReader(body.charStream());
Type rawType = type.getRawType(); // 泛型的实际类型
Type typeArgument = type.getActualTypeArguments()[0]; // 泛型的参数
if (rawType != LzyResponse.class) {
// 泛型格式如下: new JsonCallback<外层BaseBean<内层JavaBean>>(this)
T t = Convert.fromJson(jsonReader, type);
response.close();
return t;
} else {
if (typeArgument == Void.class) {
// 泛型格式如下: new JsonCallback<LzyResponse<Void>>(this)
SimpleResponse simpleResponse = Convert.fromJson(jsonReader, SimpleResponse.class);
response.close();
//noinspection unchecked
return (T) simpleResponse.toLzyResponse();
} else {
// 泛型格式如下: new JsonCallback<LzyResponse<内层JavaBean>>(this)
LzyResponse lzyResponse = Convert.fromJson(jsonReader, type);
response.close();
int code = lzyResponse.errorCode;
// 一般来说服务器会和客户端约定一个数表示成功,如200,其余的表示失败,如400,300等,这里根据实际情况罗列并抛出
if (code == 0) {
//noinspection unchecked
return (T) lzyResponse;
} else if (code == 400) {
throw new IllegalStateException("Token已过期");
} else if (code == 300) {
throw new IllegalStateException("用户名密码错误");
} else {
// 直接将服务端的错误信息抛出,onError中可以获取
throw new IllegalStateException("错误代码:" + code + ",错误信息:" + lzyResponse.errorMsg);
}
}
}
}
}
其中需要用到的Convert类如下:(该类不用修改)
public class Convert {
private static Gson create() {
return Convert.GsonHolder.gson;
}
private static class GsonHolder {
private static Gson gson = new Gson();
}
public static <T> T fromJson(String json, Class<T> type) throws JsonIOException, JsonSyntaxException {
return create().fromJson(json, type);
}
public static <T> T fromJson(String json, Type type) {
return create().fromJson(json, type);
}
public static <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
return create().fromJson(reader, typeOfT);
}
public static <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException, JsonIOException {
return create().fromJson(json, classOfT);
}
public static <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
return create().fromJson(json, typeOfT);
}
public static String toJson(Object src) {
return create().toJson(src);
}
public static String toJson(Object src, Type typeOfSrc) {
return create().toJson(src, typeOfSrc);
}
public static String formatJson(String json) {
try {
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(json);
return JsonConvertor.getInstance().toJson(je);
} catch (Exception e) {
return json;
}
}
public static String formatJson(Object src) {
try {
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(toJson(src));
return JsonConvertor.getInstance().toJson(je);
} catch (Exception e) {
return e.getMessage();
}
}
}
需要引入的依赖:
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.readystatesoftware.chuck:library:1.0.4'
全局通用的LzyResponse类和SimpleResponse定义如下:(需修改字段名和接口返回的一致)
public class LzyResponse<T> implements Serializable {
private static final long serialVersionUID = 5213230387175987834L;
public int errorCode = -1;
public String errorMsg;
public T data;
@Override
public String toString() {
return "LzyResponse{\n" +//
"\tcode=" + errorCode + "\n" +//
"\tmsg='" + errorMsg + "\'\n" +//
"\tdata=" + data + "\n" +//
'}';
}
}
public class SimpleResponse implements Serializable {
private static final long serialVersionUID = -1477609349345966116L;
public int errorCode;
public String errorMsg;
public LzyResponse toLzyResponse() {
LzyResponse lzyResponse = new LzyResponse();
lzyResponse.errorCode = errorCode;
lzyResponse.errorMsg = errorMsg;
return lzyResponse;
}
}
其中SimpleResponse类是为了处理服务器返回data部分为空的情况,如下:
{
"code":300,
"msg":"用户名或密码错误",
}
上面优化之后的JsonCallback,只有code为200时才会回调onSuccess方法,非200的情况都会回调到onError方法中,在onError方法中我们可以通过instanceof操作符来判断类型从而来区分是OkGo自己的异常还是我们自定义Callback时用户抛出的异常。
使用方法如下:
OkGo.<LzyResponse<List<BannerBean>>>get("https://wanandroid.com/banner/json")
.tag(this)
.execute(new JsonCallback<LzyResponse<List<BannerBean>>>() {
@Override
public void onSuccess(Response<LzyResponse<List<BannerBean>>> response) {
// 回调onSuccess就是返回了200
String desc = response.body().data.get(0).getDesc();
Toast.makeText(CollectActivity.this, desc, Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Response<LzyResponse<List<BannerBean>>> response) {
Throwable exception = response.getException();
if(exception != null) exception.printStackTrace();
if(exception instanceof UnknownHostException || exception instanceof ConnectException){
ToastUtil.showShort("服务器连接失败,请检查后重试!");
}else if(exception instanceof SocketTimeoutException){
ToastUtil.showShort("网络请求超时!");
}else if(exception instanceof HttpException){
ToastUtil.showShort("服务器维护,请稍后再试(404,500)!");
}else if(exception instanceof StorageException){
ToastUtil.showShort("SD卡不存在或者没有权限!");
}
else if(exception instanceof IllegalStateException){
String message = exception.getMessage();
ToastUtil.showShort(message);
}
}
});
其实通用版和定制版的主要区别就是:网络请求成功非200的情况是回调onSuccess还是回调onError。
扩展1:OkGo+RxJava2:
添加依赖:
compile 'com.lzy.net:okrx2:2.0.2'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.readystatesoftware.chuck:library:1.0.4'
使用方式如下:
public static Observable<ResponseData<ArticleListVO>> getHomeData(int page) {
String url = AppConst.BASE_URL + "article/list/" + page + "/json";
return OkGo.<LzyResponse<ArticleBean>>get(url)
.cacheMode(CacheMode.FIRST_CACHE_THEN_REQUEST) //使用缓存
.converter(new JsonConvert<LzyResponse<ArticleBean>>() {
})
.adapt(new ObservableBody<LzyResponse<ArticleBean>>());
}
这里有两个特殊的方法:
- .converter():该方法是网络请求的转换器,功能是将网络请求的结果转换成我们需要的数据类型。
- .adapt():该方法是方法返回值的适配器,功能是将网络请求的Call对象,适配成我们需要的Observable对象。
因此我们需要自定义一个全局通用的JsonConvert类:
public class JsonConvert<T> implements Converter<T> {
// 跟上面的定制版JsonCallback只有类名和继承关系不一样,剩余部分完全一致,直接复制即可,同时修改对应位置为符合实际项目
}
adapt的参数:
Observable开头,表示返回的对象是Observable对象
Body结尾,表示返回对象的泛型是T,没有外层的Response包装
OkRx使用缓存
默认情况下,我们使用的是异步请求来适配的,使用方式和okgo使用缓存一样,指定cacheKey和cacheMode就行了,如下:
OkGo.<LzyResponse<List<BannerBean>>>get("https://wanandroid.com/banner/json")
.cacheKey("banners")
.cacheMode(CacheMode.FIRST_CACHE_THEN_REQUEST) // 使用缓存
.converter(new JsonConvert<LzyResponse<List<BannerBean>>>() {
})
.adapt(new ObservableResponse<LzyResponse<List<BannerBean>>>())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response<LzyResponse<List<BannerBean>>>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
addDisposable(d);
}
@Override
public void onNext(@NonNull Response<LzyResponse<List<BannerBean>>> lzyResponseResponse) {
boolean fromCache = lzyResponseResponse.isFromCache();
LzyResponse<List<BannerBean>> body = lzyResponseResponse.body();
List<BannerBean> data = body.getData();
String desc = data.get(0).getDesc();
if (fromCache){
ToastUtil.showShort("来自缓存:"+desc);
}else{
ToastUtil.showShort("不是来自缓存:"+desc);
}
}
@Override
public void onError(@NonNull Throwable exception) {
if(exception != null) exception.printStackTrace();
if(exception instanceof UnknownHostException || exception instanceof ConnectException){
ToastUtil.showShort("服务器连接失败,请检查后重试!");
}else if(exception instanceof SocketTimeoutException){
ToastUtil.showShort("网络请求超时!");
}else if(exception instanceof HttpException){
ToastUtil.showShort("服务器维护,请稍后再试(404,500)!");
}else if(exception instanceof StorageException){
ToastUtil.showShort("SD卡不存在或者没有权限!");
}
else if(exception instanceof IllegalStateException){
String message = exception.getMessage();
ToastUtil.showShort(message);
}
}
@Override
public void onComplete() {
}
});
那么对于okrx缓存回调成功和网络请求回调成功都是回调onNext()方法,那么怎么区分呢,回调的Response对象中有个isFromCache方法,就是表示当前回调是来自缓存还是网络。
注意事项:
- 不同的缓存模式会导致onNext()方法具有不同的回调次数,可能一次或者两次
- 如果使用同步方法来适配的话,缓存模式CacheMode.FIRST_CACHE_THEN_REQUEST是不生效的,异步才生效,原因是同步请求没法返回两次数据。
取消请求
我们可以在基类(BaseActivity或者BasePresenter中)创建这么两个方法,把每一个请求的Disposable对象都交给由统一的CompositeDisposable对象去管理。
private CompositeDisposable compositeDisposable;
public void addDisposable(Disposable disposable){
if(compositeDisposable == null){
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(disposable);
}
public void dispose(){
if(compositeDisposable != null){
compositeDisposable.dispose();
}
}
然后在Observer对象的onSubscibe方法中,把Disposable添加到CompositeDisposable中:
OkGo.<LzyResponse<List<BannerBean>>>get("https://wanandroid.com/banner/json")
.converter(new JsonConvert<LzyResponse<List<BannerBean>>>() {
})
.adapt(new ObservableResponse<LzyResponse<List<BannerBean>>>())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response<LzyResponse<List<BannerBean>>>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
addDisposable(d);
}
@Override
public void onNext(@NonNull Response<LzyResponse<List<BannerBean>>> lzyResponseResponse) {
boolean fromCache = lzyResponseResponse.isFromCache();
LzyResponse<List<BannerBean>> body = lzyResponseResponse.body();
List<BannerBean> data = body.getData();
String desc = data.get(0).getDesc();
if (fromCache){
ToastUtil.showShort("来自缓存:"+desc);
}else{
ToastUtil.showShort("不是来自缓存:"+desc);
}
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
最后我们在界面销毁或者需要取消的地方调用取消请求的方法:
@Override
protected void onDestroy() {
super.onDestroy();
dispose();
}
扩展2:OkGo+RxJava2+MVP
项目结构如下:
源码地址:https://github.com/gpf0205/MVPOkgoDemo