前言
对于大多数开发者来说,我们在做网络请求的时候,并不想重复的去处理状态码,错误信息等,我只关心我需要的结果,本文将会讲解通用状态码的封装、错误信息的统一处理、多种JSON数据格式的适配。
正文
预期目标
- 统一
success
和error
状态的处理 - View 成只关心
success
的data
- 各种奇葩 Json 结构的统一处理
- 统一 Exception 处理
分析
其实整个实现思路 Retrofit 已经给过我们了,还记得我们在创建 Retrofit 的时候调用的 addConverterFactory
方法吗?而我们设置的就是官方默认提供的 GsonConverterFactory
,其实 Retrofit 就是使用的它作为序列化和反序列化。
我们看看 GsonConverterFactory
里面的源码结构:
一共有三个类:GsonConverterFactory、GsonRequestBodyConverter、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();
}
}
}
从源码中可以看出,GsonResponseBodyConverter
对于返回结果的处理仅仅是将 json转换为相应的实体类
。
接下来我们看看 Retrofit 的 addConverterFactory
方法。
/** Add converter factory for serialization and deserialization of objects. */
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
可以看到该方法接收的是抽象类Converter.Factory
的参数,这说明我们可以直接写一个Converter
,然后继承实现Converter.Factory
的方法,就能把整个序列化的过程把握在我们手里了。
编码
先看看下面这个数据结构吧:
{
"code":200,
"data":{},
"message":"请求成功"
}
上面是常见的API返回的数据结构,还算是比较正规的做法,其中code是状态码,data是数据,message是附带的消息,关于code先定义以下几种:
状态码 | 含义 |
---|---|
200 | 请求成功 |
401 | token失效 |
-1 | 请求失败,message中包含错误提示 |
… | … |
所以,在网络请求中,只需要关注 200 和非 200 的两种成功状态,接下来我们对 code
进行统一的封装和预处理,需要达到的要求是:code 为 200 的时候正常解析数据,不为 200 的时候抛出 异常
,包含 code
和message
,让 View层
去处理。
创建 GsonConvert
第一步不直接进行修改,而是建一个和GsonConverterFactory
同样的包,然后 copy 过来,然后改一下名字。
创建泛型实体类
上面的数据结构是一般比较简单而常见的数据结构,我们将其封装为一个泛型实体类:
public class BaseDataModel<T> {
private int code;
private T data;
private String msg;
// 忽略了 get/set方法
public boolean isSuccessful() {
return code == 200;
}
}
使用也比较简单,只需要将泛型替换为相应的实体类就行。但是这也有一个不好的地方就是一旦项目中对接的数据结构变得繁杂的时候,就需要不断的顶部相应的实体了,还是比较麻烦,后面会讲到,所以我们还是使用自定义ResponseBodyConverter实现统一处理。
创建Exception类
我们需要定义一个我们自己的 Exception类,来对一些和后端约定的状态码进行转义和包装,方便我们处理。
由于代码量比较多,为了不影响排版,我将其折叠起来了
public class NetErrorException extends IOException { private Throwable exception; private int mErrorType = NO_CONNECT_ERROR; private String mErrorMessage; /*无连接异常*/ public static final int NoConnectError = 1; /** * 数据解析异常 */ public static final int PARSE_ERROR = 0; /** * 无连接异常 */ public static final int NO_CONNECT_ERROR = 1; /*网络连接超时*/ public static final int SocketTimeoutError = 6; /** * 无法连接到服务 */ public static final int ConnectExceptionError = 7; /** * 服务器错误 */ public static final int HttpException = 8; /** * 登陆失效 */ public static final int LOGIN_OUT = 401; /** * 其他 */ public static final int OTHER = -99; /** * 没有网络 */ public static final int UNOKE = -1; /** * 无法找到 */ public static final int NOT_FOUND = 404; /*其他*/
public NetErrorException(Throwable exception, int mErrorType) { this.exception = exception; this.mErrorType = mErrorType; } public NetErrorException(String message, Throwable cause) { super(message, cause); } public NetErrorException(String message, int mErrorType) { super(message); this.mErrorType = mErrorType; this.mErrorMessage = message; } @Override public String getMessage() { if (!TextUtils.isEmpty(mErrorMessage)) { return mErrorMessage; } switch (mErrorType) { case PARSE_ERROR: return "数据解析异常"; case NO_CONNECT_ERROR: return "无连接异常"; case OTHER: return mErrorMessage; case UNOKE: return "当前无网络连接"; case ConnectExceptionError: return "无法连接到服务器,请检查网络连接后再试!"; case HttpException: try { if (exception.getMessage().equals("HTTP 500 Internal Server Error")) { return "服务器发生错误!"; } } catch (Exception e) { e.printStackTrace(); } if (exception.getMessage().contains("Not Found")) return "无法连接到服务器,请检查网络连接后再试!"; return "服务器发生错误"; } try { return exception.getMessage(); } catch (Exception e) { return "未知错误"; } } /** * 获取错误类型 */ public int getErrorType() { return mErrorType; }
}
统一订阅处理
由于这里使用的是RxJava
,需要自定义一个Subscriber
来对Convert
抛出的Exception
进行捕获,也需要对其它Exception
进行捕获和包裹,防止发生错误后直接崩溃,代码不多,如下:
public abstract class ApiSubscriber<T> extends ResourceSubscriber<T> {
@Override
public void onError(Throwable e) {
NetErrorException error = null;
if (e != null) {
// 对不是自定义抛出的错误进行解析
if (!(e instanceof NetErrorException)) {
if (e instanceof UnknownHostException) {
error = new NetErrorException(e, NetErrorException.NoConnectError);
} else if (e instanceof JSONException || e instanceof JsonParseException) {
error = new NetErrorException(e, NetErrorException.PARSE_ERROR);
} else if (e instanceof SocketTimeoutException) {
error = new NetErrorException(e, NetErrorException.SocketTimeoutError);
} else if (e instanceof ConnectException) {
error = new NetErrorException(e, NetErrorException.ConnectExceptionError);
} else {
error = new NetErrorException(e, NetErrorException.OTHER);
}
} else {
error = new NetErrorException(e.getMessage(), NetErrorException.OTHER);
}
}
// 回调抽象方法
onFail(error);
}
/**
* 回调错误
*/
protected abstract void onFail(NetErrorException error);
}
修改 HandlerGsonResponseBodyConverter
这一步其实没什么难度,只是在convert
方法中提前将ResponseBody.string()取出来,通过最简单的JSONObject
来判断code
,为200则返回data
,否则抛出自定义错误。详细代码如下:
final class HandlerErrorGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
/**模拟的假数据*/
private final List<String> mockResult;
private final Random random;
HandlerErrorGsonResponseBodyConverter(TypeAdapter<T> adapter) {
this.random = new Random();
this.adapter = adapter;
mockResult = new ArrayList<>();
mockResult.add("{\"code\":200,\"message\":\"成功,但是没有数据\",\"data\":[]}");
mockResult.add("{\"code\":-1,\"message\":\"这里是接口返回的:错误的信息,抛出错误信息提示!\",\"data\":[]}");
mockResult.add("{\"code\":401,\"message\":\"这里是接口返回的:权限不足,请重新登录!\",\"data\":[]}");
}
@Override
public T convert(ResponseBody value) throws IOException {
// 这里就是对返回结果进行处理
String jsonString = value.string();
try {
// 这里为了模拟不同的网络请求,所以采用了本地字符串的格式然后进行随机选择判断结果。
int resultIndex = random.nextInt(mockResult.size() + 1);
if (resultIndex == mockResult.size()) {
return adapter.fromJson(jsonString);
} else {
// 这里模拟不同的数据结构
jsonString = mockResult.get(resultIndex);
Log.e("TAG", "这里进行了返回结果的判断");
// ------------------ JsonObject 只做了初略的判断,具体情况自定
JSONObject object = new JSONObject(jsonString);
int code = object.getInt("code");
if (code != 200) {
throw new NetErrorException(object.getString("message"), code);
}
return adapter.fromJson(object.getString("data"));
}
} catch (JSONException e) {
e.printStackTrace();
throw new NetErrorException("数据解析异常", NetErrorException.PARSE_ERROR);
} finally {
value.close();
}
}
}
如何调用
主要是有以下两个地方:
- 创建Retrofit的时候将
addConverterFactory
换成自定义的HandlerGsonConverterFactory.create()
- RxJava的subscribeWith换成自定义的ApiSubscriber。
部分代码如下:
//......
retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(HandlerGsonConverterFactory.create()) // 这里使用的是用自己自定义的转换器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
service = retrofit.create(GitHubService.class);
//......
mResultTv.setText("创建请求................\n");
disposable = service.listRxJava2FlowableRepos("aohanyao", "owner")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new ApiSubscriber<List<Repo>>() {
@Override
public void onNext(List<Repo> repos) {
mResultTv.append("请求成功,repoCount:" + repos.size() + ":\n");
for (Repo repo : repos) {
mResultTv.append("repoName:" + repo.getName() + " star:" + repo.getStargazers_count() + "\n");
}
}
@Override
protected void onFail(NetErrorException error) {
mResultTv.append("请求失败" + error.getMessage() + "................\n");
}
@Override
public void onComplete() {
mResultTv.append("请求成功................\n");
}
});
演示
关联 MVP
在前面,使用了统一的 ApiSubscriber
做订阅处理,也直接在 onFail
中抛出了异常和信息,接下来就是简化和封装抛出的异常信息,将其和 Presenter
和 BaseView
绑定在一起。
修改BaseView
之前定义的 BaseView 如下,onFailure
中只抛出了message
,更改为抛出我们定义的NetErrorException
。
更改前:
/**
* 基类View,对统一的接口进行定义
*/
public interface BaseView {
/**
* 回调失败信息
*
* @param message 失败消息
*/
void onFailure(String message);
/**
* 完成网络请求,可以在这个方法中关闭弹出等操作
*/
void onComplete();
}
更改后:
/**
* 基类View,对统一的接口进行定义
*/
public interface BaseView {
/**
* 回调失败信息
*
* @param exception 异常内容
*/
void onFailure(NetErrorException exception);
/**
* 完成网络请求,可以在这个方法中关闭弹出等操作
*/
void onComplete();
}
另外再实现BaseView
的MVPBaseFragment
和MVPBaseActivity
中更改相应的onFailure
方法,现在就先直接弹出错误信息,稍后再进行深化。
@Override
public void onFailure(NetErrorException exception) {
showWarningDialog(exception.getMessage());
}
创建ApiGithub
ApiGithub 主要是将对 GitHubService
的初始化及提供单例访问的统一入口,减少样板代码,将上面写在Activity中的代码移动到ApiGithub中。
public class ApiGithub {
private static ApiGithub mApiGithub = null;
private GitHubService mGitHubService = null;
private ApiGithub() {
}
public static ApiGithub getInstance() {
synchronized (ApiGithub.class) {
if (mApiGithub == null) {
mApiGithub = new ApiGithub();
}
}
return mApiGithub;
}
/**
* 获取GitHub服务
* @return
*/
public GitHubService gitHubService() {
synchronized (ApiGithub.class) {
if (mGitHubService == null) {
mGitHubService = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(HandlerGsonConverterFactory.create()) // 这里使用的是用自己自定义的转换器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(GitHubService.class);
}
}
return mGitHubService;
}
}
创建Contract
创建Contract,定义view接口和Presenter接口。
public interface HandlerResponseErrorContract {
interface View extends BaseView {
/**
* 获取仓库成功
*
* @param repos
*/
void getReportSuccess(List<Repo> repos);
}
abstract class Presenter extends BasePresenter {
/**
* 持有View层
*/
protected View view;
public Presenter(View view) {
super(view);
this.view = view;
}
/**
* 获取仓库
*/
public abstract void getReports();
}
}
创建Presenter
前面已经讲过MVP相关的点,这里就不详细讲解了,没看过的可以点击这里去看看。这里的 Presenter 也是相对简洁明了的。
public class HandlerResponseErrorPresenter extends HandlerResponseErrorContract.Presenter {
public HandlerResponseErrorPresenter(HandlerResponseErrorContract.View view) {
super(view);
}
@Override
public void getReports() {
mDisposable = ApiGithub.getInstance()
.gitHubService()
.listRxJava2FlowableRepos("aohanyao", "owner")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new ApiSubscriber<List<Repo>>(view) {
@Override
public void onNext(List<Repo> repos) {
view.getReportSuccess(repos);
}
});
}
}
修改Activity
主要是修改集成为我们的MVPBaseActivity
,以及实现HandlerResponseErrorContract.View
相关的代码,移除原本的初始化Retrofit和请求的代码,Activity一下子就变得简洁很多了。
public class HandlerResponseErrorActivity extends MVPBaseActivity<HandlerResponseErrorPresenter> implements HandlerResponseErrorContract.View {
protected TextView mResultTv;
protected TextView mDescTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample_get_request);
mResultTv = findViewById(R.id.mResultTv);
mDescTv = findViewById(R.id.mDescTv);
initEvent();
}
private void initEvent() {
findViewById(R.id.mSendRequestBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mResultTv.setText("创建请求................\n");
// 发起请求
getP().getReports();
}
});
}
@Override
public void onFailure(NetErrorException exception) {
mResultTv.append("请求失败:" + exception.getMessage()+"\n");
super.onFailure(exception);
}
@Override
protected HandlerResponseErrorPresenter createPresenter() {
return new HandlerResponseErrorPresenter(this);
}
@Override
public void onComplete() {
mResultTv.append("请求结束");
}
@Override
public void getReportSuccess(List<Repo> repos) {
mResultTv.append("请求成功,repoCount:" + repos.size() + ":\n");
for (Repo repo : repos) {
mResultTv.append("repoName:" + repo.getName() + " star:" + repo.getStargazers_count() + "\n");
}
}
}
演示
其实演示结果和前面是一样的,外表没有什么变化,唯一的区别在于发生错误的时候会弹出一个对话框,这里是我们做的统一预处理。
总结
本篇主要是对请求结果进行了预处理,在此之上进行了对多数据结构的适配还有统一错误的处理,都是搬砖来进行的堆砌,接下来进行要点总结:
- 参照 Retrofit 提供的GsonConvert,自定义
Converter
,自行控制序列化过程。 - 在
convert
方法中对ResponseBody
进行处理- 本质上
ResponseBody
中返回的数据就是json字符(如果使用的是这种协议) - 通过JSONObject对获取关键数据进行判断(如:code/message)
- 成功则返回该有的数据结构,失败则抛出自定义Exception交由上层处理
- 本质上
- 定义
ApiSubscriber
用于订阅处理,在里面进行Exception的初步处理(判断异常种类等) - 通过BaseView将处理过的Exception抛出View层中,在View中做基础处理,弹窗或者Toast
源码
如果可以的话,请给我一个star 仓库地址
来都来了,就给个关注吧,时不时会悄悄的推送一些小技巧的文章~~
后记
本来按照原本的计划是要写Token的静默刷新
的,但由于规划的问题,这个文章已经写过了,需要的请看RxJava2 + Retrofit2 完全指南 之 Authenticator处理与Token静默刷新