Flash务实主义(八)——减少数据传输量(转)
http://www.infoq.com/cn/news/2011/06/flash-pragmatism-8-data-transfer
举个简单的例子,我们要显示一个背包中的道具,需要道具数据库保存ID、类型ID、图片地址、名称、大类别、子类别、质量、说明、是否出售、是否锁定、道具创建时间、道具持续时间、使用效果定义字符串、可使用等级、堆叠数量、最大堆叠数、出售单价等等,如果按传统做法,就是返回一个二维数组,将所有信息加载进来,然后直接填充列表,依次填写各项内容。这个做法很很简单,初级程序员就能完成,但代价是,传输数据量会非常大。
下面介绍的内容就是为了缩小数据量,较少传输时间及降低服务器压力。
(这种做法将所有数据都从服务器端传输过来)
传输数据格式
现在常见的传输数据格式有三种:XML,JSON,AMF。
XML是通用数据格式,在保存数据方面有明显优势,在传输数据方面,对Flash而言,数据传输的双方都是确定的,并不需要通用,所以XML这点优势不突出。因此,实际可用的就是体积较小的JSON和AMF这两种格式。前者可以用Firebug直接查看数据,后者目前也有很多方法可以查看数据了。同样数据内容,JSON即使压缩也应该比AMF的体积大,但AMF由于协议负责会有固定额外体积,如果只传一个数据JSON反而会比较小。
一般情况下,选AMF更好,不过即使用JSON应该也不会有太多性能问题,旧项目无需强求。
(旧项目使用amf或json均可)
存储数据格式
在游戏中通常可以看到,道具栏可能会有同种道具多次出现。这时候重复加载数据是一种浪费。通常一款游戏道具数量不过数百个,即使包含大量描述信息,全部用XML保存,最多也就几百KB(千字节),用G-ZIP(即ByteArray的compress()方法)压缩后可达几十KB,这不过相当于一张大图而已,与整体资源体积相比只是个零头。所以即使作为资源一次加载也是没问题的。
这样我们可以将所有道具固定信息保存在一个文件中,开始便加载,在加载完后取出所有数据。这些数据是道具定义,包含类型ID、图片地址、名称、大类别、子类别、质量、说明、是否出售、是否锁定、使用效果定义字符串、可使用等级、最大堆叠数量、出售单价。这些信息的保存形式可以是XML、JSON、AMF,具体形式无所谓。服务端只需要返回数据库中保存的唯一标识ID、类型ID、堆叠数量这三个值就可以,然后通过类型ID找到道具定义中其他需要的数据。
显然这笔之前传输的数据量要少很多。
三种存储形式各有特点:XML优点在于格式通用,很多软件都能修改及输出,且容易被Flash解析;JSONG也容易被Flash解析,体积较XML小,但支持的软件少,需要自己写编辑工具或转换工具;AMF体积更小,但因为不是文本形式,不用专门工具无法修改,是最依赖工具的格式。
由于这部分分数据体积不大,不需要过于纠结保存的格式。个人比较推荐XML格式,这样完全不依赖特殊工具,数据库也容易自动生成,是目前网页游戏及客户端游戏广发使用的。(星际争霸2就包含了大量XML)
(固定信息即游戏中的静态数据,它不会变化,一开始加载这个xml,保存到内存中,需要时根据道具ID从里面取。)
文本文件压缩方法
上面提到的G-ZIP压缩指的是ByteArray的compress()方法,将文本写入ByteArray,执行compress()方法保存成文档,读取时获得ByteArray,执行uncompress()获得文本。
一般一个有格式描述的XML文件可以压缩到1/10,所以文本文件体积不是问题。可以用这个工具来实现压缩及解压文本文件:
http://code.google.com/p/ghostcat/source/browse/trunk/tools/GhostCatTool.swf
这里有个小技巧,当ByteArray未被压缩时,执行uncompress()会出错但不会改变ByteArray的内容,因此可以用try捕获uncompress()方法引发的错误,无论成功与否都获得数据。这样如果加载未压缩文件也可以正常使用,在调试时使用未压缩文件也方便修改,发布时再生成压缩版本以减小体积。
仅更新修改
当你使用了一个道具后,该如何更新物品列表呢?发出使用请求后重新加载道具列表?这是最简单也最浪费的做法,原因应该无需说明。
这就要求我们应用模型和视图分离的思想。获得整个道具列表后将数据保存在他处,即使关闭面板数据也还存在。之后直接操作这个保存的数据。使用道具就减少道具堆叠数量,数量为0时就删除这个道具,同时发送给服务端使用道具请求。不重复获取整个列表,而是与服务端一直同时修改列表。虽然出现Bug时会造成前后端不一致,但这种做法是值得推荐的。
获得道具也是同样原理,应该获取新道具信息,并将数据添加到道具模型中。每次都重新加载列表确实很简单很稳定,但做法过于粗糙和暴力。
减少键值数据
当传送大量数据(诸如500个好友)时,即使只传输必要数据,数据量也可能会很大。当然这要看采用哪种传输数据格式,不管用哪种格式都会有数据冗余——那就是对象的键。当传输的数据是数组时,数组里是同样类型的对象,但即使都是同样类型,序列化时依然要在每个对象里重复传递对象的每个键字符串。如果有500条数据,这些键字符串就会重复500次。
把对象转换成数组,就可以不传递这个键值,数据体积将会大大减小,也可以单独将键值传过去,方便完成传输后重新组合成对象。
(不是很明白,到底数组能不能解决这个问题?)
二进制协议
最彻底的解决方案是采用规定的二进制协议来传输数据。将数据根据特定的格式和顺序依次存入ByteArray中,定长数据直接保存,而不定长数据则先保存长度再保存数据(数组也是不定长数据的一种),这样生成BytesArray,直接发送到另一边,再用同样的方法将数据取出来。这样做数据量最少,但是二进制协议的开发成本相对比较高,也容易出错,因此只在对即时通讯要求较高的情况下使用。