Android-Http请求JavaBean、List、Map等复杂对象

58b016bb6195e71be1a0ab52f1d15f82.gif

今天群里小伙伴说我好久没更新文章了,赶紧翻了下自己的博客和公众号,发现真的成一个月一篇的更新了,今后打算每周一篇,时间定在周四和周五下午。

好久不写其实是因为不知道要写点什么才算是干货,不过最近有小伙伴问,Http怎么直接请求JavaBean、List<JavaBean>或者Map<>这种复杂对象,我觉得这个主题写文章也不错,不过对于这个问题的解决方案,其实在2016年我就直播过了,并把视频和代码都共享出来了,如果你想直接看代码和视频:

视频下载地址,对应视频序号为10:

https://pan.baidu.com/s/1bpHp3I3

代码下载地址,对应文件夹为HttpRestModule:

https://github.com/yanzhenjie/LiveSourceCode

场景和现状

首先说一下为什么会有这种问题,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:

43bed24fac7cec62c5dd78031c8d9830.png

项目属性JavaBean:

f237fedb4e0e084a33891a2129645293.png

上面的结构大概是一个用户的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版本:

81f5a0ba0f89bc0347007a1c211de6f6.png

我选择用FastJson的理由

我对Gson和FastJson做过性能测试,在数据相对少的情况下,二者差别不大,甚至Gson性能上更优于FastJson,不过只有几十毫秒,当数据量大的时候Fastjson的性能明显高于Gson一秒以上;而且从便用性上来看,FastJson兼容Android原生的JSONObject用法,并且在序列化和反序列化时代码超级见解,下面我举几个栗子。

a08f826adee00d14c55f25f1a2ad6611.png

d4323579d764cc4cb98ec6da0bbe76ed.jpeg

根据业务对请求参数加密签名

一般公司都会对请求的数据进行加密或者签名的,关于签名建议在看完本文后再看,清风徐来写了一篇在子线程为请求做加密和签名的文章,我就不再赘述了,需要的读者可以看看: 

http://blog.csdn.net/xuyonghong1122/article/details/53350968

Http常用的两种数据层次结构

注意我们这里说的是数据层次结构,至于数据格式呢,我们上面提到了JSON、XML和Protobuf,本文以JSON为例,看完这篇文章我相信你肯定可以举一反三了。

在这里举两个常见的数据层次结构的栗子:

  1. 业务状态码在HttpBody中返回

  2. 业务状态码用Http响应码返回

什么是业务状态码呢,比如说我们提交了订单到服务器,服务器产生订单信息后把订单详情数据返回给我们,我们需要判断服务器是否成功执行了这个操作,就要依赖业务状态码了,如果业务状态码错误,我们就要根据message提示用户错误信息,下面分别说明。

业务状态码在HttpBody中返回

这种形式其实是responseCode + errorCode + data/message的形式,errorCode表示业务状态码,data表示服务器数据,message表示错误时的错误提示信息,我们判断responseCode正确后,再根据errorCode的正确与错误拿message或者data,上一段JSON代码:

  • 错误时的数据格式:

8434fce8dc84592b131e38bbb5320f7f.png

  • 正确时的数据格式:

2256f5db13f753fb5594444e9d731402.jpeg

看了数据层次结构可能就理解的更加清晰了,上面列出了两种情况,一种是业务错误时,一种是业务正确时,不同的项目可能还有其它不同的业务状态码。

在没有进行任何封装的情况下我们是这样做的:

f122eda3be9ea4822264b8228ff7f168.jpeg

请求个数据还真麻烦,其中还且不论数据层次结构的正确性,否则我们要用try-catch来处理异常。不过读者且不着急如何,我们先看另一种数据层次结构的情况。

业务状态码用Http响应码返回

这种形式其实是responseCode + data/message的形式,responseCode在表示Http状态的同时还兼顾了业务状态码,data表示服务器数据,message表示错误时的错误提示信息,我们根据responseCode正确与错误拿message或者data,上一段JSON代码:

  • 错误时的数据格式:

0c728693e09a9c4e0764a12e3b603a1c.png

  • 正确时的数据:

87ad920d6a6c0fc7d32b0e1817a0e263.jpeg

