收藏本站腾讯微博新浪微博

经典论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

蓝色理想 最新研发动态 网站开通淘帖功能 - 蓝色理想插件 论坛内容导读一页看论坛 - 给官方提建议

论坛活动及任务 地图和邮件任务 请多用悬赏提问 热夏来袭,选一款蓝色理想的个性T恤吧!

手机上论坛,使用APP获得更好体验 急需前端攻城狮,获得内部推荐机会 论坛开通淘帖功能,收藏终于可以分类了!

搜索
查看: 10416|回复: 6

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

[复制链接]
发表于 2010-9-3 17:29:51 | 显示全部楼层 |阅读模式
本文技术含量并不高,对于不必担心内存问题的小项目以及内存管理良好的大型项目也没有使用的必要,对此可以不予理会。

本篇教程为以下的贴子提供了一个解决方案:
http://bbs.blueidea.com/thread-2996177-1-1.html

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


复制代码


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);
复制代码


本贴最后附上swf测试文件,下一贴介绍批量生成Embed标签的方法及相关源文件。

[[i] 本帖最后由 HBrO 于 2010-9-19 10:36 编辑 ]

loadBytes.swf

995.26 KB, 下载次数: 364

loadBytes内存占用测试

发表于 2010-9-3 18:47:28 | 显示全部楼层
支持大虾。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-9-4 09:29:24 | 显示全部楼层
现介绍批量生成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 将批量生成的标签复制到剪贴板就可以将其粘贴到你们的代码里了。

loadBytes内存占用测试.rar

928.6 KB, 下载次数: 351

回复 支持 反对

使用道具 举报

发表于 2010-9-4 22:09:52 | 显示全部楼层
谢谢楼主,学习收藏!
回复 支持 反对

使用道具 举报

发表于 2010-9-7 23:59:11 | 显示全部楼层

回复 3# HBrO [楼主] 的帖子

版主辛苦了,非常感谢.
回复 支持 反对

使用道具 举报

发表于 2013-1-24 16:54:30 | 显示全部楼层
一篇好文章,绝对要支持下!
回复 支持 反对

使用道具 举报

发表于 2013-7-30 10:57:46 | 显示全部楼层
受益匪浅,谢谢楼主
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|小黑屋|Archiver|手机版|blueidea.com ( 湘ICP备12001430号 )  

GMT+8, 2020-8-15 10:53 , Processed in 0.093512 second(s), 9 queries , Gzip On, Memcache On.

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表