Flash应用效率优化启示录Ⅳ

在上一章我们已经预告过本章的内容,有没有?我将在本章为大家带来如何发扬我们民族“勤俭节约”的美德的方法,我称之为“华丽终极超强死亡缓存策略”,小S present,here we go~!
缓存应用域
        在上一章贫道已经提到过,最好把要用到的素材全部放到一个swf里面去,这样既可以压缩素材容量又可以减免在swf中使用时要加载多个素材,发起多个HTTP连接而增加的等待时间。那么在此章贫道将为列位施主展示一下具体的操作方式,让列位施主“理论联系实际”,做个爱学习的好孩子。
        首先自然是打开Flash CS工具,然后把你需要用到的素材扔到库里面去然后设置它们的属性,让它们导出为ActionScript(不要忘了勾选'在第一帧中导出'单选框),下图是我做的一个简单的库,里面有常用的几种类型素材,图片,元件(可以是MovieClip、Sprite等多种类型):
<ignore_js_op>1.jpg 
        之后进行测试影片后会生成一个swf,这个swf就是我们的素材集合了,把它(我这里命名为assets.swf)放到你的AS项目的某个目录中去(我这里放在assets目录下)。好了,准备工作妥当,接下来是上正菜得时候了。
         我们需要在工程中设计一个单例来管理所有的素材,我叫它AssetsManager,它的原型如下:

  1. package com.zuisg.we
  2. {
  3.         import flash.events.EventDispatcher;
  4.                 
  5.         public class AssetsManager extends EventDispatcher
  6.         {
  7.                 private static var _instance:AssetsManager = null;
  8.                 
  9.                 //构造函数接收一个SingletonClass类型的参数,而SingletonClass类只有AssetsManager能访问它,
  10.                 //所以在外部无法实例化一个AssetsManager
  11.                 public function AssetsManager( singleton:SingletonClass ){}
  12.                 
  13.                 /**
  14.                  *  得到全局唯一的AssetsManager类实例
  15.                  */                
  16.                 public static function get instance():AssetsManager
  17.                 {
  18.                         if( _instance == null )_instance = new AssetsManager( new SingletonClass() );
  19.                         
  20.                         return _instance;
  21.                 }
  22.         }
  23. }
  24. class SingletonClass{}//包外类,只有此文件内的类可以访问之,也就是说只有AssetsManager能访问它
复制代码

这是一个标准的单例类,它继承了EventDispatcher所以它具备了发送事件的能力,为什么要让它具备发送事件的能力呢?因为加载素材需要时间,外面不能立即拿到AssetsManager提供的素材,所以就需要侦听一个“加载完成”事件来知道何时才能开始去问AssetsManager要素材。
        因为本节的话题是“缓存应用域”,所以我们需要给AssetsManager增加一个ApplicationDomain类型的属性来保存被加载的swf的应用域。ApplicationDomain(应用域)是什么东西呢?它是分散的类定义组的一个容器,所有ActionScript类的定义语句都会被保存于应用域中,就如刚才我们在fla文件库中的那几个素材被导出为ActionScript并分别被命名为了myImage, myMovie, myFuckingMovie,这些定义都会在被编译成为swf之后保存于此swf的应用域中。之后我们设计一个共有方法来允许外部控制加载的开始,并在加载完毕后保存被加载swf文件的应用域:

  1.                 
  2. private var appDomain:ApplicationDomain;
  3. ………………
  4. /**
  5. * 加载一个swf并保存应用域
  6. * @param url        欲加载swf路径
  7. */                
  8. public function loadResource( url:String ):void
  9. {
  10.         var loader:Loader = new Loader();
  11.         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
  12.         loader.load( new URLRequest( url ) );
  13. }
  14.                                 
  15. private function onComplete( e:Event ):void
  16. {
  17.          //被加载对象的应用域一般保存于LoadInfo中
  18.         appDomain = (e.currentTarget as LoaderInfo).applicationDomain;
  19.         dispatchEvent( new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E) );
  20. }
复制代码

这里我们抛出的是一个自定义的事件叫做AssetsEvent,它的代码如下:

  1. package com.zuisg.we.events
  2. {
  3.         import flash.events.Event;
  4.         
  5.         public class AssetsEvent extends Event
  6.         {
  7.                 /** 素材加载完毕后将会发送此事件*/
  8.                 public static const LOAD_COMPLETE_E:String = "load complete event";
  9.                 
  10.                 public function AssetsEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
  11.                 {
  12.                         super(type, bubbles, cancelable);
  13.                 }
  14.         }
  15. }
