对于目前的状况来说,移动终端的网络状况没有PC网络状况那么理想。在一个Android应用中,如果需要接收来自服务器的大容量数据,那么就不得不考虑客户的流量问题。本文根据笔者的一个项目实战经验出发,解决大容量数据的交互问题,解决数据大小会根据实际情况动态切换问题(服务器动态选择是否要压缩数据,客户端动态解析数据是否是被压缩的),还有数据交互的编码问题。

解决数据过大的问题,最直观的方法就是压缩数据。服务器将需要传递的数据先进行压缩,再发送给Android客户端,Android客户端接收到压缩的数据,对其解压,得到压缩前的数据。

如果规定Android客户端和服务器的交互数据必须是经过某种压缩算法后的数据,那么这种“规定”失去了视具体情况而定的灵活性。笔者拟将Http协议进行封装,将动态的选择传输的数据是否要经过压缩,客户端也能动态的识别,整理并获得服务器想要发送的数据。Android客户端向服务器请求某个方面的数据,这个数据也许是经过压缩后传递比较合适,又也许是将原生数据传递比较合适。也就是说,笔者想要设计一种协议,这种协议适用于传输数据的数据量会动态的切换,也许它会是一个小数据,也许它又会是一个数据量庞大的大数据(大数据需要经过压缩)。

可能说的比较抽象,那么我用实际情况解释一下。

我项目中的一个实际情况是这样的:这个项目是做一个Android基金客户端,Android客户端向服务器请求某一个基金的历史走势信息,由于我的Android客户端实现了本地缓存,这让传递数据的大小浮动非常大。如果本地缓存的历史走势信息的最新日期是5月5日,服务器的历史走势信息的最新日期是5月7日,那么服务器就像发送5月6日和5月7日这两天的走势信息,这个数据很小,不需要压缩(我使用的压缩算法,对于数据量过小的数据压缩并不理想,数据量过小的数据压缩后的数据会比压缩前的数据大)。然而,Android客户端也可能对于某个基金没有任何的缓存信息,那么服务器将发送的数据将是过去三四年间的历史走势信息,这个数据会有点大,就需要进行压缩后传递。那么客户端对于同一个请求得到的数据,如何判断它是压缩后的数据还是未曾压缩的数据呢?

笔者使用的解决方案是把传递数据的第一个字节作为标识字节,将标识这个数据是否被压缩了。也能标识传递数据的编码问题。Android对于接收到的数据(字节数组),先判断第一个字节的数据,就能根据它所代表的数据格式和编码信息进行相应的操作。说了那么多,也许不如看实际的代码理解的快。首先是压缩算法,这里笔者用到的是jdk自带的zip压缩算法。

  1. 1 package com.chenjun.utils.compress;
  2. 2
  3. 3 import java.io.ByteArrayInputStream;
  4. 4 import java.io.ByteArrayOutputStream;
  5. 5 import java.io.InputStream;
  6. 6 import java.io.OutputStream;
  7. 7 import java.util.zip.GZIPInputStream;
  8. 8 import java.util.zip.GZIPOutputStream;
  9. 9
  10. 10 public class Compress {
  11. 11 private static final int BUFFER_LENGTH = 400;
  12. 12
  13. 13
  14. 14 //压缩字节最小长度,小于这个长度的字节数组不适合压缩,压缩完会更大
  15. 15 public static final int BYTE_MIN_LENGTH = 50;
  16. 16
  17. 17
  18. 18 //字节数组是否压缩标志位
  19. 19 public static final byte FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY = 0;
  20. 20 public static final byte FLAG_GBK_STRING_COMPRESSED_BYTEARRAY = 1;
  21. 21 public static final byte FLAG_UTF8_STRING_COMPRESSED_BYTEARRAY = 2;
  22. 22 public static final byte FLAG_NO_UPDATE_INFO = 3;
  23. 23
  24. 24 /**
  25. 25 * 数据压缩
  26. 26 *
  27. 27 * @param is
  28. 28 * @param os
  29. 29 * @throws Exception
  30. 30 */
  31. 31 public static void compress(InputStream is, OutputStream os)
  32. 32 throws Exception {
  33. 33
  34. 34 GZIPOutputStream gos = new GZIPOutputStream(os);
  35. 35
  36. 36 int count;
  37. 37 byte data[] = new byte[BUFFER_LENGTH];
  38. 38 while ((count = is.read(data, 0, BUFFER_LENGTH)) != -1) {
  39. 39 gos.write(data, 0, count);
  40. 40 }
  41. 41
  42. 42 gos.finish();
  43. 43
  44. 44 gos.flush();
  45. 45 gos.close();
  46. 46 }
  47. 47
  48. 48
  49. 49 /**
  50. 50 * 数据解压缩
  51. 51 *
  52. 52 * @param is
  53. 53 * @param os
  54. 54 * @throws Exception
  55. 55 */
  56. 56 public static void decompress(InputStream is, OutputStream os)
  57. 57 throws Exception {
  58. 58
  59. 59 GZIPInputStream gis = new GZIPInputStream(is);
  60. 60
  61. 61 int count;
  62. 62 byte data[] = new byte[BUFFER_LENGTH];
  63. 63 while ((count = gis.read(data, 0, BUFFER_LENGTH)) != -1) {
  64. 64 os.write(data, 0, count);
  65. 65 }
  66. 66
  67. 67 gis.close();
  68. 68 }
  69. 69
  70. 70 /**
  71. 71 * 数据压缩
  72. 72 *
  73. 73 * @param data
  74. 74 * @return
  75. 75 * @throws Exception
  76. 76 */
  77. 77 public static byte[] byteCompress(byte[] data) throws Exception {
  78. 78 ByteArrayInputStream bais = new ByteArrayInputStream(data);
  79. 79 ByteArrayOutputStream baos = new ByteArrayOutputStream();
  80. 80
  81. 81 // 压缩
  82. 82 compress(bais, baos);
  83. 83
  84. 84 byte[] output = baos.toByteArray();
  85. 85
  86. 86 baos.flush();
  87. 87 baos.close();
  88. 88
  89. 89 bais.close();
  90. 90
  91. 91 return output;
  92. 92 }
  93. 93
  94. 94
  95. 95 /**
  96. 96 * 数据解压缩
  97. 97 *
  98. 98 * @param data
  99. 99 * @return
  100. 100 * @throws Exception
  101. 101 */
  102. 102 public static byte[] byteDecompress(byte[] data) throws Exception {
  103. 103 ByteArrayInputStream bais = new ByteArrayInputStream(data);
  104. 104 ByteArrayOutputStream baos = new ByteArrayOutputStream();
  105. 105
  106. 106 // 解压缩
  107. 107
  108. 108 decompress(bais, baos);
  109. 109
  110. 110 data = baos.toByteArray();
  111. 111
  112. 112 baos.flush();
  113. 113 baos.close();
  114. 114
  115. 115 bais.close();
  116. 116
  117. 117 return data;
  118. 118 }
  119. 119 }
  120. 复制代码

