我们服务器后台返回数据的统一格式:
{
"code": 200,
"data": {
"title": "balbalbla",
"img": "http://jexxxxx.png",
"text": "balabalbalba"
},
"message": "SUCCESS"
}
最低级的方式,用GsonFormat来直接对这个返回体进行解析成一个Bean类,但这样的话,每个Bean类都会有code、message成员,而data又是一个不确定的数据结构。
于是我们把code、message抽出来,data通过泛型,将他们封装成一个专门用于接收自家后台传来的数据RspModel
。此后,我们只要解析、编写data
体内容的Bean类,传入RspModel
中,如返回的data实际是一个User类,那么我们就用RspModel
来接收。如此一来,整个返回体也可以统一、规范管理起来,特别是对code的解析,特有帮助。
public class RspModel {
public final static int CODE_SUCCESS = 200;//成功
public final static int CODE_FAIL = 400;//失败
public final static int CODE_UNAUTHORIZED = 401;//未认证(签名错误)
public final static int CODE_NOT_FOUND = 404;//接口不存在
public final static int CODE_INTERNAL_SERVER_ERROR = 500;//服务器内部错误
private int code;
private String message;
private T data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
对于Retrofit的基本使用和基础封装,这里就不讲述了,不是重点。
每位使用Retrofit的猿们,应该都会有个RemoteService
来通过@GET、@POST等注解来编写接口方法。
通过上面的封装后,这里我们就酱子来编写接口了。Call的泛型不再是各个独立的Bean类,而是由RspModel统一管理统一格式的新Bean类。
public interface RemoteService {
@GET("getUserById")
Call> getUserByName(@query(userId) String userId);
}
接着就是调用这个接口了:
...
RemoteService service = Network.remote(); //不展开讲述此处的封装
service.getUserByName(id).enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
RspModel> rspModel = response.body();
if (rspModel != null) {
if (rspModel.getCode() == RspModel.CODE_SUCCESS) {
//成功读取
//正式处理Data数据
User user = rspModel.getData();
...
} else {
Factory.decodeRspCode(rspModel, new DataSource.FailedCallback() {
@Override
public void onDataNotAvailable(String strRes) {
//处理错误码结果
}
});
}
}
@Override
public void onFailure(Call> call, Throwable t) {
//处理异常信息
ViseLog.e(t);
}
});
}
细心的你会发现,enqueue( )方法里面的Callback有两个回调方法,我们在里面编写的代码逻辑中,有部分是固定的操作,就是
1. 判断response.body
2. 判断RspModel的Code
3. 解析出Data并进行后续处理(最终目的操作,使用数据,使用方式是不同的)
3. 处理错误(错误码、异常信息)
所有的网络回调都需要做上面的操作,最终拿data去做后续的分发使用。
那么,对于这个网络请求方法的使用者,他关心的是什么呢,就是有数据,给我数据;没数据,告诉我到底发生什么错误了
。对RspModel的解析应该是不知情,也不需要知道。RspModel也是有可能发生改变的,可能在code、Message、data字段基础上,多了个字段,或者上述字段改了名字。如果像上面那样,每个enqueue( )方法里面的Callback里面都独立做一遍解析,会造成两个问题1. 重复代码多 2.一旦RspModel发生变化,解析过程要全部修改
。一个项目,几十个上百个接口都有可能,我怎么可能去修改几十次上百次呢。
所以,要封装对RspModel的解析逻辑,暴露出最终得到数据或错误信息的接口。
public abstract class MyRetrofitCallback implements Callback> {
@Override
public void onResponse(Call> call, Response> response) {
RspModel rspModel = response.body();
if (rspModel != null) {
if (rspModel.getCode() == RspModel.CODE_SUCCESS) {
//返回成功结果
Log.d(rspModel.getMessage());
success(rspModel.getData());
} else { //返回的结果码为错误码
//解析错误码
Factory.decodeRspCode(rspModel, new DataSource.FailedCallback() {
@Override
public void onDataNotAvailable(String strRes) {
//对非success返回码
Log.d(rspModel.getMessage());
failed(strRes);
}
});
}
} else {
//rspModel == null
failed("rspModel == null");
}
}
@Override
public void onFailure(Call> call, Throwable t) {
failed(t.getMessage());
}
protected abstract void success(T data);
protected abstract void failed(String errMsg);
}
以后,就直接用我们封装好的callback,不再使用Retrofit原生的Callback
service.getUserByName(id).enqueue(new MyRetrofitCallback() {
@Override
protected void success(User data) {
if (data != null && !data.isEmpty()) {
//读取到有数据
callback.onDataLoaded(data); //自己封装的,用于数据读取层(网络、数据库)到使用层传递数据的统一接口
} else {
//数据库无数据
callback.onDataNotAvailable("无新远程指导订单数据");
}
}
@Override
protected void failed(String msg) {
callback.onDataNotAvailable(msg);
}
});
看,这样,代码就很清爽,而且AndroidStudio的自动代码补全功能,会根据RemoteService
Call< T >里面写的泛型类名,自动为我们的MyRetrofitCallback< T >补上名字,开发起来就很快了。
回调中做少量特别的处理逻辑,就可以把数据直接扔给使用层了,再也不怕写错写漏返回体的解析逻辑了。
接口、抽象类、泛型、重构、封装、拓展性
用个抽象类实现一个接口,并实现所需方法,把通用逻辑编写到接口方法中,最终暴露新的方法接口给调用者。
就类似加了个中间人:
原本:A搬砖,B涂水泥并砌砖
如今:A搬砖,AB涂水泥,B砌砖
不,应该是:
原本:A只会搬砖,B涂水泥并砌砖
如今:A会搬砖并涂水泥,B尽情发挥砌砖的天赋才华!
因为,搬砖和涂水泥的工作时固定的,而且还不能出错,每一面都要均匀涂抹。而砌砖呢,就是怎样好看怎样砌,这是很创造性的,可能每一块砖的砌法都不同。
起步慢没关系 走了弯路没关系 因为你一直没退场 这场马拉松 不退场的你 会越来越靠近终点