复制代码

当外部调用loadResource方法并传入一个加载路径后就可以开始加载,并在监听到LOAD_COMPLETE_E事件后得知此时可以开始问AssetsManager要素材资源了。此时我们已经保存了被加载对象的应用域,那么要得到应用域中保存的类定义可以使用ApplicationDomain的getDefinition方法。下面的代码是我设计的一个供外部调用的根据类名来获取素材实例的一个方法:

  1. /**
  2.                  * 根据类名获取素材实例 
  3.                  * @param className        类名
  4.                  * @return                         资源实例
  5.                  * 
  6.                  */                
  7.                 public function getAssetByClass( className:String ):Object
  8.                 {
  9.                         var result:Object;
  10.                         try
  11.                         {        
  12.                                 var classReference:Class = appDomain.getDefinition( className ) as Class;
  13.                                 result = new classReference();
  14.                         }
  15.                         catch( e:Error )
  16.                         {
  17.                                 trace("不存在此类定义");
  18.                         }
  19.                         return result;
  20.                 }
复制代码

有了如此卡哇伊如此好用的一个方法之后外部就可以根据需要来取资源实例了:

  1. package
  2. {
  3.         import com.zuisg.we.AssetsManager;
  4.         
  5.         import flash.display.Bitmap;
  6.         import flash.display.BitmapData;
  7.         import flash.display.MovieClip;
  8.         import flash.display.Sprite;
  9.         import flash.events.Event;
  10.         public class OptimizeYourFlashApp extends Sprite
  11.         {
  12.                 
  13.                 public function OptimizeYourFlashApp()
  14.                 {
  15.                         AssetsManager.instance.addEventListener( AssetsEvent.LOAD_COMPLETE_E, onLoadComplete );
  16.                         AssetsManager.instance.loadResource( "assets/assets.swf" );
  17.                 }
  18.                 
  19.                 private function onLoadComplete( e:AssetsEvent):void
  20.                 {
  21.                         var mc:MovieClip = AssetsManager.instance.getAssetByClass( "myMovie" ) as MovieClip;
  22.                         if( mc != null )
  23.                         {
  24.                                 addChild( mc );
  25.                         }
  26.                         
  27.                         var bmd:BitmapData = AssetsManager.instance.getAssetByClass( "myImage" ) as BitmapData;
  28.                         if( bmd != null )
  29.                         {
  30.                                 var bmp1:Bitmap = new Bitmap( bmd );
  31.                                 var bmp2:Bitmap = new Bitmap( bmd );
  32.                                 addChild( bmp1 );
  33.                                 addChild( bmp2 );
  34.                                 bmp2.x = bmp1.width;
  35.                         }
  36.                         
  37.                 }
  38.         }
  39.         
  40. }
复制代码

由于AssetsManager是一个单例类,所以在工程中的任何地方都能够调用AssetsManager.instance.getAssetByClass来获取想要的资源,但是必须确保资源已经加载完毕。如上就是缓存应用域的方法,只需加载一次资源文件即可满足整个项目的资源使用。

缓存已加载资源
       如果你在项目开发中无法做到把全部资源打包成一个swf,那么你不得不面对一个严峻的事实那就是多次甚至大量的加载。那么现在由贫道提供的“华丽终极超强死亡缓存策略”可能会对你有所帮助。此策略只适合缓存位图数据(bitmapData),为什么呢?因为我们知道,像MovieClip、Sprite、Bitmap这些常用的显示对象是没办法重用和复制的。举个例子,看下面的代码:

  1. var mc1:MovieClip = resource;//resource是一个可用的MovieClip资源实例
  2. var mc2:MovieClip = resource;
  3. addChild( mc1 );
  4. addChild( mc2 );
  5. mc2.x = 200;
复制代码

