巧用Loader与ByteArray管理显示对象的内存

楼主未能理解removeChild所做的事情,以为removeChild以后就不能再获得对象引用,于是提出了gotoAndStop和visible两种做法来实现图片切换。

经测试,gotoAndStop在播放头移动以后,上一帧的内容并未能从内存中移除,即使强制运行垃圾回收器,也未见内存占用明显下降,而当播放头重新回到曾经播放的帧时,内存仍继续上升。FlashPlayer在时间轴上所残留的内存无法释放。

visible的方案先将所有显示对象加入到舞台,控制其可见性以达到切换效果。本法的优点在于显示对象一次性载入,既不新增也不移除,所以只要载入完毕后内存占用不高,就不必担心内存溢出。缺点是浪费了很多不必要的内存占用,尽管它不再上升。

回帖者提供了addChild与removeChild的方案,本法灵活性相对较高,显示对象按需调入,避免了多余的内存占用。但 是,removeChild仅仅把对象从显示列表中移除,要将其从内存中删除,除了要将显示对象设置为null,对象本身的监听器,视频流,播放状态等也 要大部分清除干净,此外还要求用户管理好对象的引用,才可以保证对象能被回收。
另一方面,垃圾回收器不会在你做好以上工作后立即运行,至于强制运行,根据前辈们的经验,清除也是不彻底的(不排除他们没有管理好引用和监听器)。

DisplayObject基类及其子类Sprite,MovieClip均不具有通过as清除内存的方法,所以,一旦内存管理不善,哪怕是个小游戏,也 难逃内存溢出的风险。所以,作者尝试从Sprite,MovieClip的圈子里走出来,寻求一种可以通过as直接掌控内存的方案 (LocalConnection方案也仅为一种间接的方式)。

在FlashPlayer9时代,使用Loader.unload,其结果与removeChild大同小异。FlashPlayer10新增 unloadAndStop方法,可以同时强制执行垃圾回收器,测试结果显示,loader的content所占用内存能大部分被回收,而且立竿见影,一 执行,内存便得到释放,而且比LocalConnection方案的强制执行效果要完美。更重要的是,content的内存管理再糟糕,引用,监听器再混 乱,unloadAndStop也能顺利清理。

除了Loader.unloadAndStop可以立刻把内存移除以外,BitmapData.dispose,ByteArray.clear等方法均 具备直接释放内存的功能。本文将Loader与ByteArray结合,提出一种管理单个swf(即无外部加载文件)内存的方法。

对于新手(当然包括从事较底层开发的老手)而言,他们大多只会使用Loader的load方法载入swf或者jpg,png,gif等文件,实际 上,loader还有loadBytes方法,而要载入的ByteArray并不一定需要通过外部文件才能获得。可以通过编译器导入,或者 JPGEncoder等编码器生成。所以,当作者在回贴中指出用Loader时,楼主立刻回应表示不允许使用外部载入的方式。

不允许使用外部载入的限制,在实际应用中也并不少见,而本文的最终目的,正是为这一限制提供一种解决方案。

以下是具体做法,在FlashCS5 IDE下进行。
要在Flash中嵌入图片,元件,swf并且得到它的字节数组,导入并且为ActionScript是无法实现的(如果全部是图片,可以考虑用BitmapData替代),而应使用Embed元标签(FlashCS3不支持):
[Embed(source="images/Ascent.jpg",mimeType="application/octet-stream")]
private var _byteArray_Ascent:Class;
source值为嵌入的图片路径,至于mimeType属性,直接抄过去即可。
至此,_byteArray_Ascent类得到初始化(注意:非实例化),其继承ByteArray且包含了图片的字节数组。
用Loader载入的方法也相当简单:

  1. var my_loader:Loader = new Loader();
  2. my_loader.loadBytes(new _byteArray_Ascent());
  3. addChild(my_loader);

复制代码

如果需要移除,调用my_loader.unloadAndStop(true);就可以将这个loader的content从内存中移除,如果对 ByteArray的内存占用仍然不够放心,可以在unloadAndStop前调用ByteArray.clear方法。这么一处理,哪怕引用没管理 好,大部分的内存也可以成功释放,开发者不必担心由于引用问题而导致大量内存驻留于FlashPlayer中。

解决的基本思路已经给出,但导入文件(即添加Embed元标签)的过程,在FlashIDE下并无批量处理的方法(可能只是作者尚未发现),为此,作者写了一个批量生成Embed代码的jsfl文件。jsfl具体代码和使用方法下一贴再给出。

测试代码如下:

