你的位置:首页 > 操作系统

[操作系统][HTTP那些事] JSON数据


随着Android的发展,各路大神的贡献,我们可用的轮子越来越多。比如HTTP请求框架,有自家的Volley,Square的okhttp, async-http-lib, 还有聚合版的xUtils以及AFinal。我想你肯定用过其中一个。

当然Stay今天不是来科普的,而是来跟大家一起思考一个问题的。我们暂且不提他们在内部做了多少优化,我们就说lib的返回数据。

在常用的http请求的返回值中,文件,JSON占绝大多数(图片有其他框架,这里不考虑)。文件下载都有专门的response,会帮你下载到制定路径,这个肯定都支持。那JSON呢?貌似都返回一个JSONObject或者JSONArray。

我去,做好事得做全啊,返回JSONObject是个什么鬼,难道还得自己动手写解析反序列化成自己要得对象?那是最低级的程序员干的事。好在我们都不傻,还有GSON,fastJson,Jackson帮我们来完成这步转化。

比方说服务器返回的数据:(双引号没加,占位置,别喷)

{name:stay, age:17, job:soho}

对应的对象:

Class User{  public String name,  public int age,  public String job}

好,那我们只需要在response回调时拿到result,调用json-lib反序列化就可以了,比如这样:

User user = gson.fromJson(result, User.class)

现在我们就可以使用user对象来更新UI了对吧。就多了一行代码,没强迫症的也就忍过去了。


接下来我们再看下面一种json数据:

{resCode:200, data:{name:Stay, age:17, job:soho}, msg:success}{resCode:401, data:{}, msg:token invalid}

我去,这是什么鬼,不好好遵守http协议,统一返回200是什么鬼,token不合法给我返回401 error code不好吗。。别说,很多公司都这么定义返回数据的

这样我们怎么办。。多写一步解析咯。

JSONObject json = new JSONObject(result)JSONObject data = json.optJSONObject("data")if(data != null){  User user = gson.fromJson(data.toString(), User.class)}

天啊,即使没强迫症,大概也会受不了每个API请求都写这么多代码了吧。


BB了这么多,大家应该懂我想表达什么了吧?

为什么不直接将json转换成我们要的对象User再回调呢?

而且在json数据大的情况下,反序列化还是耗时操作,有可能会卡UI的好吗。

这可能么?当然可以,不然Stay铺垫这么多干嘛。不过在Stay说解决方案之前,大家可以试着自己考虑下实现。

  1. 我们拿到的是String,格式是JSON

  2. 每次拿到JSON String,我们都来做了一步反序列化对象操作

  3. gson.fromJson需要两个参数(String JSON,Class dest)

  4. 回调参数得变成onResponse(User user)

  5. 框架层得知道Class dest

 

如果能把这些事情想清楚,你就可以很顺利得扩展那些开源框架了,以后你也再不用手写json解析了。

就说这么多,留点时间给大家自己思考下,下面再说解决方案😳

最后说下需要用到得知识点:泛型,反射

 

 

 

上文中,我们提到,能否让我们的HTTP框架帮我们完成自动反序列化的操作。同时也给大家做了些提示:泛型和反射。

现在我们以Volley为例:

在Volley中有三种Request:FileRequest,StringRequest,ImageRequest。

JSON数据也是字符串,所以我们要重写StringRequest中的部分方法就可以咯。

看下StringRequest源码,你会看到解析服务器byte[]到String的是parseNetworkResponse(NetworkResponse response),解析完String直接就return给外层了。

这里我们也采用相同的方式,创建一个GsonRequest< T >继承Request< T >, 至于实现,先把StringRequest的代码copy过来。唯一不同的是,StringRequest因为指定返回String类型数据所以不需要泛型。

在parseNetworkResponse(NetworkResponse response)中,我们引入gson来反序列化json string,T的class怎么办呢?你可以通过外层显式的传进来或者通过反射来拿类上的泛型T的type。两种都可以。

具体到代码:

这里写图片描述

扩展完毕,你只需要new GsonRequest,声明好泛型T,等待接收t对象回调就好啦。