如果运行这段代码那么我们只可能看到位于200位置的mc2的外形,看不见mc1的外形,因为把一个MovieClip实例赋值给一个对象是“地址引用”而非“值引用”,对于Sprite、Bitmap、Loader这些东西来说也是一样的。
       不过还好,有一个幸存者:BitmapData是可用复用的,如果把一个BitmapData实例赋值给两个Bitmap实例的bitmapData属性(或者你可以调用BitmapData的clone方法来实现拷贝),那么这两个Bitmap都会显示出来。所以贫道的“华丽终极超强死亡缓存策略”的实质就是缓存BitmapData。
       那么为了体现出贫道“华丽终极超强死亡缓存策略”的威力,还是先看看使用一般加载的方法会耗时多少吧,还是在我们刚才创建的AssetsManager基础上增加功能,写一个方法来读取图片:

  1. public function loadImage( url:String ):void
  2. {
  3.         var loader:Loader = new Loader();
  4.         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageComplete);
  5.         loader.load( new URLRequest( url ) );
  6. }
  7.                 
  8. private function onImageComplete( e:Event ):void
  9. {
  10.         try
  11.         {
  12.                 var loadInfo:LoaderInfo = e.currentTarget as LoaderInfo;
  13.                         
  14.                 //取出被加载图片的位图数据
  15.                 var bmd:BitmapData = (loadInfo.content as Bitmap).bitmapData;
  16.                                 
  17.                 //发送加载完成事件
  18.                 var event:AssetsEvent = new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_BITMAPDATA);
  19.                 event.loadedBitmapData = bmd;
  20.                                 
  21.                 dispatchEvent( event );
  22.         }
  23.         catch( e:Error )
  24.         {
  25.                 trace("加载对象格式错误!");
  26.         }
  27. }
复制代码

很容易理解吧?这段代码列位应该已经用的应该很多了。哦,忘了说了,我把AssetsEvent又改装了一下,为它增加了两个属性:

  1. package com.zuisg.we.events
  2. {
  3.         import flash.display.BitmapData;
  4.         import flash.events.Event;
  5.         
  6.         public class AssetsEvent extends Event
  7.         {
  8.                 /** 素材加载完毕后将会发送此事件*/
  9.                 public static const LOAD_COMPLETE_E:String = "load complete event";
  10.                 
  11.                 /**
  12.                  * 缓存策略:缓存应用域 
  13.                  */                
  14.                 public static const CACHE_POLICY_APPDOMAIN:String = "cache application domain";
  15.                 
  16.                 /**
  17.                  * 缓存策略:缓存位图数据 
  18.                  */                
  19.                 public static const CACHE_POLICY_BITMAPDATA:String = "cache bitmapData";
  20.                 
  21.                 /** 被加载位图数据 */
  22.                 public var loadedBitmapData:BitmapData;
  23.                 
  24.                 /** 加载策略 */
  25.                 public var cachePolicy:String;
  26.                 
  27.                 public function AssetsEvent(type:String, cachePolicy:String, bubbles:Boolean=false, cancelable:Boolean=false)
  28.                 {
  29.                         super(type, bubbles, cancelable);
  30.                         this.cachePolicy = cachePolicy;
  31.                 }
  32.         }
  33. }
复制代码

当我使用AssetsManager中的加载方法加载完成后会派发一个AssetsEvent 事件,在外部侦听到此事件后我们可以根据此事件的cachePolicy属性判断我们当初加载的是swf并缓存了它的应用域还是加载的是图片并缓存了它的bitmapData。如果是后者,那么我们就可以取出事件中携带的被加载图片的bitmapData来为我所用(保存于loadedBitmapData这个属性里面)。
        那么现在在外部调用一下这个新方法并记录两次加载同一张图片的耗时情况:

  1. public class OptimizeYourFlashApp extends Sprite
  2. {
  3.         private var startTime:int;
  4.         private var i:int = 0;
  5.                 
  6.         public function OptimizeYourFlashApp()
  7.         {
  8.                 AssetsManager.instance.addEventListener( AssetsEvent.LOAD_COMPLETE_E, onLoadComplete );
  9.                 startTime = getTimer();
  10.                 AssetsManager.instance.loadImage( "assets/27.jpg" );
  11.         }
  12.                 
  13.         private function onLoadComplete( e:AssetsEvent ):void
  14.         {
  15.                 if( e.cachePolicy == AssetsEvent.CACHE_POLICY_BITMAPDATA && e.loadedBitmapData != null )
  16.                 {
  17.                         var bmp:Bitmap = new Bitmap( e.loadedBitmapData );
  18.                         i++;
  19.                         trace("第" + i + "次耗时:" + (getTimer() - startTime) + "毫秒");
  20.                                 
  21.                         if( i < 2 )
  22.                         {
  23.                                 startTime = getTimer();
  24.                                 AssetsManager.instance.loadImage( "assets/27.jpg" );
  25.                         }
  26.                 }
  27.                         
  28.         }
  29. }