这种结构层次的数据比上面那种简单多了,但是解析起来还是比较麻烦,在没有进行任何封装的情况下我们是这样做的:

ca48532eeae3fee003c785bcd604d4eb.jpeg

这里要特别注意,我在上面用的200和401都是举栗子,读者的服务器定义的可能不是这样子。同时这里也会存在数据层次结构的问题,所以在解析的时候也需要捕捉异常。

综上所述,如果我们可以直接请求到UserInfo、List<UserInfo>那岂不是可以简化很多判断和代码?好的,下面我们发车,乘客们坐稳了。

封装请求JavaBean, List等复杂对象

首先这里要选一个Http框架,这个随读者自己喜欢用哪个就用哪个。

不过我这里要说2点:

解析数据最好放在子线程,因为Gson、FastJson和Simple等解析都是耗时的。

数据签名校验也最好放在子线程,因为设计到数据计算,也就是算法,所以也是耗时的。

我这里使的是NoHttp,它的底层使用的是URLConnection或者OkHttp,可以动态切换底层;基础用法我就不解释了,这里有NoHttp的使用文档:doc.nohttp.net。

对于签名的话可以参考清风徐来的这篇文章: 

http://blog.csdn.net/xuyonghong1122/article/details/53350968

NoHttp通过自定义Request请求自己想要的数据类型,比如NoHttp自带的StringRequest等都是继承RestRequest后自定义的。先来看看NoHttp如何请求不同类型的数据,这个一定要看看,不然等下可能看不懂:

6b5f843923677e6d52da6d0f31df12e3.jpeg

到这里基本就会用NoHttp了,我们需要重点关心的一个地方是// 重点、重点、重点:拿到结果。这里的代码:

529daed3486b3766cc947c704a099f90.png

为什么不同类型的结果都是通过response.get()来拿呢,原因就是NoHttp用的是泛型,所以Request是XX类型的,接受结果的Listener就是必须要得是XX类型的,所以我们在成功方法这里看到:

4ff4fb36815a4b90d69d898935622ecb.png

看到是Response<String>、Response<Bitmap>了吧,所以我们可以通过response.get()直接拿到对应类型的数据。

那么同样的道理,我们继承RestRequest自定义Request直接把数据解析成JavaBean、List<JavaBean>不久O了吗?先来参考一下NoHttp的StringRequest是如何写的:

93d446649c00da9f52c294f3befacaf4.jpeg

看到这里我需要告诉人儿们,parseResponse(Headers, byte[])就是NoHttp底层用来解析请求结果的方法,NoHttp底层会调用这个方法来拿到这个Request对应的结果,所以parseResponse(Headers, byte[])方法也是在子线程被回调的,因此在这里做数据解析实在太美妙了,我们来写一个请求JavaBean的Request:

84f8f1a9f379b2d772f71037967e2a5d.jpeg

是不是稍微好一点了,不过有的同学又看不懂了,可能会问你咋又用了泛型,因为要用于所有的JavaBean,所以这个泛型的作用是来限制参数类型的,因此我们可以这样用:

f60088726f2954510493e84baec13626.jpeg

到这里NoHttp自定义请求相信读者也会了,下面我们把结合业务的请求结果返回分析一下。

结合业务的返回结果对象的封装

在最开始最介绍两种数据层次结构的时候我们看到,无论是请求String还是JavaBean都需要在onSucceed(int, Response)中做很多判断和解析,所以我们这里想直接返回一个已经封装好的对象作为结果,我是这样设计的:

a99d1b2efab2566ab1bb7e505fb93e19.jpeg

重点干货:这样就仅仅用一个判断就搞定了,我们所有的自定义请求全部返回Result这个类,而真正的结果被Result包裹,Http层的成功和业务的成功都被isSucceed记录,那么我们在onSucceed(int, T)的时候仅仅这样写:

a3ad5bff01dedf2b30e8671196403db4.png

怎样?是不是简介清晰了很多呢?又可以避免很多异常的捕获,因此我们下面就要把Http的请求结果解析后封装成这样来返回。

结合业务解析结果

下面我们就结合上面提到的两种数据层次结构(业务),来封装我们的自定义请求中解析的代码。