这里写图片描述

如果你想知道这种扩展是如何一步步推导出来的,可以看Stay录的专题视频。 
传送门:预处理服务器返回的数据(JSON转对象)

像这样的扩展还有很多,框架不是万能的,要合理的根据自己的需求定制你想要的框架。

最后,留个问题给大家,如果是服务器返回了1M的JSON数据,还能用上述扩展么?如果不可以,那该怎么办呢?

 

 

 

在JSON反序列化的最后,有提到,如果有1M的JSON文本应该如何来解析?

1M的JSON String,不管用GSON,fastjson,jackson,估计都要OOM了吧。本来我想说200M的JSON数据的,想想这太坑了,就改说1M了。

答案,用JsonReader读流。比如说:

public User readUser(JsonReader reader) throws IOException {    reader.beginObject();    while (reader.hasNext()) {      String name = reader.nextName();      if (name.equals("name")) {        username = reader.nextString();      } else if (name.equals("followers_count")) {        followersCount = reader.nextInt();      } else {        reader.skipValue();      }    }    reader.endObject();    return new User(username, followersCount);}

  

我去,要手写JSON解析了,这太麻烦了吧。。。 
但是你想,跟性能比起来,这些体力也不算什么了吧。

上述没太多特别的地方,你可以直接看JsonReader的源码注释,里面有详细的用法示例。

在这里呢,我们先说说如何让JsonReader来读大JSON文本。

FileReader in = new FileReader(path);JsonReader reader = new JsonReader(in);

  

首先,你得先把JSON文本以文件的形式存到SD卡上。再通过FileReader拿到文件流,再通过JsonReader来读流,读流的方式也就意味着是顺序读的,所以即使它不是正确的json格式,也会一直读到错误为止。

JsonReader对手写的json解析语法非常严格,写错是非常头疼的事,另外建议把nodeName变为常量去做判断,不然以后改变量名得哭瞎。

当然,Stay肯定不会讲这么简单的东西,我们怎么跟HTTP框架结合在一起呢?这解析过程肯定也是耗时操作,我总不能先用框架把数据当文件下载下来,然后再开一个线程来解析吧。这才是最蛋疼的地方。

可惜原生Volley都不支持文件下载,这里我就拿自己的HTTP框架做演示了。

简单说下实现过程:

  1. 首先写个接口,比如JsonReaderable,里面定义一个方法readFromJson(JsonReader reader)
  2. 让你想要被反序列化的对象pojo实现这个接口,比如这样这里写图片描述
  3. 让框架先把数据当文件下载到SD卡
  4. 在callback之前再bindData,比如这样这里写图片描述这样就能将json数据自动反序列化成对象callback回去了。你只需要在每个对象pojo中实现readFromJson方法就好了。
  5. 如果是jsonarray怎么办,我们要返回一个ArrayList啊。比如这样这里写图片描述
  6. 一个好的框架相当的重要啊,我们再来看外层的调用这里写图片描述

  应该不用解释吧,都能看懂。

这种情况虽然比较少见,但在一些erp啊,sap项目中经常会遇到(别问Stay怎么知道)如果你也见过Android上500M的数据库,那这些心得你都能自己领悟到了。

现在我们在App中基本采取的都是分页,一般来说不需要用JsonReader,但如果Json数据超过10K以上,pojo的复杂度特别高,并且还有嵌套时,也应该考虑使用。

你也许会问,500M,即使用JsonReader读流生成对象了,内存也装不下呀。没事,你可以通过ormapping型数据库框架来存数据,比如说读200个对象存一次,清一次。或者你可以用接口回调的方式扔给外层处理,onPartialDataBinding(ArrayList list)

其实这个扩展其他第三方框架也没什么问题,只要思路有了,实现起来也就很容易了。

框架最好是根据App具体的需求以及使用场景来定制,仅会调用哪些开源lib,看不懂,改不了,这样只能让自己在技术路上越走越窄。

就写到这里,别问Stay要代码哈,只讲思维与解决方案。