复制代码

测试结果如下:
第1次耗时:138毫秒
第2次耗时:38毫秒

从这个结果可以看出flash player自身就具备了一定量的缓存功能,不过和贫道的“华丽终极超强死亡缓存策略”比起来还是差得远了。

首先为列位介绍一个类:Dictionary。它是一个动态类,我们可以用它来创建属性的动态集合。举个例子:

  1. var dic:Dictionary = new Dictionary()
  2. var name:String = "shit";
  3. dic[name] = new Object()
复制代码

这样我们就把一个对象以name为键值存入了一个Dictionary当中,取数据时只要访问dic[name]即可。当一个Dictionary中存有多个对象是我们就可以用键值来进行访问某个对象。
       接下来在AssetsManager中设置一个Dictionary的对象来存放已加载过的BitmapData:

  1. /**
  2. * BitmapData缓存字典 
  3. */                
  4. private var _cacheOfImage:Dictionary = new Dictionary(true);
复制代码

这里在Dictionary的构造函数中设置第一个参数为true可以开启弱引用,帮助手册上的解释是:如果对某个对象的唯一引用位于指定的 Dictionary 对象中,则键符合垃圾回收的条件,并且在回收对象时会被从表中删除。 这也是使用Dictionary的好处之一。当然,你也可以用Array数组来实现ary[name] = obj这样的键-值存储,但是只是为了进行键-值存储的话还是推荐使用轻量级且具备弱引用功能的Dictionary。
     之后直接先给出改进后的loadImage方法:

  1. /**
  2. * 加载图片并缓存 
  3. * @param url        加载图片路径
  4. */                
  5. public function loadImage( url:String ):void
  6. {
  7.         //若缓存中存在此资源则直接拿出来使用
  8.         if( _cacheOfImage[url] != null )
  9.         {
  10.                 var e:AssetsEvent = new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_BITMAPDATA);
  11.                 e.loadedBitmapData = ( _cacheOfImage[url] as BitmapData ).clone();
  12.                 dispatchEvent( e );
  13.         }
  14.         //若缓存中不存在此资源则进行加载
  15.         else
  16.         {
  17.                 var loader:Loader = new Loader();
  18.                 //将url暂存于Loader的name属性中
  19.                 loader.name = url;
  20.                 loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageComplete);
  21.                 loader.load( new URLRequest( url ) );
  22.         }
  23.                         
  24. }
  25. private function onImageComplete( e:Event ):void
  26. {
  27.         try
  28.         {
  29.                 var loadInfo:LoaderInfo = e.currentTarget as LoaderInfo;
  30.                                 
  31.                 //取出保存于Loader中的加载路径名
  32.                 var name:String = loadInfo.loader.name;
  33.                                 
  34.                 //取出被加载图片的位图数据
  35.                 var bmd:BitmapData = (loadInfo.content as Bitmap).bitmapData;
  36.                                 
  37.                 //根据路径名缓存位图数据
  38.                 _cacheOfImage[ name ] = bmd;
  39.                                 
  40.                 //发送加载完成事件
  41.                 var event:AssetsEvent = new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_BITMAPDATA);
  42.                 event.loadedBitmapData = bmd;
  43.                                 
  44.                 dispatchEvent( event );
  45.                //记得移除无用的事件侦听,让无用对象能被垃圾回收
  46.                 loadInfo.removeEventListener(Event.COMPLETE, onImageComplete);
  47.         }
  48.         catch( e:Error )
  49.         {
  50.                 trace("加载对象格式错误!");
  51.         }
  52. }
复制代码