主类LoadBytesTest.as(类里include了两个文件,其中包含Embed标签和生成类数组的代码,在图片比较多的情况下,为了让主类代码更加清晰明朗,这两段代码从类文件里分离出去更为合适)

  1. package {
  2.         import flash.display.MovieClip;
  3.         import flash.utils.ByteArray;
  4.         import flash.display.Loader;
  5.         import flash.display.BitmapData;
  6.         import flash.utils.Timer;
  7.         import flash.events.TimerEvent;
  8.         import flash.sampler.getSize;
  9.         import flash.net.LocalConnection;
  10.         public class loadBytesTest extends MovieClip {
  11.                 //包含Embed.as
  12.                 include “embed.as”
  13.                 //加载器
  14.                 protected var _ldr:Loader;
  15.                 //图像的ByteArrayVector
  16.                 protected var _imagesVector:Vector.<Class>;
  17.                 protected var _imagesVectorLen:int;
  18.                 //当前正在使用的字节数组索引
  19.                 protected var _currentBytesIndex:int = 0;
  20.                 //当前正在使用的字节数组
  21.                 protected var _currentBytes:ByteArray;
  22.                 //切换的计时器
  23.                 //private var _switchTimer:Timer;
  24.                 public function loadBytesTest() {
  25.                         // constructor code
  26.                         init();
  27.                 }
  28.                 protected function init():void
  29.                 {
  30.                         //将字节数组的类存放于Vector中
  31.                         _imagesVector = new Vector.<Class>();
  32.                         include “pushEmbed.as”
  33.                         _imagesVectorLen = _imagesVector.length;
  34.                         //创建显示对象
  35.                         _ldr = new Loader();
  36.                         addChild(_ldr);
  37.                         //用Timer切换图片
  38.                         var _switchTimer=new Timer(200);
  39.                         _switchTimer.addEventListener(TimerEvent.TIMER,switchTimerHandler);
  40.                         _switchTimer.start();
  41.                 }
  42.                 private function switchTimerHandler(event:TimerEvent):void
  43.                 {
  44.                         //如果Loader存在ByteArray则清空
  45.                         if(_ldr.contentLoaderInfo.bytes!=null)
  46.                         {
  47.                                 _ldr.contentLoaderInfo.bytes.clear();
  48.                         }
  49.                         //卸载Loader
  50.                         _ldr.unloadAndStop(true);
  51.                         //清除当前的ByteArray
  52.                         if(_currentBytes != null)
  53.                         {
  54.                                 _currentBytes.clear();
  55.                         }
  56.                         //获得当前Vector的ByteArray
  57.                         _currentBytes=new _imagesVector[(_currentBytesIndex++)%_imagesVectorLen]();
  58.                         //加载进Loader
  59.                         _ldr.loadBytes(_currentBytes);
  60.                 }
  61.         }
  62. }

复制代码

embed.as(不是类文件)

  1. [Embed(source="images/Ascent.jpg",mimeType="application/octet-stream")]
  2. private var _byteArray_Ascent:Class;
  3. //….中间的其它embed省略,理论上可以无限添加
  4. [Embed(source="images/Windows XP.jpg",mimeType="application/octet-stream")]
  5. private var _byteArray_WindowsXP:Class;

复制代码

pushEmbed.as(也不是类文件)

  1. _imagesVector.push(_byteArray_Ascent);
  2. //…中间的push省略
  3. _imagesVector.push(_byteArray_WindowsXP);

复制代码

 

 

现介绍批量生成Embed标签的方法:

作者在FlashCS5及FlashBuilder4下尚未发现批量生成Embed标签的方法。作者的swf里嵌入了35张图片,一个个地输入代码也已经 觉得很麻烦。如果图片再多一些(1楼中的帖子链接中,楼主要嵌入100多张,回帖的甚至做过嵌入400多张图片的应用),制作过程就更繁琐了。

批量生成代码的工具有很多,因此,一键生成Embed元标签的特殊功能也可以用不同的方式实现。本贴将介绍Flash IDE下用jsfl批量生成代码的方法。

1 将要嵌入的图片全部复制到同一个文件夹下(作者用的文件夹是fla文件所在目录下的images文件夹),建议文件名全是英文字符。

2 在fla所在目录下新建一个jsfl文件(traceImageCommand.jsfl),输入如下代码(jsfl函数的用法请读者自行查阅jsfl的API):

  1. //将文件名转成类名,建议根据实际情况过滤文件名中包含的一些字符(不允许出现在类名中的字符)
  2. function convertFileNameToClassName(__fileName){
  3.         return “_byteArray_” + __fileName.slice(0,-4).split(” “).join(“”);
  4. }
  5. //获得当前文档的引用
  6. var currentDoc = fl.getDocumentDOM();
  7. //取得当前文档的路径
  8. var pathURI = currentDoc.pathURI;
  9. //取得与文档同级的images文件夹
  10. var fileURI = pathURI.slice(0, pathURI.lastIndexOf(“/”))+”/images/”;
  11. //取得images文件夹的jpg图片数组(如要png,bmp等,可以在此添加)
  12. var fileList = FLfile.listFolder(fileURI+”*.jpg”, “files”);
  13. //输出embed
  14. for(var i in fileList){
  15.         //输出embed标签
  16.         fl.trace(“[Embed(source=\"images/" + fileList[i] + “\”,mimeType=\”application/octet-stream\”)]”);
  17.         //输出类名,
  18.         fl.trace(“private var ” + convertFileNameToClassName(fileList[i]) + “:Class;\n”);
  19. }
  20. fl.trace(“\n\n”);
  21. //输出Vector供类使用
  22. var vectorVariableName = “_imagesVector”;
  23. fl.trace(“                        var “+ vectorVariableName + “:Vector.<Class> = new Vector.<Class>();”);
  24. for(var i in fileList){
  25.         fl.trace(“                        ” + vectorVariableName + “.push(” + convertFileNameToClassName(fileList[i]) + “);”);
  26. }

复制代码

3 打开fla文件,选择“命令”-“运行命令”,选中上一步创建的traceImageCommand.jsfl,输出面板将输出两段代码,第一段为元标签及对应类的声明,第二段为生成Vector类并将图片的Class添加进去的代码。

4 将批量生成的标签复制到剪贴板就可以将其粘贴到你们的代码里了。



发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

(Spamcheck Enabled)