这里供外部调用的方法是byteCompress()和byteDecompress(),都将接收一个byte数组,byteCompress是数据压缩方法,将返回压缩后的数组数据,byteDecompress是数据解压方法,将返回解压后的byte数组数据。FLAG_GBK_STRING_COMPRESSED_BYTEARRAY表示服务器传递的数据是GBK编码的字符串经过压缩后的字节数组。其它的常量也能根据其名字来理解。(这里多说一句,最好将编码方式和是否压缩的标识位分开,比如将标识字节的前四个位定义成标识编码方式的位,将后面四个位标识为是否压缩或者其它信息的标识位,通过位的与或者或方式来判断标识位。笔者这里偷懒了,直接就这么写了。)

下面是处理传递数据的方法(判断是否要压缩)。我这里用要的是Struts 1框架,在Action里组织数据,并作相应的处理(压缩或者不压缩),并发送。

  1. public ActionForward execute(ActionMapping mapping, ActionForm form,
  2. HttpServletRequest request, HttpServletResponse response) {
  3. JjjzForm jjjzForm = (JjjzForm) form;
  4.  
  5. //基金净值历史走势信息
  6. ArrayList<Jjjz> jjjzs = null;
  7.  
  8. //得到基金净值历史走势的方法省略了
  9.  
  10. Gson gson = new Gson();
  11. String jsonStr = gson.toJson(jjjzs, jjjzs.getClass());
  12.  
  13. byte[] resultOriginalByte = jsonStr.getBytes();
  14.  
  15. //组织最后返回数据的缓冲字节数组
  16. ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream();
  17. OutputStream os = null;
  18.  
  19. try {
  20.  
  21. os = response.getOutputStream();
  22. //如果要返回的结果字节数组小于50位,不将压缩
  23. if(resultOriginalByte.length < Compress.BYTE_MIN_LENGTH){
  24. byte flagByte = Compress.FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY;
  25. resultBuffer.write(flagByte);
  26. resultBuffer.write(resultOriginalByte);
  27. }
  28. else{
  29. byte flagByte = Compress.FLAG_GBK_STRING_COMPRESSED_BYTEARRAY;
  30. resultBuffer.write(flagByte);
  31. resultBuffer.write(Compress.byteCompress(resultOriginalByte));
  32. }
  33. resultBuffer.flush();
  34. resultBuffer.close();
  35.  
  36. //将最后组织后的字节数组发送给客户端
  37. os.write(resultBuffer.toByteArray());
  38. } catch (IOException e) {
  39. // TODO Auto-generated catch block
  40. e.printStackTrace();
  41. } catch (Exception e) {
  42. // TODO Auto-generated catch block
  43. e.printStackTrace();
  44. }
  45. finally{
  46. try {
  47. os.close();
  48. } catch (IOException e) {
  49. // TODO Auto-generated catch block
  50. e.printStackTrace();
  51. }
  52. }
  53. return null;
  54. }
  55. 复制代码