上面的代码是我精心设计过的加载方法,提供多进程并行开始下载,因为每一个对象调用loadImage方法时都会新new一个Loader,这样产生的1对1关系能确保在一次收到多个下载请求时能够正确地实现并行加载,我们把一些加载完成后要用到的信息放在Loader的name属性里(我很喜欢用这个闲置的属性^_^),待加载完成后将加载图片的bitmapData以它的文件路径名进行保存。那么在第二次加载同一个路径名的图片时就可以直接从缓存的Dictionary对象中取出来直接用了。
        最后来看看使用新的loadImage方法进行加载的速度测试结果,文档类里面不需要改代码,直接调试,输出结果如下:
第1次耗时:208毫秒
第2次耗时:0毫秒
        虽然是意料之中但也没想到会那么快,第二次加载居然实现了零等待时间!
        不过在使用过程中你也许会碰到一个问题:我在A类和B类中同时写了如下代码:

  1. AssetsManager.instance.addEventListener( AssetsEvent.LOAD_COMPLETE_E, onLoadComplete );
  2. AssetsManager.instance.loadImage( "assets/xx.jpg" );
复制代码

这两个类都需要使用AssetsManager的loadImage方法进行图片加载,所以都对AssetsManager的单例进行了完成事件的侦听。假设A加载图片得速度比B快,那么A先加载完毕后在AssetsManager中会抛出一个完成事件,事件中将会携带A所加载图片的位图数据,但是由于AssetsManager是个单例,而在A类和B类中又同时对AssetsManager进行了事件侦听,所以在A侦听到加载完成事件的同时B也会侦听到了,这将会导致数据紊乱。
<ignore_js_op>2.JPG 
        解决方案是为每一个加载请求者添加一个id,然后把这个id放入加载完成事件中去,当加载完毕一个图片后派发LOAD_COMPLETE_E事件,全部侦听此事件的类都会侦听到,侦听到事件后去查看此事件中的id,如果跟自己的一样那就进行下一步操作,如果不是则掠过。这就好比是领快递,是我的包裹我就领走,不是就忽略。
        好了,接下来开始把想法付诸代码吧。先为AssetEvent增加一个id属性,之后修改AssetsManager中的loadImage代码:

  1. /**
  2. * 加载图片并缓存 
  3. * @param url        加载图片路径
  4. * @param id        加载请求者的标识符
  5. */                
  6. public function loadImage( url:String, id:String ):void
  7. {
  8.         //若缓存中存在此资源则直接拿出来使用
  9.         if( _cacheOfImage[url] != null )
  10.         {
  11.                 var e:AssetsEvent = new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_BITMAPDATA);
  12.                 e.loadedBitmapData = ( _cacheOfImage[url] as BitmapData ).clone();
  13.                 e.id = id;//赋予事件id
  14.                 dispatchEvent( e );
  15.         }
  16.         //若缓存中不存在此资源则进行加载
  17.         else
  18.         {
  19.                 var loader:Loader = new Loader();
  20.                 //将url以及id暂存于Loader的name属性中,以下划线分隔各个信息
  21.                 loader.name = url + "_" + id;
  22.                 loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageComplete);
  23.                 loader.load( new URLRequest( url ) );
  24.         }
  25.                         
  26. }
  27. private function onImageComplete( e:Event ):void
  28.                 {
  29.                         try
  30.                         {
  31.                                 
  32.                                 var loadInfo:LoaderInfo = e.currentTarget as LoaderInfo;
  33.                                 
  34.                                 //取出保存于Loader的name属性中的信息
  35.                                 var info:Array = loadInfo.loader.name.split('_');
  36.                                 var name:String = info[0];//下划线前的字符串是加载路径
  37.                                 var id:String = info[1];//下划线后的字符串是id
  38.                                 
  39.                                 //取出被加载图片的位图数据
  40.                                 var bmd:BitmapData = (loadInfo.content as Bitmap).bitmapData;
  41.                                 
  42.                                 //根据路径名缓存位图数据
  43.                                 _cacheOfImage[ name ] = bmd;
  44.                                 
  45.                                 //发送加载完成事件
  46.                                 var event:AssetsEvent = new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_BITMAPDATA);
  47.                                 event.loadedBitmapData = bmd;
  48.                                 event.id = id;
  49.                                 
  50.                                 dispatchEvent( event );
  51.                                 
  52.                                 //记得移除无用的事件侦听,让无用对象能被垃圾回收
  53.                                 loadInfo.removeEventListener(Event.COMPLETE, onImageComplete);
  54.                         }
  55.                         catch( e:Error )
  56.                         {
  57.                                 trace("加载对象格式错误!");
  58.                         }
  59.                 }
