加载形式
FLASH 加载文件有两种常规方式:一种是 URLLoader,可以加载文本、二进制数据或 URL 编码变量形式的数据,然后转换成简单的文本形式或值对字符串形式;一种是 Loader,可以将加载的图像文件转换成 BitmapData,也可以解析 SWF 文件。(Socket, LocalConnection 这些非常用类不在讨论范围)
加载的资源应被统一管理以方便调用和重用,管理方式一般有两种:一种是将资源全部打包进 SWF;一种是加载分散资源通过配置进行管理。我将对这两种管理方式进行介绍。
资源打包成 SWF
在编辑 FLA 文件时,我们可以导入各种图片,并为其设置链接名。除手工逐个操作外,我们可以借助 JSFL 进行自动化处理(请参考 http://bbs.9ria.com/thread-31730-1-1.html )。
由此生成 SWF 后,再用 Loader 加载。这时候我们需要的不再是舞台上的内容,而是 SWF 应用域里包含资源的类。可以用 Loader 的 contentLoaderInfo.applicationDomain.getDefinition() 方法来获得这个类,并实例化。如果在执行加载方法时第二个参数 LoaderContext 设置成了 ApplicationDomain.currentDomain,使得被加载的 SWF 与主 SWF 共享同一应用域,那么直接使用 getDefinitionByName () 方法也可以获得这个类。
通过 new 的方式就可以实例化获得的 BitmapData 或 MovieClip 类,然后使用。因此项目中要想得到一个资源,只要知道它的链接名即可。链接名是自行设定的有意义的名字,完全可以当做资源的唯一 ID。
需要注意的是,new 的过程就是图片解压缩的过程。处于 Class 状态时,图片占用的内存和 SWF 文件中这个图片占用的磁盘空间一致,而一旦通过 new 解压成无压缩的 BitmapData 后,占用的内存会急剧增加。不管是 PNG、JPG,还是矢量动画,new 之后的体积都会比原来大得多,因此不要随便将资源实例化后暂存。这个实例化过程理所当然是比较费时的,可能会出现卡的现象,但预先实例化,内存占用上是有很大区别的。
此外,如果选择设置 LoaderContext 使得全部资源加载到同一个域的话,有冲突的链接名是以先来先到的原则处理,即如果两个资源链接名相同,以先加载的对象为准。
打包成 SWF 有一个优点,SWF 可以让 JPEG 支持透明通道。一般来说,JPEG 压缩率高而不支持透明通道,PNG 压缩率低支持透明通道。将 PNG 导入 FLA 然后设置成 JPEG 压缩后,就能在压缩的同时保留透明通道,可以让支持透明通道的图片体积大大减小。
打包成 SWF 后,加载快且易于管理,是推荐方式。但这种做法限定你必须一次性加载所有资源,不能按需加载,有一定的局限性。比较适合加载 UI 皮肤,以及需要立即显示的图标等等。
还有一点需要注意:SWF 舞台上的内容,即使不显示出来也会消耗资源,因此请务必保证在发布时舞台为空。
资源分散加载
如果文件要按需加载,或者不希望用 SWF 打包增加维护成本,或者有大量文本以致于不能用 FLA 导入,那我们只能逐个文件加载。
因为资源可能处于不同目录,命名也不规范,也会有扩展名,这样的路径在代码中作为 ID 存在是不合适的。所以一般都会有一个文本配置文件,将这些文件路径和一个名称对应起来,并提供给模块加载。加载完成后则是通过这个名称来获取资源。
不要直接用 Loader 加载文件
不同文件有不同的加载方式,文本和二进制文件只能通过 URLLoader 加载,而 PNG、JPG、SWF 等文件则可以通过 Loader 和 URLLoader 两种方式加载。如果资源需要长期保存,建议全部用 URLLoader 方式加载,在需要获取资源时,再通过 Loader 的 loadBytes 方法解析已经加载的二进制数据,之后再显示。
这样做目的是为了节省内存,因为 Loader 加载的资源会自动实例化(解码),PNG、JPG 会展开成无压缩的 BitmapData,SWF 舞台的内容也会全部实例化,他们会占用大量内存。先用 URLLoader 将他们作为二进制数据加载,需要时再解码实例化,就不会出现这个问题。
并发加载
多文件加载还有一个问题:浏览器对并发下载数有限制,而这个限制和 Flash Player 的机制有冲突,所以一般情况下 Flash Player 同时发起的加载请求数最好不要超过 5 个, 否则加载事件可能会失效。为了解决这个问题,大部分人的解决方案都是采取队列加载,一次只加载一个文件。这在文件数量较小、单文件体积较大时并没有问题,但是当文件数量多、单文件体积小时,由于每次加载完一个文件后,重新请求下一个文件时需要等待服务器响应一段时间才开始加载,这样会浪费很多带宽,文件数量多时这个缺陷不能忽略,最多可能消耗 2 至 3 倍的加载时间。
为了解决这个问题,我们需要一种特殊的队列加载模块,可以同时加载,但是同时加载的文件数量不能超过某个值。基本思路就是在加载完一个文件后,检查正在进行加载的文件数量,小于定值就取队列中的下一个地址新建加载,否则就什么都不做。
BulkLoader( http://code.google.com/p/bulk-loader ),
LoaderMax( http://www.greensock.com/loadermax )
都提供了这个功能,当然我的 GhostCat 也通过 AssetManager(复合 QueueLoadOper 实现)提供了这个功能。复杂度实际上并没有比线性加载高多少。
这样做,加载时等待服务器返回时依然有文件在下载,多个加载过程会平衡消耗带宽,带宽就不会被浪费。一般同时加载数量为 2 就足够了。但如果你的文件特别零散,使得两个文件同时等待返回的几率也很高,也可以考虑设置 2 以上的值。
哈希表缓存
当加载文件数量特别大的时候(诸如数百个),注意不要只使用数组保存。你可以创建一个 Dictionary,然后将名称作为键加载内容作为值,做一个哈希表,以后都直接通过名称从这个哈希表取值,会比遍历数组查找名称快很多。
使用 ZIP 或其他打包形式
如果你不喜欢 SWF 这种打包方式,也可以选择 ZIP 打包,详情请阅读 http://nochump.com/blog/archives/15 。
ZIP 的优点是可以用 winrar 打开,不需要借助专门的工具,缺点则是解压需要时间。当然,你也可以考虑用二进制自定义一个封装格式,这样也能加密资源文件,但这需要你自己编写一个管理工具。当然,这也不算特别麻烦。
使用 ShareObject 缓存
虽然有浏览器缓存,但实际上这种缓存持续不了几天,因为浏览器一向都有最大缓存限制。一般你看几个视频,这个缓存空间就消耗得差不多了。为了不让 Flash 加载的文件缓存被冲洗掉,你可以将加载的文件的二进制数据(Loader 是 contentLoaderInfo.bytes,URLLoader 则要用二进制方式加载获取其 data 属性)保存在 ShareObject 里,并添加版本号以便更新,下次加载就直接取这个数据。这个操作会请求大量 ShareObject 空间,因此 FLASH 会弹出提示让用户确认。如果你担心用户不确认,可以在游戏其他地方向用户说明情况并要求他们点击确认按钮。
现在不少游戏都采用了这种做法,效果还是可以的。
显示总体加载进度
我们可能在开始时会加载多个文件。比起每加载一个文件显示一次进度条从 0% 到 100% 的过程,显然是显示所有文件总体的加载进度,只进行一次从 0% 到 100% 的过程更具有实际意义。但是 FLASH 做这件事情并不太容易,因为它想要获得一个文件的大小就必须去加载它,而这个加载需要时间,你没有办法从一开始就立即获得所有文件的大小。
实现的方法只有一个,就是在程序中或者配置中写死所有文件的总大小,然后根据所有文件已经加载字节数的总和来计算和显示进度。
至于这个总和如何获得,可以在程序中写上 trace,实际加载运行一次便能获得实际所有文件大小总和,也可以做专门工具计算,也可以用操作系统来直接查看总和,总之方法不是问题。而且,即使这个值不准确也无大碍,因为加载流程依然是按加载完所有文件作为依据,而这个进度只是用来显示,就算不准也不过是未到 100% 就结束或者到了 100% 也要等待一段时间才能结束。这无伤大雅。
之前所说的类库都提供了这样的功能。
主 SWF 加载问题
SWF 必须加载完所有类后才能开始运行并显示图像,这样一来,第一个主 SWF 加载自己时就无法显示加载进度。解决这个问题有两种办法:
一种办法很老但是实用,就是创建一个小 SWF 先显示出来,专门用来加载主 SWF,主 SWF 加载完毕后它就完成了使命。实际上这并不麻烦也是最稳定的一种方案。
另一种方式是利用 Frame 元标签。在主 SWF 类名上面添加
[Frame(factoryClass=" 加载类类名 ")]
即可指定一个类作为加载类,它会在主 SWF 未加载完之前显示。这个类是一个两帧 MovieClip,当它自己加载完毕后,就可以反射出主 SWF 的内容并实例化。
可以看这篇文章: http://www.bit-101.com/blog/?p=946 。
而我自己的加载类模板则是这个,可以作为参考:
评论