`
ivoter
  • 浏览: 90539 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

 
阅读更多

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

 

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

 

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

 

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

 

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

 

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

 

  

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

 

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

 

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

 

 

复制代码
     public ActionForward execute(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response) {
        JjjzForm jjjzForm = (JjjzForm) form;
        

        //基金净值历史走势信息
        ArrayList<Jjjz> jjjzs = null;
        
        //得到基金净值历史走势的方法省略了
        
        Gson gson = new Gson();
        String jsonStr = gson.toJson(jjjzs, jjjzs.getClass());
        
        byte[] resultOriginalByte = jsonStr.getBytes();
                
        //组织最后返回数据的缓冲字节数组
        ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream();
        OutputStream os = null;
        
        
        try {
            
            os = response.getOutputStream();
            //如果要返回的结果字节数组小于50位,不将压缩
            if(resultOriginalByte.length < Compress.BYTE_MIN_LENGTH){
                byte flagByte = Compress.FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY;
                resultBuffer.write(flagByte);
                resultBuffer.write(resultOriginalByte);
            }
            else{
                byte flagByte = Compress.FLAG_GBK_STRING_COMPRESSED_BYTEARRAY;
                resultBuffer.write(flagByte);
                resultBuffer.write(Compress.byteCompress(resultOriginalByte));
            }
            resultBuffer.flush();
            resultBuffer.close();
                       
            //将最后组织后的字节数组发送给客户端
            os.write(resultBuffer.toByteArray());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        finally{
            try {
                os.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return null;
    }
复制代码

 

 

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

 

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

 

 

复制代码
        byte[] receivedByte = EntityUtils.toByteArray(httpResponse.getEntity());

        String result = null;
        
        //判断接收到的字节数组是否是压缩过的
        if (receivedByte[0] == Compress.FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY) {
            result = new String(receivedByte, 1, receivedByte.length - 1, EXCHANGE_ENCODING);
        } 
        
        else if (receivedByte[0] == Compress.FLAG_GBK_STRING_COMPRESSED_BYTEARRAY) {

            byte[] compressedByte = new byte[receivedByte.length - 1];

            for (int i = 0; i < compressedByte.length; i++) {
                compressedByte[i] = receivedByte[i + 1];
            }
            byte[] resultByte = Compress.byteDecompress(compressedByte);
            result = new String(resultByte, EXCHANGE_ENCODING);
        }
复制代码

 

 

   这里最后得到的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

分享到:
评论
1 楼 qq_975251849 2015-10-20  
我想问下,那安卓端上传数据,JAVA如何解析呢?

相关推荐

    Android客户端与服务器端的json数据交互(内含大量知识点)

    Android客户端与服务器端的json数据交互(内含大量知识点),包含文件的上传,文件浏览器等。

    Google Android SDK开发范例大全(PDF高清完整版1)(4-1)

    5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩多少...

    Google Android SDK开发范例大全(PDF完整版4)(4-4)

    5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩多少...

    Google Android SDK开发范例大全(PDF高清完整版3)(4-3)

    5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩多少...

    《Google Android SDK开发范例大全(第3版)》.pdf

    主要以范例集的方式来讲述android的知识点,详细介绍了开发android的人机交互界面、android常用的开发控件、android手机收发短信等通信服务、开发android手机的自动服务功能和娱乐多媒体功能以及整合android与google...

    Google Android SDK开发范例大全的目录

    5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 –p254 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    目前市场业务中在产品以及其他项目的认证和检测方面存在诸多不便,用户需要实地考察并频繁与检测单位沟通,填写繁琐的纸质检测报告、当面送递样品,对于检测环节中存在的问题难以及时交互并处理。市场上相应的检测...

    Google Android SDK开发范例大全(完整版附部分源码).pdf

    5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩多少...

    Google Android SDK开发范例大全(完整版)

    如果要构建一个较大的应用程序,或者构建需要为多个活动或应用程序提供数据的应用程序,那么可以使用内容提供程序实现数据访问。 广播接收器 Android 应用程序可用于处理一个数据元素,或者对一个事件(例如接收文本...

    Google Android SDK开发范例大全(第3版)part2

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK开发范例大全(第3版) 1/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK开发范例大全(第3版) 4/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK开发范例大全(第3版) 3/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK开发范例大全(第3版) 5/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK 开发范例大全01

    5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 –p254 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩...

    Google Android SDK 开发范例大全02

    5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 –p254 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩...

    PoloMeeting局域网视频会议完整安装包(MCU服务器+客户端)

    操作简洁方便,服务器安装不需要任何配置,运行后就可搭建专业级的视频会议,企业无需投入高昂的成本,就能轻松拥有高质量、高稳定、大容量、易部署的视频会议系统,满足音视频通信、数据交互、远程共享和协助等需求...

Global site tag (gtag.js) - Google Analytics