复制代码

之后,我们在外部使用时需要在调用loadImage方法时传入第二个参数id,并完成事件的侦听函数中判断事件中的id是否和自己的匹配:

  1. private var id:String;
  2. public function OptimizeYourFlashApp()
  3. {
  4.         AssetsManager.instance.addEventListener( AssetsEvent.LOAD_COMPLETE_E, onLoadComplete );
  5.                         
  6.         id = Math.random().toString();//随机生成一个标识符
  7.                                                 
  8.         AssetsManager.instance.loadImage( "assets/27.jpg", id );
  9. }
  10.                 
  11. private function onLoadComplete( e:AssetsEvent ):void
  12. {
  13.         if( e.cachePolicy == AssetsEvent.CACHE_POLICY_BITMAPDATA && e.loadedBitmapData != null
  14.         &&  e.id == id )
  15.         {    //do something }
  16. }
复制代码
 
如果要想缓存一个MovieClip影片剪辑怎么办呢?在启示录Ⅱ中我谈到的转换MovieClip为BitmapData的方法你还记得吗?如果我们把一个MovieClip转为BitmapData数组后我们就可以通过缓存这个数组达到目的。
     首先为AssetEvent增加一个事件类型的常量,一个被加载影片转换而成的bitmapData数组属性:
  1. public class AssetsEvent extends Event
  2. {
  3.         …………
  4.                 
  5.         /**
  6.          * 缓存策略:缓存动画数据 
  7.          */                
  8.         public static const CACHE_POLICY_MOVIE:String = "cache movie";
  9.                                 
  10.         /** 被加载影片剪辑的bitmapData数组 */
  11.         public var loadedMovie:Array;
  12.                 
  13.         ……
  14. }
复制代码
之后自然是为AssetsManager增加一个加载影片剪辑的共有方法:
  1. /**
  2. * 加载影片剪辑并转化为BitmapData数组 
  3. * @param url                加载路径
  4. * @param id                加载请求者id
  5. * @param symbol        加载影片剪辑中的元件名,若为空,则显示整一个舞台
  6. */                
  7. public function loadMovie( url:String, id:String, symbol:String="" ):void
  8. {
  9.         //若缓存中存在此资源则直接拿出来使用
  10.         if( _cacheOfImage[url] != null )
  11.         {
  12.                 var e:AssetsEvent = new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_MOVIE);
  13.                 e.loadedMovie = ( _cacheOfImage[url] as Array ).concat();//拷贝一份bitmapData数组
  14.                 e.id = id;
  15.                 dispatchEvent( e );
  16.         }
  17.                 //若缓存中不存在此资源则进行加载
  18.         else
  19.         {
  20.                 var loader:Loader = new Loader();
  21.                 //所有信息均暂存于Loader的name属性中,以下划线分隔各个信息
  22.                 loader.name = url + "_" + id + "_" + symbol;
  23.                 loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onMovieComplete);
  24.                 loader.load( new URLRequest( url ) );
  25.         }
  26. }
  27. private function onMovieComplete( e:Event ):void
  28. {
  29.         try
  30.         {
  31.                                 
  32.                 var loadInfo:LoaderInfo = e.currentTarget as LoaderInfo;
  33.                                 
  34.                 //取出保存于Loader的name属性中的信息
  35.                 var info:Array = loadInfo.loader.name.split('_');
  36.                 var name:String = info[0];//加载路径
  37.                 var id:String = info[1];//id
  38.                 var symbol:String = info[2];//动画元件名
  39.                                 
  40.                 //取出被加载的影片剪辑
  41.                 var mc:MovieClip;
  42.                 var bmds:Array;
  43.                 if( symbol != null )
  44.                 {
  45.                         var classReference:Class = loadInfo.applicationDomain.getDefinition( symbol ) as Class;
  46.                         mc = new classReference() as MovieClip;
  47.                 }
  48.                 else
  49.                 {
  50.                         mc = loadInfo.content as MovieClip;
  51.                 }
  52.                                 
  53.                 //取出正确的影片剪辑对象后转化为bitmapData数组
  54.                 if( mc != null )bmds = M2B.transformM2B( mc, mc.width, mc.height );        
  55.                         
  56.                 //根据路径名缓存位图数据
  57.                 _cacheOfImage[ name ] = bmds;
  58.                                 
  59.                 //发送加载完成事件
  60.                 var event:AssetsEvent = new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_MOVIE);
  61.                 event.loadedMovie = bmds;
  62.                 event.id = id;
  63.                                 
  64.                 dispatchEvent( event );
  65.                                 
  66.                 //记得移除无用的事件侦听,让无用对象能被垃圾回收
  67.                 loadInfo.removeEventListener(Event.COMPLETE, onImageComplete);
  68.         }
  69.         catch( e:Error )
  70.         {
  71.                 trace("加载对象格式错误!");
  72.         }
  73. }
