举个简单的例子,我们要显示一个背包中的道具,需要道具数据库保存 ID、类型 ID、图片地址、名称、大类别、子类别、质量、说明、是否出售、是否锁定、道具创建时间、道具持续时间、使用效果定义字符串、可使用等级、堆叠数量、最大堆叠数、出售单价等等,如果按传统做法,就是返回一个二维数组,将所有信息加载进来,然后直接填充列表,依次填写各项内容。这个做法很很简单,初级程序员就能完成,但代价是,传输数据量会非常大。
下面介绍的内容就是为了缩小数据量,较少传输时间及降低服务器压力。
传输数据格式
现在常见的传输数据格式有三种:XML,JSON,AMF。
XML 是通用数据格式,在保存数据方面有明显优势,在传输数据方面,对 Flash 而言,数据传输的双方都是确定的,并不需要通用,所以 XML 这点优势不突出。因此,实际可用的就是体积较小的 JSON 和 AMF 这两种格式。前者可以用 Firebug 直接查看数据,后者目前也有很多方法可以查看数据了。同样数据内容,JSON 即使压缩也应该比 AMF 的体积大,但 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)
文本文件压缩方法
上面提到的 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,直接发送到另一边,再用同样的方法将数据取出来。这样做数据量最少,但是二进制协议的开发成本相对比较高,也容易出错,因此只在对即时通讯要求较高的情况下使用。
评论