这里我预发送的数据是一个Json格式的字符串(GBK编码),将判断这个字符串的长度(判断是否适合压缩)。如果适合压缩,就将缓冲字节数组(ByteArrayOutputStream resultBuffer)的第一个字节填充FLAG_GBK_STRING_COMPRESSED_BYTEARRAY,再将Json字符串的字节数组压缩,并存入数据缓冲字节数组,最后向输出流写入缓冲字节数组,关闭流。如果不适合压缩,将发送的数据的第一个字节填充为FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY,再将Json字符串的字节数组直接存入数据缓冲字节数组,写入输出流,关闭流。

最后就是Android客户端的解析了,将上述的Compress压缩辅助类拷贝到Android项目中就行。下面是Http请求后得到的字节数组数据做解析工作。(Android客户端如何使用Http向服务器请求数据请参考我前面的一篇博客)。

  1. byte[] receivedByte = EntityUtils.toByteArray(httpResponse.getEntity());
  2.  
  3. String result = null;
  4.  
  5. //判断接收到的字节数组是否是压缩过的
  6. if (receivedByte[0] == Compress.FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY) {
  7. result = new String(receivedByte, 1, receivedByte.length - 1, EXCHANGE_ENCODING);
  8. }
  9.  
  10. else if (receivedByte[0] == Compress.FLAG_GBK_STRING_COMPRESSED_BYTEARRAY) {
  11.  
  12. byte[] compressedByte = new byte[receivedByte.length - 1];
  13.  
  14. for (int i = 0; i < compressedByte.length; i++) {
  15. compressedByte[i] = receivedByte[i + 1];
  16. }
  17. byte[] resultByte = Compress.byteDecompress(compressedByte);
  18. result = new String(resultByte, EXCHANGE_ENCODING);
  19. }

这里最后得到的result就是服务器实际要发送的内容。

缺陷反思:任何设计都是有缺陷的。我这样做已经将Http协议做了进一层封装。Http的数据部分的第一个字节并不是实际数据,而是标识字节。这样,降低了这个接口的可重用性。统一发送Json字符串的Action能被网页(Ajax)或者其他客户端使用,经过封装压缩之后,只有能识别这个封装(就是能进行解析)的客户端能使用这个接口。网页(Ajax)就不能解析,那么这个Action就不能被Ajax使用。

具体开发过程中要视具体情况而定,如果数据量小的话我还是建议使用标准的Http协议,也就是说直接发送字符串,不做任何的压缩和封装。如果数据量实在过于大的话,建议使用我上述的方法。

有博友问,对于Android应用来说,什么样的数据才算是大数据。我想这个大数据的界限并不是固定的,并不是说10k以上,或者100k以上就算是大数据,这个界限是由许多方面的利弊来衡量的。首先我要说,我设计的这个协议是适用于大数据和小数据动态切换的情况。对于大小数据界限的划定,交给开发人员去衡量利弊。这个衡量标准我想应该包括以下几部分内容:

第一,压缩算法的有效临界点。只有要压缩的数据大于这个点,压缩后的数据才会更小,反之,压缩后的数据会更加的大。我使用的zip算法这个点应该是50字节左右,因此,在我应用中,将大数据定义成50字节以上的数据。

第二:压缩和解压的开销。服务器要压缩数据,客户端要解压数据,这个都是需要CPU开销的,特别是服务器,如果请求量大的话,需要为每一个响应数据进行压缩,势必降低服务器的性能。我们可以设想这样的一种情况,原生数据只有50字节,压缩完会有40字节,那么我们就要思考是否有必要来消耗CPU来为我们这区区的10个字节来压缩呢?

综上,虽然这个协议适合大小数据动态切换的数据传输,但是合理的选择大数据和小数据的分割点(定义多少大的数据要压缩,定义多少以下的数据不需要压缩)是需要好好权衡的。

http://www.cnblogs.com/answer1991/archive/2012/05/07/2487052.html