复制代码
上面的代码相信仔细阅读过我之前帖子的道友应该不难理解。接下来我们就可以使用这个方法了,不要告诉我你忘记怎么用了!
  1. package
  2. {
  3.         import com.zuisg.we.AnimationObject;
  4.         import com.zuisg.we.AssetsManager;
  5.         import com.zuisg.we.events.AssetsEvent;
  6.         
  7.         import flash.display.Sprite;
  8.         import flash.utils.getTimer;
  9.         public class OptimizeYourFlashApp extends Sprite
  10.         {
  11.                 private var startTime:int;
  12.                 private var i:int = 0;
  13.                 private var id:String;
  14.                 
  15.                 public function OptimizeYourFlashApp()
  16.                 {
  17.                         AssetsManager.instance.addEventListener( AssetsEvent.LOAD_COMPLETE_E, onLoadComplete );
  18.                         
  19.                         id = Math.random().toString();//随机生成一个标识符
  20.                         
  21.                         startTime = getTimer();
  22.                         
  23.                         AssetsManager.instance.loadMovie( "assets/assets.swf", id, 'myMovie' );
  24.                 }
  25.                 
  26.                 private function onLoadComplete( e:AssetsEvent ):void
  27.                 {
  28.                         if( e.cachePolicy == AssetsEvent.CACHE_POLICY_MOVIE && e.loadedMovie != null
  29.                         &&  e.id == id )
  30.                         {
  31.                                 var animationObj:AnimationObject = new AnimationObject();
  32.                                 animationObj.imgList = e.loadedMovie;
  33.                                 animationObj.play();
  34.                                 animationObj.x = i * 200;
  35.                                 animationObj.y = 100;
  36.                                 addChild( animationObj );
  37.                                 i++;
  38.                                 trace("第" + i + "次耗时:" + (getTimer() - startTime) + "毫秒");
  39.                                 
  40.                                 if( i < 2 )
  41.                                 {
  42.                                         startTime = getTimer();
  43.                                         AssetsManager.instance.loadMovie( "assets/assets.swf", id, 'myMovie' );
  44.                                 }
  45.                                 
  46.                         }
  47.                         
  48.                         
  49.                 }
  50.         }
  51.         
  52. }
复制代码
显示出来的内容是一开始我创建的那个素材资源中的名为myMovie的影片剪辑。它已经被转换为bitmapData数组并使用我自定义的一个Bitmap类来进行动画播放。由于我缓存了这个转换结果,所以第二次加载的耗时依然是0毫秒~
      好好阅读并理解我奉献给列位的这个AssetsManager吧,这里给出源码供列位道友使用,enjoy it~! <ignore_js_op>AssetsManager.rar (4.45 KB, 下载次数: 220) 

最近有道友提出了一个问题就是在之前讲的缓存应用域的代码只能适用于存在单一素材文件的情况下。如果我当前falsh应用所使用的素材全部存在一个resource.swf文件中的话上面的代码是不会存在什么问题的,但是若当前falsh应用所使用的素材存放于多个swf文件下的话就会出现问题了,注意到以下代码:
  1. private function onComplete( e:Event ):void
  2. {
  3.           //被加载对象的应用域一般保存于LoadInfo中
  4.          appDomain = (e.currentTarget as LoaderInfo).applicationDomain;
  5.          dispatchEvent( new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E) );
  6. }