再回顾一下上面我们提到的两种数据层次结构,分别是:

  • 业务状态码在HttpBody中返回

  • 业务状态码用Http响应码返回

业务状态码在HttpBody中返回

看到这里估计很多人已经忘记了这种数据的层次结构是怎样的,我们回顾一下:

错误时的数据格式:

55b60f8199e701a5d0334b06269be892.png

正确时的数据格式:

7defb723f5f6ea21dc651ecf65e5407b.jpeg

结合上面定义的Result类,我们在自定义Request就是这样写的了:

2d62f8cf6cf1cd9029de258199c981e9.jpeg

一个最简单的parseResponse()方法应该是这样的:

f6ef2b148073e8ea54aa9c1bf06ee860.png

看到这里也就很清楚了,我们最多把业务和Http层的判断再加进去就完美了吧,下面是我的业务的封装,读者一定要结合注释看:

5d606ebbdba6bb05f7c6c96f81a3b918.jpeg

我们写了一个基类,用来封装我们的业务层的解析,下面我们具体实现String、JavaBean、List<JavaBean>的请求。

结合业务的StringRequest

很简单了,我们继承上面的AbstractRequest:

7ed9c2d7e64e4d22a84204722ff10359.png

结合业务的EntityRequest

同样的继承上面的AbstractRequest:

c5b4e23818bb0a53a22810d80f6522da.png

结合业务的EntityListRequest

同样的继承上面的AbstractRequest:

a8c75b8afd9af5f790a354a4301eecef.png

对Listener的封装

到这里,第一种封装就完成了,但是我们看看我们请求的时候,我们的Listener是怎样写的:

63ad326fa9a7fe5f2d8cec3881081478.png

有人就有疑问了,咋还不是你上面说的那种最简单的形式呢?问的好,因为我们的默认的Listener是的回调是用Response的,它的Listener<T>这个泛型限制的是Response<T>中的这个泛型。我们再看看AbstractRequest是怎样的:

aff895d8ad9fe422b1051faa194c2000.png

我们注意到其中的RestRequest<Result<T>>,这个<Result<T>>才是和Listener<T>中的<T>同一个等级,所以onSucceee()方法中的Response是Response<Result<T>>这样子的,因为为了方便回调,我们需要像我们上面使用的SimpleHttpListener一样来一个默认的实现,并且把Result回调出去。

因此先定义一个HttpListener:

1a148a9071b2d780a54001d067ef087a.png

方法都是什么意思不用我解释了吧,然后我们写一个NoHttp的Listener的默认实现类,用来回调HttpListener:

09e9b41e312389ce430ed12e2dd93df7.jpeg

现在我们已经把复杂的回调简单化了,然后我们把最开始的那个Activity中的请求的代码和我们现在的封装结合一下:

e9bc3d70a26c83529b161d10dacb6b13.jpeg

14c173a19cf3ecb84922cd762ca7b0ae.png哈哈是不是超级简单了,简直完美呀!!!

业务状态码用Http响应码返回

接下来我们把第二种数据层次结构业务状态码在HttpBody中返回的形式也封装一下,还是需要回顾一下数据层次结构:

  • 错误时的数据格式:

f8e9106003a90ca3180fc8f669670f3e.png

  • 正确时的数据:

5cd522c03ddfce07542123dd5a33de75.jpeg

根据上面的分析,这里只是AbstractRequest中解析结果后封装Result时的逻辑不同,所以我们仅仅需要改动AbstractRequest的代码:

eaf3bb6eb94171f8bdc13a61f3797a80.jpeg

de703258f9af564c117a16a836b9058d.png其它代码都是相同的,到此为止两种数据层次结构的封装就都完成了,选择一种适合你们数据层地结构的格式使用即可。

另外几点说明:

  1. XML格式也是如此,只是需要使用Simple解析。

  2. 使用GSON解析也可以,看你爱好了,我个人偏好FastJson。

  3. 然后是网络框架的选择,我推荐Retrofit、Volley或者NoHttp,因为我对Nohttp比较熟悉,所以文中使用的是NoHttp。

  4. 本文的源代码在这里下载:

https://github.com/yanzhenjie/LiveSourceCode

长按识别二维码关注我

8f27eb9d692ff73f75ed79829253379a.jpeg

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值