- 浏览: 157370 次
- 性别:
- 来自: 深圳
最新评论
-
dawei1980:
请问,解压密码是多少?
Android本地APP集成Mui框架 -
牧羊之人:
Android本地APP集成Mui框架 -
还有也许:
貌似懂了一点。如果onCreate方法中创建了一个db,然后在 ...
Android线程模式(handler,thread,looper) -
chenshijun0101:
header里面怎么更改他的样式呢?急求
android Preference相关样式修改 -
qlraishui:
good
Binder机制分析【三】-service绑定Binder
对于目前的状况来说,移动终端的网络状况没有PC网络状况那么理想。在一个Android应用中,如果需要接收来自服务器的大容量数据,那么就不得不考虑客户的流量问题。本文根据笔者的一个项目实战经验出发,解决大容量数据的交互问题,解决数据大小会根据实际情况动态切换问题(服务器动态选择是否要压缩数据,客户端动态解析数据是否是被压缩的),还有数据交互的编码问题。
解决数据过大的问题,最直观的方法就是压缩数据。服务器将需要传递的数据先进行压缩,再发送给Android客户端,Android客户端接收到压缩的数据,对其解压,得到压缩前的数据。
如果规定Android客户端和服务器的交互数据必须是经过某种压缩算法后的数据,那么这种“规定”失去了视具体情况而定的灵活性。笔者拟将Http协议进行封装,将动态的选择传输的数据是否要经过压缩,客户端也能动态的识别,整理并获得服务器想要发送的数据。Android客户端向服务器请求某个方面的数据,这个数据也许是经过压缩后传递比较合适,又也许是将原生数据传递比较合适。也就是说,笔者想要设计一种协议,这种协议适用于传输数据的数据量会动态的切换,也许它会是一个小数据,也许它又会是一个数据量庞大的大数据(大数据需要经过压缩)。
可能说的比较抽象,那么我用实际情况解释一下。
我项目中的一个实际情况是这样的:这个项目是做一个Android基金客户端,Android客户端向服务器请求某一个基金的历史走势信息,由于我的Android客户端实现了本地缓存,这让传递数据的大小浮动非常大。如果本地缓存的历史走势信息的最新日期是5月5日,服务器的历史走势信息的最新日期是5月7日,那么服务器就像发送5月6日和5月7日这两天的走势信息,这个数据很小,不需要压缩(我使用的压缩算法,对于数据量过小的数据压缩并不理想,数据量过小的数据压缩后的数据会比压缩前的数据大)。然而,Android客户端也可能对于某个基金没有任何的缓存信息,那么服务器将发送的数据将是过去三四年间的历史走势信息,这个数据会有点大,就需要进行压缩后传递。那么客户端对于同一个请求得到的数据,如何判断它是压缩后的数据还是未曾压缩的数据呢?
笔者使用的解决方案是把传递数据的第一个字节作为标识字节,将标识这个数据是否被压缩了。也能标识传递数据的编码问题。Android对于接收到的数据(字节数组),先判断第一个字节的数据,就能根据它所代表的数据格式和编码信息进行相应的操作。说了那么多,也许不如看实际的代码理解的快。首先是压缩算法,这里笔者用到的是jdk自带的zip压缩算法。
这里供外部调用的方法是byteCompress()和byteDecompress(),都将接收一个byte数组,byteCompress是数据压缩方法,将返回压缩后的数组数据,byteDecompress是数据解压方法,将返回解压后的byte数组数据。FLAG_GBK_STRING_COMPRESSED_BYTEARRAY表示服务器传递的数据是GBK编码的字符串经过压缩后的字节数组。其它的常量也能根据其名字来理解。(这里多说一句,最好将编码方式和是否压缩的标识位分开,比如将标识字节的前四个位定义成标识编码方式的位,将后面四个位标识为是否压缩或者其它信息的标识位,通过位的与或者或方式来判断标识位。笔者这里偷懒了,直接就这么写了。)
下面是处理传递数据的方法(判断是否要压缩)。我这里用要的是Struts 1框架,在Action里组织数据,并作相应的处理(压缩或者不压缩),并发送。
这里我预发送的数据是一个Json格式的字符串(GBK编码),将判断这个字符串的长度(判断是否适合压缩)。如果适合压缩,就将缓冲字节数组(ByteArrayOutputStream resultBuffer)的第一个字节填充FLAG_GBK_STRING_COMPRESSED_BYTEARRAY,再将Json字符串的字节数组压缩,并存入数据缓冲字节数组,最后向输出流写入缓冲字节数组,关闭流。如果不适合压缩,将发送的数据的第一个字节填充为FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY,再将Json字符串的字节数组直接存入数据缓冲字节数组,写入输出流,关闭流。
最后就是Android客户端的解析了,将上述的Compress压缩辅助类拷贝到Android项目中就行。下面是Http请求后得到的字节数组数据做解析工作。(Android客户端如何使用Http向服务器请求数据请参考我前面的一篇博客)。
这里最后得到的result就是服务器实际要发送的内容。
缺陷反思:任何设计都是有缺陷的。我这样做已经将Http协议做了进一层封装。Http的数据部分的第一个字节并不是实际数据,而是标识字节。这样,降低了这个接口的可重用性。统一发送Json字符串的Action能被网页(Ajax)或者其他客户端使用,经过封装压缩之后,只有能识别这个封装(就是能进行解析)的客户端能使用这个接口。网页(Ajax)就不能解析,那么这个Action就不能被Ajax使用。
具体开发过程中要视具体情况而定,如果数据量小的话我还是建议使用标准的Http协议,也就是说直接发送字符串,不做任何的压缩和封装。如果数据量实在过于大的话,建议使用我上述的方法。
有博友问,对于Android应用来说,什么样的数据才算是大数据。我想这个大数据的界限并不是固定的,并不是说10k以上,或者100k以上就算是大数据,这个界限是由许多方面的利弊来衡量的。首先我要说,我设计的这个协议是适用于大数据和小数据动态切换的情况。对于大小数据界限的划定,交给开发人员去衡量利弊。这个衡量标准我想应该包括以下几部分内容:
第一,压缩算法的有效临界点。只有要压缩的数据大于这个点,压缩后的数据才会更小,反之,压缩后的数据会更加的大。我使用的zip算法这个点应该是50字节左右,因此,在我应用中,将大数据定义成50字节以上的数据。
第二:压缩和解压的开销。服务器要压缩数据,客户端要解压数据,这个都是需要CPU开销的,特别是服务器,如果请求量大的话,需要为每一个响应数据进行压缩,势必降低服务器的性能。我们可以设想这样的一种情况,原生数据只有50字节,压缩完会有40字节,那么我们就要思考是否有必要来消耗CPU来为我们这区区的10个字节来压缩呢?
综上,虽然这个协议适合大小数据动态切换的数据传输,但是合理的选择大数据和小数据的分割点(定义多少大的数据要压缩,定义多少以下的数据不需要压缩)是需要好好权衡的。
解决数据过大的问题,最直观的方法就是压缩数据。服务器将需要传递的数据先进行压缩,再发送给Android客户端,Android客户端接收到压缩的数据,对其解压,得到压缩前的数据。
如果规定Android客户端和服务器的交互数据必须是经过某种压缩算法后的数据,那么这种“规定”失去了视具体情况而定的灵活性。笔者拟将Http协议进行封装,将动态的选择传输的数据是否要经过压缩,客户端也能动态的识别,整理并获得服务器想要发送的数据。Android客户端向服务器请求某个方面的数据,这个数据也许是经过压缩后传递比较合适,又也许是将原生数据传递比较合适。也就是说,笔者想要设计一种协议,这种协议适用于传输数据的数据量会动态的切换,也许它会是一个小数据,也许它又会是一个数据量庞大的大数据(大数据需要经过压缩)。
可能说的比较抽象,那么我用实际情况解释一下。
我项目中的一个实际情况是这样的:这个项目是做一个Android基金客户端,Android客户端向服务器请求某一个基金的历史走势信息,由于我的Android客户端实现了本地缓存,这让传递数据的大小浮动非常大。如果本地缓存的历史走势信息的最新日期是5月5日,服务器的历史走势信息的最新日期是5月7日,那么服务器就像发送5月6日和5月7日这两天的走势信息,这个数据很小,不需要压缩(我使用的压缩算法,对于数据量过小的数据压缩并不理想,数据量过小的数据压缩后的数据会比压缩前的数据大)。然而,Android客户端也可能对于某个基金没有任何的缓存信息,那么服务器将发送的数据将是过去三四年间的历史走势信息,这个数据会有点大,就需要进行压缩后传递。那么客户端对于同一个请求得到的数据,如何判断它是压缩后的数据还是未曾压缩的数据呢?
笔者使用的解决方案是把传递数据的第一个字节作为标识字节,将标识这个数据是否被压缩了。也能标识传递数据的编码问题。Android对于接收到的数据(字节数组),先判断第一个字节的数据,就能根据它所代表的数据格式和编码信息进行相应的操作。说了那么多,也许不如看实际的代码理解的快。首先是压缩算法,这里笔者用到的是jdk自带的zip压缩算法。
package com.chenjun.utils.compress; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; public class Compress { private static final int BUFFER_LENGTH = 400; //压缩字节最小长度,小于这个长度的字节数组不适合压缩,压缩完会更大 public static final int BYTE_MIN_LENGTH = 50; //字节数组是否压缩标志位 public static final byte FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY = 0; public static final byte FLAG_GBK_STRING_COMPRESSED_BYTEARRAY = 1; public static final byte FLAG_UTF8_STRING_COMPRESSED_BYTEARRAY = 2; public static final byte FLAG_NO_UPDATE_INFO = 3; /** * 数据压缩 * * @param is * @param os * @throws Exception */ public static void compress(InputStream is, OutputStream os) throws Exception { GZIPOutputStream gos = new GZIPOutputStream(os); int count; byte data[] = new byte[BUFFER_LENGTH]; while ((count = is.read(data, 0, BUFFER_LENGTH)) != -1) { gos.write(data, 0, count); } gos.finish(); gos.flush(); gos.close(); } /** * 数据解压缩 * * @param is * @param os * @throws Exception */ public static void decompress(InputStream is, OutputStream os) throws Exception { GZIPInputStream gis = new GZIPInputStream(is); int count; byte data[] = new byte[BUFFER_LENGTH]; while ((count = gis.read(data, 0, BUFFER_LENGTH)) != -1) { os.write(data, 0, count); } gis.close(); } /** * 数据压缩 * * @param data * @return * @throws Exception */ public static byte[] byteCompress(byte[] data) throws Exception { ByteArrayInputStream bais = new ByteArrayInputStream(data); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 压缩 compress(bais, baos); byte[] output = baos.toByteArray(); baos.flush(); baos.close(); bais.close(); return output; } /** * 数据解压缩 * * @param data * @return * @throws Exception */ public static byte[] byteDecompress(byte[] data) throws Exception { ByteArrayInputStream bais = new ByteArrayInputStream(data); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 解压缩 decompress(bais, baos); data = baos.toByteArray(); baos.flush(); baos.close(); bais.close(); return data; } }
这里供外部调用的方法是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个字节来压缩呢?
综上,虽然这个协议适合大小数据动态切换的数据传输,但是合理的选择大数据和小数据的分割点(定义多少大的数据要压缩,定义多少以下的数据不需要压缩)是需要好好权衡的。
发表评论
-
判断图片是浅色还是深色
2020-03-04 13:23 793首先需要获取 WallpaperManager.FLAG_L ... -
如何将uri转成真实路径地址
2018-10-15 17:38 1205/** * 获取文件选择器选中的文 ... -
备用网址记录
2018-01-08 11:05 436各种开源下载 http://www.mvnjar.com/ ... -
android中touch事件,click事件,longclick事件分析
2016-08-03 15:51 1496针对屏幕上的一个View控件,Android如何区分应当触发o ... -
Android 快速开发系列 打造万能的ListView GridView 适配器
2016-06-27 17:21 679前往 http://blog.csdn.net/lmj6235 ... -
android中的Handler和AsyncTask如何防止内存泄露
2016-06-13 13:55 1639Handler泄露的关键点有两个: 1). 内部类 ... -
[转载]SharedPreferences 存储java对象,很实用
2016-04-14 16:36 1063public void putObject(String ke ... -
Android本地APP集成Mui框架
2016-01-26 14:41 31402.如何在安卓原生APP中 ... -
Android与设计模式浅谈
2015-04-27 10:42 1089Android作为新一代的操作系统,集合着Google ... -
从网页启动Activity
2015-03-24 11:28 1371正好Android SDK 给我们提供了解决方案,在网页中点击 ... -
[转]android shape的使用
2014-10-13 13:30 763shape用于设定形状,可以在selector,layout等 ... -
touch事件分发处理流程
2014-05-23 09:44 821Touch 事件发生时 Activity 的 dispatch ... -
修改标准GSensor相关,是重力感应游戏在平板都可以玩
2013-12-21 11:27 856为什么有些重力感应的游戏不能玩,有些可以玩,主要原因在于fra ... -
Android模拟按键
2013-10-14 14:27 2412如果想要实现类似iphone的悬浮框按钮,那就必须知道如何去模 ... -
android资源适配解析及资源适配优先级规则
2013-10-12 12:41 36771.sw的值是怎么计算得来 ... -
[转]隐藏虚拟按键(导航栏)的方法
2013-10-12 10:35 2901Controls for system UI visibili ... -
[转载]Android大图裁剪解决办法
2013-04-25 14:29 2140cropimage 可以调用手机自带的com.android ... -
反锯齿办法
2012-12-28 14:14 956在Android中,目前,我知道有两种出现锯齿的情况。 ① ... -
android线程的那些事
2012-11-17 15:36 2320有些时候Thread里面更新UI是可以成功的。 比如在Acti ... -
图像缩放和旋转
2012-11-16 11:20 1199在绘制bitmap时,都会涉及一个参数矩阵Matrix,Mat ...
相关推荐
Android客户端与服务器端的json数据交互(内含大量知识点),包含文件的上传,文件浏览器等。
当Android设备作为USB设备连接到PC时,它会呈现为一个特定的USB类,如MIDI、HID或大容量存储设备,这取决于设备配置和安装的驱动。 其次,Android APP与PC通信通常使用Android Debug Bridge (ADB),这是一个强大的...
例子主要包括SocketAsyncEventArgs通讯封装、服务端实现日志查看、SCOKET列表、上传、下载、远程文件流、吞吐量协议,用于测试SocketAsyncEventArgs的性能和压力,最大连接数支持65535个长连接,最高命令交互速度...
而本地文件存储则适用于大容量或非结构化的数据。理解并熟练掌握这些存储机制,对提升Android应用的用户体验至关重要。在实际开发中,开发者应根据应用的特点和性能需求选择最合适的数据存储方案。
例如,用户偏好可以使用SharedPreferences,结构化的配置数据可以存储在SQLite中,而大容量的媒体文件则可存为本地文件,网络数据则通过HTTP请求获取。在DataStoreDemo项目中,可以找到这些存储方式的实例代码,帮助...
5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩多少...
5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩多少...
【Android蓝牙5.0与Android L BLE Peripheral】 随着科技的发展,蓝牙技术也在不断进步,蓝牙5.0(Bluetooth 5)作为一个重要的里程碑,为无线通信带来了显著的改进。相较于之前的蓝牙版本,蓝牙5.0提供了更高的...
文件存储适用于大容量的非结构化数据。 **异步处理与多线程** 由于Android应用的主线程不能执行耗时操作,开发者通常使用IntentService、AsyncTask、Handler、Thread或最近的Coroutines(针对Kotlin)来处理后台...
(3) 通过统一服务器通信,确保客户端与服务器之间的流畅交互,能应对大量数据传输的挑战。 (4) 该系统适用于各种比赛场景,提供了定制化的投票功能,满足不同赛事的需求。 综上所述,基于Android平台的评委打分系统...
5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩多少...
- **文件存储**:直接操作文件系统,适用于大容量数据或非结构化数据。 4. **网络编程** - **HTTP请求**:使用HttpURLConnection或第三方库如OkHttp进行网络通信。 - **JSON解析**:Gson、Jackson或org.json库...
通过查看源码,你可以学习到具体实现这些功能的细节,如如何定义和注册监听器,如何与服务器交互,以及如何使用Map进行图片缓存。 总之,这个demo提供了一个实用的案例,教你如何在Android应用中实现高效且流畅的...
SharedPreferences适合保存简单的键值对,而文件系统适用于大容量文本或二进制数据。 Android应用的权限管理随着版本更新不断变化,从早期的运行时权限到Android 10的分区存储,开发者需要了解如何适配不同版本,...
在Android中,缓存通常用于存储网络数据,避免每次启动应用或用户请求时都重新从服务器加载。 2. **Android缓存类型** - **内存缓存**: 数据存储在应用程序的内存中,访问速度快,但会随着应用关闭而丢失。 - **...
在物联网监控系统中,WIFI可以提供更快的数据传输速度,适合于大容量数据的传输。 【J2EE服务器】 Java 2 Enterprise Edition(J2EE)是一个用于开发和部署企业级应用程序的平台。在这个系统中,J2EE服务器可能用于...
3. 文件存储:直接读写文件,适用于大容量数据和非结构化数据。 七、多媒体与图形处理 1. Camera API:用于访问和控制设备摄像头。 2. MediaPlayer:播放音频和视频。 3. OpenGL ES:图形处理库,用于游戏和高性能...
5.24 取得电信网络与手机相关信息——TelephonyManager与android.provider.Settings.System的应用 第6章 手机自动服务纪实 –p254 6.1 您有一条短信popup提醒——常驻BroadcastReceiver的应用 6.2 手机电池计量还剩...