复制代码
在每次加载完成后appDomain 这个应用域都会被新加载完毕的swf的应用域所覆盖,因此,如果按照上面这个代码依次加载1.swf和2.swf的话,最后我缓存的应用域appDomain 中实际上只持有2.swf的应用域,此时通过getAssetByClass方法将无法拿到1.swf中的类定义。
       那么这里说一下解决的方案。在Flash AS3中,我们一般在使用Loader对象加载文件时往往会忽略掉其load方法的第二个参数,我们一起来看看AS3语言参考手册中对load方法第二个参数的解释:
  1. context:LoaderContext (default = null) — LoaderContext 对象,它具有定义下列内容的属性: 
  2. Flash Player 是否应在加载对象时检查策略文件是否存在
  3. 被加载的对象的 ApplicationDomain
  4. 加载的对象的 SecurityDomain
复制代码
注意到“被加载的对象的 ApplicationDomain”这句话,这就是咱们的入手点,我们可以为load方法设置第二个参数context,并设置此context的applicationDomain属性为当前应用域appDomain 即可。如果我们在每个Loader对象加载素材文件时都对它们如此设置,那么全部被加载swf的应用域将全部合并到appDomain 对象中,达到集全部被加载swf文件中的元件类定义于一身的目的。下面让我们来看看改进后的AssetsMananger代码吧。
  1. /**
  2. * 带缓存功能的素材管理类 
  3. * @author S_eVent
  4. */        
  5. public class AssetsManager extends EventDispatcher
  6. {
  7.     /** 当前应用的应用域副本 */
  8.         private var appDomain:ApplicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
  9. ……
  10.          /**
  11.          * 加载一个swf并保存应用域
  12.          * @param url        欲加载swf路径
  13.          * 
  14.          */                
  15.         public function loadResource( url:String ):void
  16.         {
  17.                 var loader:Loader = new Loader();
  18.                 loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
  19.                 var loaderContext:LoaderContext = new LoaderContext(false, appDomain);//将被加载的swf文件应用域合并至appDomain中
  20.                 loader.load( new URLRequest( url ), loaderContext );
  21.         }
  22. ……
  23.         private function onComplete( e:Event ):void
  24.         {
  25.                 dispatchEvent( new AssetsEvent(AssetsEvent.LOAD_COMPLETE_E, AssetsEvent.CACHE_POLICY_APPDOMAIN) );
  26.         }
  27. }
复制代码
之后,我们写个小例子来测试一下AssetsMananger能否顺利地加载两个素材文件并得到它们里面的类定义:
  1. public class Test extends Sprite
  2. {
  3.         public static const ASSETS_FLODER_PATH:String = "assets/";
  4.         
  5.         private var resourceList:Array = ["PlayResource.swf", "resource.swf"];
  6.         private var currentLoadingIndex:int = 0;
  7.         
  8.         
  9.     public function Test()
  10.         {
  11.                 currentLoadingIndex = 0;
  12.                 loadResource( resourceList[currentLoadingIndex] );
  13.         }
  14.         private function loadResource( url:String ):void
  15.         {
  16.                 AssetsManager.instance.addEventListener( AssetsEvent.LOAD_COMPLETE_E, onLoadComp );
  17.                 AssetsManager.instance.loadResource( ASSETS_FLODER_PATH + url );
  18.         }
  19.         
  20.         private function onLoadComp( e:AssetsEvent ):void
  21.         {
  22.                 if( ++currentLoadingIndex < resourceList.length )
  23.                 {
  24.                         loadResource( resourceList[currentLoadingIndex] );
  25.                 }
  26.                 //全部素材已经加载完毕
  27.                 else
  28.                 {
  29.                         //添加来自PlayResource.swf中的元件
  30.                         var child1:MovieClip = AssetsManager.instance.getAssetByClass("exclamation") as MovieClip;
  31.                         if( child1 )
  32.                         {
  33.                                 child1.x = child1.y = 50;
  34.                                 addChild(child1);
  35.                         }
  36.                         
  37.                         //添加来自resource.swf中的元件
  38.                         var child2:Sprite = AssetsManager.instance.getAssetByClass("GEM_COLLECT_FLOWER") as Sprite;
  39.                         if( child2 )
  40.                         {
  41.                                 child2.x = 200;
  42.                                 child2.y = 50;
  43.                                 addChild(child2);
  44.                         }
  45.                                 
  46.                 }
  47.         }
  48. }
复制代码
实验结果非常顺利~最后,祝列位使用愉快,如果有问题可以接着提出~
原文地址:https://www.cnblogs.com/keng333/p/2717154.html