转载 解决Android与服务器交互大容量数据问题的更多相关文章

  1. 解决Android与服务器交互大容量数据问题

    对于目前的状况来说,移动终端的网络状况没有PC网络状况那么理想.在一个Android应用中,如果需要接收来自服务器的大容量数据,那么就不得不考虑客户的流量问题.本文根据笔者的一个项目实战经验出发,解决 ...

  2. 从高处理解android与服务器交互(看懂了做开发就会非常的容易)

    今天帮一个朋友改一个bug 他可以算是初学者吧 .我给他看了看代码,从代码和跟他聊天能明显的发现他对客户端与服务器交互 基本 不是很了解.所以我花了更多时间去给他讲客户端与服务器的关系.我觉得从这个高 ...

  3. android 从服务器获取新闻数据并显示在客户端

    新闻客户端案例 第一次进入新闻客户端需要请求服务器获取新闻数据,做listview的展示, 为了第二次再次打开新闻客户端时能快速显示新闻,需要将数据缓存到数据库中,下次打开可以直接去数据库中获取新闻直 ...

  4. [转载]解决flash与js交互、flash跨域交互、flash跨域提交

    http://blog.csdn.net/andyxm/article/details/5219919 我们引用本地flash,实现flash与js双向交互. function thisMovie(m ...

  5. android与服务器交互总结(json,post,xUtils,Volley)

    http://www.23code.com/tu-biao-chart/ 从无到有,从来没有接触过Json,以及与服务器的交互.然后慢慢的熟悉,了解了一点.把我学到的东西简单的做个总结,也做个记录,万 ...

  6. [转载]解决Android studio新建项目慢的问题

    原文地址为:https://blog.csdn.net/easion_zms/article/details/73181402 Android Studio 好处很多,但是当从github上或者导入其 ...

  7. (转载)Android之三种网络请求解析数据(最佳案例)

    [置顶] Android之三种网络请求解析数据(最佳案例) 2016-07-25 18:02 4725人阅读 评论(0) 收藏 举报  分类: Gson.Gson解析(1)  版权声明:本文为博主原创 ...

  8. Android和FTP服务器交互,上传下载文件(实例demo)

    今天同学说他备份了联系人的数据放在一个文件里,想把它存到服务器上,以便之后可以进行下载恢复..于是帮他写了个上传,下载文件的demo 主要是 跟FTP服务器打交道-因为这个东东有免费的可以身亲哈 1. ...

  9. 使用基于Android网络通信的OkHttp库实现Get和Post方式简单操作服务器JSON格式数据

     目录 前言 1 Get方式和Post方式接口说明 2 OkHttp库简单介绍及环境配置 3 具体实现 前言 本文具体实现思路和大部分代码参考自<第一行代码>第2版,作者:郭霖:但是文中讲 ...

随机推荐

  1. 查看文档的后几行命令:tail

    假如有一个文件test.txt,内容如下: [root@lee ~]# cat test.txt 这是第1行 这是第2行 这是第3行 这是第4行 这是第5行 这是第6行 这是第7行 这是第8行 这是第 ...

  2. 在UIWebView中设置cookie

     本文转载至 http://blog.csdn.net/chengyakun11/article/details/8863878 项目中,需要在打开3g网页时,通过cookie传递一些信息. 实现代码 ...

  3. FineUIPro中如何支持多语言(全局资源文件和本地资源文件)

    一个客户在邮件中问到了FineUIPro的多语言实现问题,其实 FineUIPro 并没有对此做特殊处理,因此直接使用 ASP.NET 原生支持的资源文件就能实现. 下面我们就以FineUIPro的空 ...

  4. Gaby Ivanushka(快排)

    Gaby Ivanushka Once upon a time there lived a tsar that has a daughter — Beautiful Vasilisa. There w ...

  5. vscode 和 atom 全局安装和配置 eslint 像 webstorm 等 ide 一样使用 standard标准 来检查项目

    首先你要安装了 nodejs ,然后在终端命令行输入下面的这堆 npm install eslint eslint-plugin-standard eslint-config-standard esl ...

  6. 九度OJ 1283:第一个只出现一次的字符 (计数)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1808 解决:997 题目描述: 在一个字符串(1<=字符串长度<=10000,全部由大写字母组成)中找到第一个只出现一次的字符 ...

  7. 使用weka训练一个分类器

    1 训练集数据 1.1 csv格式 5.1,3.5,1.4,0.2,Iris-setosa 4.9,3.0,1.4,0.2,Iris-setosa 4.7,3.2,1.3,0.2,Iris-setos ...

  8. The Princess and the Pea,摘自iOS应用Snow White and more stories

    Once upon a time there was a prince who wanted to marry a real princess.从前,有个王子想和真正的公主结婚. He looked ...

  9. 牛客小白月赛1 E 圆与三角形 【数学】

    题目链接 https://www.nowcoder.com/acm/contest/85/E 思路 在三角形中,这一串东西的值恒为1 又 SIN A 的最大值 为1 所以 这串式子的最大值 就是 r ...

  10. monokai-background

    foreground-color:f8f8f2 background-color:272822