Android结合业务请求JavaBean、List<JavaBean>等复杂对象
2017-04-13 14:52
387 查看
版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com
本文已授权 严振杰的公众首发,微信搜索严振杰即可关注。
本文篇幅较长,大概需要10分钟的时间才能阅读完,请做好心理准备。
今天群里小伙伴说我好久没更新文章了,赶紧翻了下自己的博客主页,发现真的成一个月一篇的更新了,加上群里小伙伴问
视频地址:https://pan.baidu.com/s/1bpHp3I3,对应视频序号为10。
代码地址:https://github.com/yanzhenjie/LiveSourceCode,对应文件夹为HttpRestModule。
首先说一下为什么会有这种问题,Http从服务器返回数据的时候主要有两部分:
我们今天说的就是把
目前开源社区已经有很多针对
用户信息的
项目属性
上面的结构大概是一个用户的
需要说明的是,解析
本文是使用的为
解析成
解析成
解析成
解析成
http://blog.csdn.net/xuyonghong1122/article/details/53350968
在这里举两个常见的数据层次结构的栗子:
业务状态码在HttpBody中返回
业务状态码用Http响应码返回
什么是业务状态码呢,比如说我们提交了订单到服务器,服务器产生订单信息后把订单详情数据返回给我们,我们需要判断服务器是否成功执行了这个操作,就要依赖业务状态码了,如果业务状态码错误,我们就要根据
错误时的数据格式:
正确时的数据格式:
看了数据层次结构可能就理解的更加清晰了,上面列出了两种情况,一种是业务错误时,一种是业务正确时,不同的项目可能还有其它不同的业务状态码。
在没有进行任何封装的情况下我们是这样做的:
请求个数据还真麻烦,其中还且不论数据层次结构的正确性,否则我们要用
错误时的数据格式:
正确时的数据:
这种结构层次的数据比上面那种简单多了,但是解析起来还是比较麻烦,在没有进行任何封装的情况下我们是这样做的:
这里要特别注意,我在上面用的200和401都是举栗子,读者的服务器定义的可能不是这样子。同时这里也会存在数据层次结构的问题,所以在解析的时候也需要捕捉异常。
综上所述,如果我们可以直接请求到
不过我这里要说2点:
解析数据最好放在子线程,因为
数据签名校验也最好放在子线程,因为设计到数据计算,也就是算法,所以也是耗时的。
我这里使的是NoHttp:https://github.com/yanzhenjie/NoHttp,它的底层使用的是
对于签名的话可以参考清风徐来的这篇文章:
http://blog.csdn.net/xuyonghong1122/article/details/53350968
到这里基本就会用
为什么不同类型的结果都是通过
看到是
那么同样的道理,我们继承
看到这里我需要告诉人儿们,
是不是稍微好一点了,不过有的同学又看不懂了,可能会问你咋又用了泛型,因为要用于所有的
到这里NoHttp自定义请求相信读者也会了,下面我们把结合业务的请求结果返回分析一下。
重点干货:这样就仅仅用一个判断就搞定了,我们所有的自定义请求全部返回
怎样?是不是简介清晰了很多呢?又可以避免很多异常的捕获,因此我们下面就要把
再回顾一下上面我们提到的两种数据层次结构,分别是:
业务状态码在HttpBody中返回
业务状态码用Http响应码返回
先来看第一种。
错误时的数据格式:
正确时的数据格式:
结合上面定义的
一个最简单的
看到这里也就很清楚了,我们最多把业务和Http层的判断再加进去就完美了吧,下面是我的业务的封装,读者一定要结合注释看:
我们写了一个基类,用来封装我们的业务层的解析,下面我们具体实现
有人就有疑问了,咋还不是你上面说的那种最简单的形式呢?问的好,因为我们的默认的
我们注意到其中的
因此先定义一个
方法都是什么意思不用我解释了吧,然后我们写一个
现在我们已经把复杂的回调简单化了,然后我们把最开始的那个
哈哈是不是超级简单了,简直完美呀!!!
错误时的数据格式:
正确时的数据:
根据上面的分析,这里只是
其它代码都是相同的,到此为止两种数据层次结构的封装就都完成了,选择一种适合你们数据层地结构的格式使用即可。
另外几点说明:
使用
然后是网络框架的选择,我推荐
本文的源代码在这里下载:https://github.com/yanzhenjie/LiveSourceCode
版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com
本文已授权 严振杰的公众首发,微信搜索严振杰即可关注。
本文篇幅较长,大概需要10分钟的时间才能阅读完,请做好心理准备。
今天群里小伙伴说我好久没更新文章了,赶紧翻了下自己的博客主页,发现真的成一个月一篇的更新了,加上群里小伙伴问
Http怎么直接请求
JavaBean、
List<JavaBean>或者
Map<>这种复杂对象,对于这个问题的解决方案,其实在2016年我就直播过了,并把视频和代码都共享出来了,如果你想直接看代码和视频:
视频地址:https://pan.baidu.com/s/1bpHp3I3,对应视频序号为10。
代码地址:https://github.com/yanzhenjie/LiveSourceCode,对应文件夹为HttpRestModule。
首先说一下为什么会有这种问题,Http从服务器返回数据的时候主要有两部分:
Headers和
Body。
Headers的众多字段一般由Http框架底层为我们解析好,比如
URLConnection、
HttpClient或者
OkHttp;
Body我们从底层Http框架拿到后一般是
InputStream中读出来的
byte[]数据(
OkHttp还可以拿到
String),然后我们把
byte[]转成
String、
Protobuf或者
Bitmap之类的数据。
我们今天说的就是把
Body转成
XXX这个环节,一般开发中我们都是请求
String,因为
String可能是
JSON或者
XML类型的数据格式,方便客户端解析,然而我们在把
JSON、
XML解析成
JavaBean、
List<JavaBean>或者其它复杂对象这个过程特别繁琐,既要判断
Http的成功与否,又要判断业务层的成功与否,还要判断数据层次结构是否正确,因此如果我们直接能请求到
JavaBean、
List<JavaBean>或者其它复杂对象那就太省事了。
目前开源社区已经有很多针对
URLConnection、
HttpClient或者
OkHttp二次封装的Http框架,使我们的请求代码变得非常简单,比如:
Retrofit、
Volley、
NoHttp等,这篇文章就是基于这些已经二次封装过的框架,让请求变得更加简单。
需要准备的知识点
为了方便大家理解后面的内容,需要准备一些知识点和必要的东西,对于这些内容都不是问题的读者那你直接略过即可。定义JavaBean
因为后面要展示JSON数据格式,我们这里定义两个对应数据格式的
JavaBean。
用户信息的
JavaBean:
class UserInfo { private String name; // 姓名。 private String url; // 个人主页地址。 private List<Project> projectList; // 做的项目列表。 // 构造方法。 public UserInfo() {} ... // getter and setter. }
项目属性
JavaBean:
class Project { private int id; // ID. private String name; // 项目名称。 private String url; // 项目地址。 private String comment; // 项目描述。 // 构造方法。 public Project() {} ... // getter and setter. }
上面的结构大概是一个用户的
JavaBean,含用户姓名、用户URL和用户的项目列表,项目的
JavaBean含项目名称、项目地址和项目描述等信息。
会使用FastJson、Gson或者Simple
FastJson用来解析JSON:https://github.com/alibaba/fastjson
Gson用来解析JSON:https://github.com/google/gson
Simple用来解析XML:http://simple.sourceforge.net/
需要说明的是,解析
JSON时用
Gson或者
FastJson可以很方便的解析
JSON数据,二者选其一就可以;解析
XML时用
Simple可以像
Gson或者
FastJson一样方便。
本文是使用的为
Android优化过的
FastJson版本:
compile 'com.alibaba:fastjson:1.1.56.android'
我选择用FastJson的理由
我对Gson和
FastJson做过性能测试,在数据相对少的情况下,二者差别不大,甚至
Gson性能上更优于
FastJson,不过只有几十毫秒,当数据量大的时候
Fastjson的性能明显高于
Gson一秒以上;而且从便用性上来看,
FastJson兼容
Android原生的
JSONObject用法,并且在序列化和反序列化时代码超级简洁,下面我举几个栗子。
解析成
JSONObject
JSONObject object = JSON.parseObject(jsonString);
解析成
JavaBean
UserInfo user = JSON.parseObject(jsonString, UserInfo.class);
解析成
List<JavaBean>
List<UserInfo> userList = JSON.parseArray(jsonString, UserInfo.class);
解析成
Map
// String类型: Map<String, String> tempMap = JSON.parseObject(jsonString, new TypeReference<Map<String, String>>() {}); // JavaBean类型: Map<String, UserInfo> tempMap = JSON.parseObject(jsonString, new TypeReference<Map<String, UserInfo>>() {}); // List<JavaBean>类型: Map<String, List<UserInfo>> tempMap = JSON.parseObject(jsonString, new TypeReference<Map<String, List<UserInfo>>>() {});
根据业务对请求参数加密签名
一般公司都会对请求的数据进行加密或者签名的,关于签名建议在看完本文后再看,清风徐来写了一篇在子线程为请求做加密和签名的文章,我就不再赘述了,需要的读者可以看看:http://blog.csdn.net/xuyonghong1122/article/details/53350968
Http常用的两种数据层次结构
注意我们这里说的是数据层次结构,至于数据格式呢,我们上面提到了JSON、
XML和
Protobuf,本文以
JSON为例,看完这篇文章我相信你肯定可以举一反三了。
在这里举两个常见的数据层次结构的栗子:
业务状态码在HttpBody中返回
业务状态码用Http响应码返回
什么是业务状态码呢,比如说我们提交了订单到服务器,服务器产生订单信息后把订单详情数据返回给我们,我们需要判断服务器是否成功执行了这个操作,就要依赖业务状态码了,如果业务状态码错误,我们就要根据
message提示用户错误信息,下面分别说明。
业务状态码在HttpBody中返回
这种形式其实是responseCode+
errorCode+
data/message的形式,
errorCode表示业务状态码,
data表示服务器数据,
message表示错误时的错误提示信息,我们判断
responseCode正确后,再根据
errorCode的正确与错误拿
message或者
data,上一段
JSON代码:
错误时的数据格式:
{"errorCode":0, "message":"帐号或者密码错误"}
正确时的数据格式:
{ "errorCode": 1, "data": { "name": "严振杰", "url": "http://www.yanzhenjie.com", "projectList": [ { "id": 0, "name": "NoHttp", "url": "https://github.com/yanzhenjie/NoHttp", "comment": "Anroid客户端实现Http标准协议的网络框架" }, { "id": 1, "name": "Album", "url": "https://github.com/yanzhenjie/Album", "comment": "一个MD风格的相册选择,支持相机,多选,兼容Android7.0及以上", }, { "id": 2, "name": "AndPermission", "url": "https://github.com/yanzhenjie/AndPermission", "comment": "Anroid6.0运行时权限管理框架" } ] } }
看了数据层次结构可能就理解的更加清晰了,上面列出了两种情况,一种是业务错误时,一种是业务正确时,不同的项目可能还有其它不同的业务状态码。
在没有进行任何封装的情况下我们是这样做的:
Response response = ... // 服务器响应。 if(response.responseCode == 200) { // Http状态码正确。 String json = response.body; JSONObject object = JSON.parseObject(json); if(object.getIntValue("errorCode") == 1) { // 业务正确,解析data。 String data = object.getString("data"); // 解析成我们需要的对象。 UserInfo user = JSON.parseObject(data, UserInfo.class); // 或者对象的集合。 List<UserInfo> userList = JSON.parseArray(data, UserInfo.class); ... } else { // 业务错误,显示错误信息。 toast(object.getString("message")); } } else { // Http 响应码可能是400...、500...,这里的处理先不论。 }
请求个数据还真麻烦,其中还且不论数据层次结构的正确性,否则我们要用
try-catch来处理异常。不过读者且不着急如何,我们先看另一种数据层次结构的情况。
业务状态码用Http响应码返回
这种形式其实是responseCode+
data/message的形式,
responseCode在表示Http状态的同时还兼顾了业务状态码,
data表示服务器数据,
message表示错误时的错误提示信息,我们根据
responseCode正确与错误拿
message或者
data,上一段
JSON代码:
错误时的数据格式:
{"message":"帐号或者密码错误"}
正确时的数据:
{ "name": "严振杰", "url": "http://www.yanzhenjie.com", "projectList": [ { "id": 0, "name": "NoHttp", "url": "https://github.com/yanzhenjie/NoHttp", "comment": "Anroid客户端实现Http标准协议的网络框架" }, { "id": 1, "name": "Album", "url": "https://github.com/yanzhenjie/Album", "comment": "一个MD风格的相册选择,支持相机,多选,兼容Android7.0及以上", }, { "id": 2, "name": "AndPermission", "url": "https://github.com/yanzhenjie/AndPermission", "comment": "Anroid6.0运行时权限管理框架" } ] }
这种结构层次的数据比上面那种简单多了,但是解析起来还是比较麻烦,在没有进行任何封装的情况下我们是这样做的:
Response response = ... // 服务器响应。 if(response.responseCode == 200) { // Http状态码、业务状态正确。 String json = response.body; JSONObject object = JSON.parseObject(json); String data = object.getString("data"); // 解析成我们需要的对象: UserInfo user = JSON.parseObject(data, UserInfo.class); // 或者对象的集合: List<UserInfo> userList = JSON.parseArray(data, UserInfo.class); ... } else if(response.responseCode == 401) { // 业务错误,显示错误信息。 String json = response.body; toast(JSON.parseObject(json).getString("message")); } else { // 其它错误状态码处理。 }
这里要特别注意,我在上面用的200和401都是举栗子,读者的服务器定义的可能不是这样子。同时这里也会存在数据层次结构的问题,所以在解析的时候也需要捕捉异常。
综上所述,如果我们可以直接请求到
UserInfo、
List<UserInfo>那岂不是可以简化很多判断和代码?好的,下面我们发车,乘客们坐稳了。
封装请求JavaBean, List等复杂对象
首先这里要选一个Http框架,这个随读者自己喜欢用哪个就用哪个。不过我这里要说2点:
解析数据最好放在子线程,因为
Gson、
FastJson和
Simple等解析都是耗时的。
数据签名校验也最好放在子线程,因为设计到数据计算,也就是算法,所以也是耗时的。
我这里使的是NoHttp:https://github.com/yanzhenjie/NoHttp,它的底层使用的是
URLConnection或者
OkHttp,可以动态切换底层;基础用法我就不解释了,这里有NoHttp的使用文档:doc.nohttp.net。
对于签名的话可以参考清风徐来的这篇文章:
http://blog.csdn.net/xuyonghong1122/article/details/53350968
NoHttp通过自定义
Request请求自己想要的数据类型,比如NoHttp自带的
StringRequest等都是继承
RestRequest后自定义的。先来看看NoHttp如何请求不同类型的数据,这个一定要看看,不然等下可能看不懂:
// 请求队列。 private RequestQueue mRequestQueue; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 创建请求队列: mRequestQueue = NoHttp.newRequestQueue(); } @Override protected void onDestroy() { mRequestQueue.cancelAll(); // 退出页面时时取消所有请求。 mRequestQueue.stop(); // 退出时销毁队列,回收资源。 super.onDestroy(); } // 发起一个请求。 public <T> void request(int what, Request<T> request, OnResponseListener<T> listener) { mRequestQueue.add(what, request, listener); } // 请求String。 private void requestString() { String url = ... StringRequest<String> request = new StringRequest<>(url); // 调用上面的方法发起请求。 request(0, request, new SimpleHttpListener<String>() { @Override public void onSucceed(int what, Response<String> response) { // 请求成功。 if(response.getHeaders().getResponseCode() == 200) { // 重点、重点、重点:拿到结果。 String string = response.get(); ... } } @Override public void onFailed(int what, Response<String> response) { // 请求失败。 } }); } // 请求Bitmap。 private void requestBitmap() { String url = ... ImageRequest<String> request = new ImageRequest<>(url); // 调用上面的方法发起请求。 request(0, request, new SimpleHttpListener<Bitmap>() { @Override public void onSucceed(int what, Response<String> response) { // 请求成功。 if(response.getHeaders().getResponseCode() == 200) { // 重点、重点、重点:拿到结果。 Bitmap string = response.get(); ... } } @Override public void onFailed(int what, Response<String> response) { // 请求失败。 } }); }
到这里基本就会用
NoHttp了,我们需要重点关心的一个地方是
// 重点、重点、重点:拿到结果。这里的代码:
String string = response.get(); Bitmap string = response.get();
为什么不同类型的结果都是通过
response.get()来拿呢,原因就是
NoHttp用的是泛型,所以
Request是
XX类型的,接受结果的
Listener就是必须要得是
XX类型的,所以我们在成功方法这里看到:
public void onSucceed(int what, Response<String> response) public void onSucceed(int what, Response<Bitmap> response)
看到是
Response<String>、
Response<Bitmap>了吧,所以我们可以通过
response.get()直接拿到对应类型的数据。
那么同样的道理,我们继承
RestRequest自定义
Request直接把数据解析成
JavaBean、
List<JavaBean>不久O了吗?先来参考一下
NoHttp的
StringRequest是如何写的:
// 这里利用泛型指定需要的返回对象类型。 public class StringRequest extends RestRequest<String> { public StringRequest(String url) { this(url, RequestMethod.GET); } public StringRequest(String url, RequestMethod requestMethod) { super(url, requestMethod); } // 最重要的就是这个方法,返回参数是泛型指定的,把解析后的对象return出去。 @Override public String parseResponse(Headers headers, byte[] body) throws Exception { return parseResponseString(headers, body); } // 这个静态方法,根据contentType中的charset把body解析为string,其它地方也可调用。 public static String parseResponseString(Headers headers, byte[] body) { if (body == null || body.length == 0) return ""; String charset = HeaderUtil.parseHeadValue(headers.getContentType(), "charset", ""); return IOUtils.toString(body, charset); } }
看到这里我需要告诉人儿们,
parseResponse(Headers, byte[])就是
NoHttp底层用来解析请求结果的方法,
NoHttp底层会调用这个方法来拿到这个
Request对应的结果,所以
parseResponse(Headers, byte[])方法也是在子线程被回调的,因此在这里做数据解析实在太美妙了,我们来写一个请求
JavaBean的
Request:
public class EntityRequest<T> extends RestRequest<T> { private Class<T> clazz; public EntityRequest(String url, Class<T> clazz) { this(url, RequestMethod.GET); this.clazz = clazz; } public EntityRequest(String url, RequestMethod requestMethod, Class<T> clazz) { super(url, requestMethod); this.clazz = clazz; } @Override public T parseResponse(Headers headers, byte[] body) throws Exception { String json = StringRequest.parseResponseString(headers, body); return JSON.parseObject(json, clazz); } }
是不是稍微好一点了,不过有的同学又看不懂了,可能会问你咋又用了泛型,因为要用于所有的
JavaBean,所以这个泛型的作用是来限制参数类型的,因此我们可以这样用:
EntityRequest<UserInfo> request = new EntityRequest(url, UserInfo.class); request(0, request, new SimpleHttpListener<UserInfo>() { @Override public void onSucceed(int what, Response<UserInfo> response) { // 请求成功。 if(response.getHeaders().getResponseCode() == 200) { // 重点、重点、重点:拿到结果。 UserInfo userInfo = response.get(); ... } } @Override public void onFailed(int what, Response<String> response) { // 请求失败。 } });
到这里NoHttp自定义请求相信读者也会了,下面我们把结合业务的请求结果返回分析一下。
结合业务的返回结果对象的封装
在最开始最介绍两种数据层次结构的时候我们看到,无论是请求String还是
JavaBean都需要在
onSucceed(int, Response)中做很多判断和解析,所以我们这里想直接返回一个已经封装好的对象作为结果,我是这样设计的:
public class Result<T> { private boolean isSucceed; // 业务和Http层是否成功。 private T result; // 结果。 private Headers headers; // Http相应头。 private String error; // 错误提示信息。 public Result(boolean isSucceed, T result, Headers headers, String error) { this.isSucceed = isSucceed; this.result = result; this.headers = headers; this.error = error; } public boolean isSucceed() { return isSucceed; } public T getResult() { return result; } public Headers getHeaders() { return headers; } public String getError() { return error; } }
重点干货:这样就仅仅用一个判断就搞定了,我们所有的自定义请求全部返回
Result这个类,而真正的结果被
Result包裹,Http层的成功和业务的成功都被
isSucceed记录,那么我们在
onSucceed(int, T)的时候仅仅这样写:
@Override public void onSucceed(int what, Result<UserInfo> result) { if(result.isSucceed()) { UserInfo user = result.getResult(); } else { toast(result.getError()); } }
怎样?是不是简介清晰了很多呢?又可以避免很多异常的捕获,因此我们下面就要把
Http的请求结果解析后封装成这样来返回。
结合业务解析结果
下面我们就结合上面提到的两种数据层次结构(业务),来封装我们的自定义请求中解析的代码。再回顾一下上面我们提到的两种数据层次结构,分别是:
业务状态码在HttpBody中返回
业务状态码用Http响应码返回
先来看第一种。
业务状态码在HttpBody中返回
看到这里估计很多人已经忘记了这种数据的层次结构是怎样的,我们回顾一下:错误时的数据格式:
{"errorCode":0, "message":"帐号或者密码错误"}
正确时的数据格式:
{ "errorCode": 1, "data": { "name": "严振杰", "url": "http://www.yanzhenjie.com", "projectList": [ { "id": 0, "name": "NoHttp", "url": "https://github.com/yanzhenjie/NoHttp", "comment": "Anroid客户端实现Http标准协议的网络框架" }, ... ] } }
结合上面定义的
Result类,我们在自定义
Request就是这样写的了:
public abstract class AbstractRequest<T> extends RestRequest<Result<T>> { private Class<T> clazz; public EntityRequest(String url, Class<T> clazz) { this(url, RequestMethod.GET); this.clazz = clazz; } public EntityRequest(String url, RequestMethod requestMethod, Class<T> clazz) { super(url, requestMethod); this.clazz = clazz; } @Override public Result<T> parseResponse(Headers headers, byte[] body) throws Exception { ... } }
一个最简单的
parseResponse()方法应该是这样的:
@Override public Result<T> parseResponse(Headers headers, byte[] body) throws Exception { String json = StringRequest.parseResponseString(headers, body); T t = JSON.parseObject(json, clazz); return new Result(true, t, headers, ""); }
看到这里也就很清楚了,我们最多把业务和Http层的判断再加进去就完美了吧,下面是我的业务的封装,读者一定要结合注释看:
public abstract class AbstractRequest<T> extends RestRequest<Result<T>> { private Class<T> clazz; public EntityRequest(String url, Class<T> clazz) { this(url, RequestMethod.GET); this.clazz = clazz; } public EntityRequest(String url, RequestMethod requestMethod, Class<T> clazz) { super(url, requestMethod); this.clazz = clazz; } // 这个方法由继承的子类去实现,解析成我们真正想要的数据类型。 protected abstract T getResult(String data); @Override public Result<T> parseResponse(Headers headers, byte[] body) throws Exception { int responseCode = headers.getResponseCode(); // 响应码。 // 响应码等于200,Http层成功。 if (responseCode == 200) { if (body == null || body.length == 0) { // 服务器包体为空。 return new Result<>(true, null, headers, null); } else { // 这里可以统一打印所有请求的数据哦: String bodyString = StringRequest.parseResponseString(headers, body); try { JSONObject bodyObject = JSON.parseObject(bodyString); // 业务层成功。 if (bodyObject.getIntValue("errorCode") == 1) { String data = bodyObject.getString("data"); // 重点、重点、重点:调用子类,解析出真正的数据。 T result = getResult(data); return new Result<>(true, result, headers, null); } else { String error = bodyObject.getString("message"); return new Result<>(false, null, headers, error); } } catch (Exception e) { // 解析异常,测试时通过,正式发布后就是服务器的锅。 String error = "服务器返回数据格式错误,请稍后重试"; return new Result<>(false, null, headers, error); } } } else { // 其它响应码,如果和服务器没有约定,那就是服务器发生错误了。 String error = "服务器返回数据格式错误,请稍后重试"; return new Result<>(false, null, headers, error); } } }
我们写了一个基类,用来封装我们的业务层的解析,下面我们具体实现
String、
JavaBean、
List<JavaBean>的请求。
结合业务的StringRequest
很简单了,我们继承上面的AbstractRequest:
public class StringRequest extends AbstractRequest<String> { public StringRequest(String url, RequestMethod requestMethod) { super(url, requestMethod); } @Override protected String getResult(String data) throws Exception { return data; } }
结合业务的EntityRequest
同样的继承上面的AbstractRequest:
public class EntityRequest<T> extends AbstractRequest<T> { private Class<T> clazz; public StringRequest(String url, RequestMethod requestMethod, Class<T> clazz) { super(url, requestMethod); this.clazz = clazz; } @Override protected T getResult(String data) throws Exception { return JSON.parseObject(data, clazz); } }
结合业务的EntityListRequest
同样的继承上面的AbstractRequest:
public class EntityListRequest<T> extends AbstractRequest<List<T>> { private Class<T> clazz; public StringRequest(String url, RequestMethod requestMethod, Class<T> clazz) { super(url, requestMethod); this.clazz = clazz; } @Override protected List<T> getResult(String data) throws Exception { return JSON.parseArray(data, clazz); } }
对Listener的封装
到这里,第一种封装就完成了,但是我们看看我们请求的时候,我们的Listener是怎样写的:
EntityRequest request = new EntityRequest(url); request(0, request, new SimpleHttpListener<Result<UserInfo>>() { @Override public void onSucceed(int what, Response<Result<UserInfo>> response) { Result<UserInfo> result = response.get(); if(result.isSucceed()) { ... } else { toast(result.getErrot()); } } });
有人就有疑问了,咋还不是你上面说的那种最简单的形式呢?问的好,因为我们的默认的
Listener是的回调是用
Response的,它的
Listener<T>这个泛型限制的是
Response<T>中的这个泛型。我们再看看
AbstractRequest是怎样的:
public abstract class AbstractRequest<T> extends RestRequest<Result<T>> ...
我们注意到其中的
RestRequest<Result<T>>,这个
<Result<T>>才是和
Listener<T>中的
<T>同一个等级,所以
onSucceee()方法中的
Response是
Response<Result<T>>这样子的,因为为了方便回调,我们需要像我们上面使用的
SimpleHttpListener一样来一个默认的实现,并且把
Result回调出去。
因此先定义一个
HttpListener:
public interface HttpListener<T> { void onSucceed(int what, Result<T> t); void onFailed(int what); void onFinish(int what); }
方法都是什么意思不用我解释了吧,然后我们写一个
NoHttp的
Listener的默认实现类,用来回调
HttpListener:
public class DefaultResponseListener<T> implements OnResponseListener<Result<T>> { private WaitDialog dialog; private HttpListener<T> listener; private AbstractRequest<T> request; public DefaultResponseListener(HttpListener<T> listener, AbstractRequest<T> request) { this.dialog = ... // 实例化dialog,你可以在构造中传如context。 this.listener = listener; this.request = request; } @Override public void onStart(int what) { if(!request.isCanceled()) { // 显示请求的dialog。 dialog.show(); } } @Override public void onSucceed(int what, Response<Result<T>> response) { // Http层的成功,在回调的onSucceed中判断业务即可。 if (listener != null) httpListener.onSucceed(what, response.get()); } @Override public void onFailed(int what, Response<Result<T>> response) { Exception exception = response.getException(); if (exception instanceof TimeoutError) { // 超时。 // Toast } if (httpListener != null) httpListener.onFailed(what); } @Override public void onFinish(int what) { if(dialog != null && dialog.isShowing()) { // 关闭请求的dialog。 dialog.dismiss(); } if (httpListener != null) httpListener.onFinish(what); } }
现在我们已经把复杂的回调简单化了,然后我们把最开始的那个
Activity中的请求的代码和我们现在的封装结合一下:
// 请求队列。 private RequestQueue mRequestQueue; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 创建请求队列: mRequestQueue = NoHttp.newRequestQueue(); } // 取消请求的标识符。 private Object sign = new Object(); // 发起一个请求。 public <T> void request(int what, AbstractRequest<T> request, HttpListener<T> listener) { request.setCancelSign(sign) // 注意这里: mRequestQueue.add(what, request, new DefaultResponseListener(listener, request)); } @Override protected void onDestroy() { requestQueue.cancelBySign(sign); // 退出页面时取消本页面的所有请求。 mRequestQueue.stop(); // 退出时销毁队列,回收资源。 super.onDestroy(); } // 请求String。 private void requestString() { StringRequest request = new StringRequest(url); request(0, request, new HttpListener() { @Override public void onSucceed(int waht, Result<String> result) { if(result.isSucceed()) { String data = result.getResult(); } else { toast(result.getError()); } } }); } // 请求JavaBean。 private void requestEntity() { EntityRequest request = new EntityRequest(url, UserInfo.class); request(0, request, new HttpListener() { @Override public void onSucceed(int waht, Result<UserInfo> result) { if(result.isSucceed()) { UserInfo user = result.getResult(); } else { toast(result.getError()); } } }); } // 请求List<JavaBean>。 private void requestList() { EntityListRequest request = new EntityListRequest(url, UserInfo.class); request(0, request, new HttpListener() { @Override public void onSucceed(int waht, Result<List<UserInfo>> result) { if(result.isSucceed()) { List<UserInfo> user = result.getResult(); } else { toast(result.getError()); } } }); }
哈哈是不是超级简单了,简直完美呀!!!
业务状态码用Http响应码返回
接下来我们把第二种数据层次结构业务状态码在HttpBody中返回的形式也封装一下,还是需要回顾一下数据层次结构:错误时的数据格式:
{"message":"帐号或者密码错误"}
正确时的数据:
{ "name": "严振杰", "url": "http://www.yanzhenjie.com", "projectList": [ { "id": 0, "name": "NoHttp", "url": "https://github.com/yanzhenjie/NoHttp", "comment": "Anroid客户端实现Http标准协议的网络框架" }, ... ] }
根据上面的分析,这里只是
AbstractRequest中解析结果后封装
Result时的逻辑不同,所以我们仅仅需要改动
AbstractRequest的代码:
public abstract class AbstractRequest<T> extends RestRequest<Result<T>> { private Class<T> clazz; public EntityRequest(String url, Class<T> clazz) { this(url, RequestMethod.GET); this.clazz = clazz; } public EntityRequest(String url, RequestMethod requestMethod, Class<T> clazz) { super(url, requestMethod); this.clazz = clazz; } // 这个方法由继承的子类去实现,解析成我们真正想要的数据类型。 protected abstract T getResult(String data); @Override public Result<T> parseResponse(Headers headers, byte[] body) throws Exception { int responseCode = headers.getResponseCode(); // 响应码正确,且包体不为空。 if (responseCode == 200 && body != null && body.length > 0) { // 这里可以统一打印所有请求的数据哦: String result = StringRequest.parseResponseString(headers, body); try { T t = getResult(result); return new Result<>(true, t, headers, null); } catch (Exception e) { // 解析发生错误。 String error = "服务器返回数据格式错误,请稍后重试"; return new Result<>(false, null, headers, error); } } else if (responseCode >= 400) { // 其它响应码处理。 // 这里可以统一打印所有请求的数据哦: String result = StringRequest.parseResponseString(headers, body); String error = "服务器发生错误,请稍后重试"; // 错误响应码时正常解析说明是服务器返回的json数据。 // 非正常解析说明是服务器返回的崩溃信息html等。 try { JSONObject jsonObject = JSON.parseObject(result); error = jsonObject.getString("message"); } catch (Exception ignored) { } return new Result<>(false, null, headers, error); } else { String error = "服务器返回数据格式错误,请稍后重试"; return new Result<>(false, null, headers, error); } } }
其它代码都是相同的,到此为止两种数据层次结构的封装就都完成了,选择一种适合你们数据层地结构的格式使用即可。
另外几点说明:
XML格式也是如此,只是需要使用
Simple解析。
使用
GSON解析也可以,看你爱好了,我个人偏好
FastJson。
然后是网络框架的选择,我推荐
Retrofit、
Volley或者
NoHttp,因为我对
Nohttp比较熟悉,所以文中使用的是NoHttp。
本文的源代码在这里下载:https://github.com/yanzhenjie/LiveSourceCode
版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com
相关文章推荐
- Android-Http请求JavaBean、List、Map等复杂对象
- Android本地最简单的数据存储,没有之一(让SharedPreferences存取JavaBean对象或List<Bean>)
- 使用第三方工具,将JavaBean对象或者List<JavaBean/String/Integer>或Set或Map对象转成JSON
- SpringMVC 在业务控制方法中收集数组及List<JavaBean>参数
- 【SpringMVC】在业务控制方法中收集数组与List<JavaBean>参数(十五)
- SharedPreferences存取JavaBean对象或List<Bean>
- 我的Android进阶之旅------>android如何将List<NameValuePair>请求参数列表转换为json格式
- Android两个Activity之间传递List<Object>对象
- 在业务控制方法中收集List<JavaBean>参数 (例如 同时添加多个用户) (12)
- Mybatis查询复杂对象(对象包括对象和List<对象>)
- Android学习笔记3 使用Intent传递复杂的数据(对象,List<Object>等)
- 对List<>中对象的属性进行排序。
- 将List集合中的map对象转为List<对象>形式--封装类
- 如何判断对象包含List<T>中的类型
- 比较一个对象list中的id值和List<Integer>的值是否相等
- android Collections.sort(List<T> list) 与JAVA Collections.sort(List<T> list)
- C#中List<T>对象的深度拷贝问题
- android .向sdcard写入json字符串、读取json字符保存到list<类>中供调用
- List<Model>对象转成DataTable
- List<Object>装的是对象 排序问题