打印

[AS3] AS3.0 下载队列类 帮助完成Flash队列下载

多次试验发现队列下载效率真的不太好。还是扩展一个Loader 加入了超时的设置,详细代码在跟帖中。
------------------------------------------------
前几天应征Flash程序员。被问到为什么Flash有时候Loader下载会无缘无故暂停,既不报超时也不报错。当时没想到为什么?后来被告知正确答案是FlashPlayer的并发下载的Bug。汗
今天自己作了一个队列下载的单例类。目的就是解决并发的问题和使用Loader的易用性。
原理是,生成了一个类的单例,在全局使用。单例中包括一个Timer时间轮训,一个array的下载URL列表,一个加载顺序的策略。很简单
如果感兴趣的朋友可以拿去用,哈哈。欢迎大家和我讨论。
CODE:
LoadLine.as
复制内容到剪贴板
代码:
package com.FSC.UI.InterActiveObject.DisplayObjectContainer.LoadLine
{
    import flash.display.MovieClip;
    import flash.display.Loader;
    import flash.net.URLRequest;
    import flash.utils.Timer;
    import flash.events.*;
    /**下载队列类
     * @ andy pan
     * @ v1.080506
     */
    public class LoadLine extends MovieClip
    {
        private static var loadLine:LoadLine;
        private static var key:Boolean=false;
        private var _loader:Loader= new Loader();
        private var _loaderList:Array = new Array();
        private var _timer:Timer = new Timer(500);
        private var _isNowLoading:Boolean=false;
        //加载失败尝试次数
        private var _tryErrorTime:Number=1;
        //当前加载失败次数
        private var _tryErrorNowTime:Number = 0;
        public function LoadLine()
        {
            if( !key ){
                throw new Error ("单例,请用 getInstance() 取实例。");
            }
            key=false;
            _timer.addEventListener(TimerEvent.TIMER,everySed);
            _timer.start();
            _loader.contentLoaderInfo.addEventListener(Event.COMPLETE,onCompleteHandler);
            _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
               _loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);      
               _loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, progressHandler);
        }
        
        public static function getInstance() : LoadLine {
            if ( loadLine == null ){
                 key=true;
                loadLine = new LoadLine();
            }
            return loadLine;
        }
        
        /**静态函数 下载命令
        * @param target下载地址
        */
        public function loadFile(target:URLRequest,emergent:Number=10){
            if(emergent==0){
                _loaderList.unshift(target);
                if(_isNowLoading){
                    //如果有正在加载的项目则 暂停
                    cancelNowLoad();
                }
            }else{
                _loaderList.push(target);
            }
        }
        /**终止当前下载
        */
        private  function cancelNowLoad(){
            _loader.close();
        }
        /**每秒监听 队列是否有下载
        * @param e 时间参数
        */
        private function everySed(e:TimerEvent){
            //但下载器空闲时
            if((!_isNowLoading)&&(_loaderList.length>0)){
                var tmp_url:URLRequest = _loaderList[0] as URLRequest;             
                _loader.load(tmp_url);
                this.addChild(_loader);
                _isNowLoading = true;
            }
        }
        /**当前下载中的侦听
        */
        private function progressHandler(e:ProgressEvent){
            this.dispatchEvent(new LoadLineProgressEvent(ProgressEvent.PROGRESS,_loaderList[0],false,false,e.bytesLoaded,e.bytesTotal));
        }
        /**当前下载完成
        */
        private function onCompleteHandler(e:Event){
            this.dispatchEvent(new LoadLineEvent(LoadLineEvent.ONLOADLINECOMPLETE,_loaderList[0],_loader.content));
            _loaderList.shift();//当完成下载后再从队列中删除
            _isNowLoading = false;
        }
        /**当加载错误进行尝试处理
        */
        private function onError(e:IOErrorEvent){
            _isNowLoading = false;
            if(_tryErrorNowTime>_tryErrorTime){
                trace("error:下载队列中有文件加载失败,并且超过尝试次数。已从队列中删除")
                this.dispatchEvent(new LoadLineEvent(LoadLineEvent.ONLOADLINECOMPLETE,_loaderList[0],false));
                _tryErrorNowTime=0;
                //当完成下载后再从队列中删除
                _loaderList.shift();
            }else{
                _tryErrorNowTime++;
            }
        }        
    }
}
自定义加载完成的事件类
LoadLineEvent.as
复制内容到剪贴板
代码:
package com.FSC.UI.InterActiveObject.DisplayObjectContainer.LoadLine
{
    import flash.events.Event;
    import flash.net.URLRequest;
    public class LoadLineEvent extends Event
    {
        public static var ONLOADLINECOMPLETE : String = "onLoadLineComplete";
        public var currentTragetURL:URLRequest;
        public var content:*;
        public var isSuccess:Boolean
        public function LoadLineEvent(type:String,URL:URLRequest,content,isSuccess:Boolean = true, bubbles:Boolean=true, cancelable:Boolean=false)
        {
            super(type, bubbles, cancelable);
            this.currentTragetURL = URL;
            this.isSuccess = isSuccess;
            this.content = content;
        }
        
    }
}
自定义加载过程中的事件类
LoadLineProgressEvent.as
复制内容到剪贴板
代码:
package com.FSC.UI.InterActiveObject.DisplayObjectContainer.LoadLine
{
    import flash.events.ProgressEvent;
    import flash.net.URLRequest;
    public class LoadLineProgressEvent extends ProgressEvent
    {
        public var currentTragetURL:URLRequest;
        public function LoadLineProgressEvent(type:String,URL:URLRequest, bubbles:Boolean=false, cancelable:Boolean=false, bytesLoaded:uint=0.0, bytesTotal:uint=0.0)
        {
            super(type, bubbles, cancelable, bytesLoaded, bytesTotal);
            this.currentTragetURL = URL;
        }
        
    }
}
使用方法如下:
import com.FSC.UI.InterActiveObject.DisplayObjectContainer.LoadLine.*;
var l:LoadLine = LoadLine.getInstance();
l.addEventListener(LoadLineEvent.ONLOADLINECOMPLETE,onComplete);
l.addEventListener(ProgressEvent.PROGRESS,onProgress);
//插入可用的url
l.loadFile(new URLRequest("http://static16.photo.sina.com.cn/bmiddle/54a5adcd44b992988febf"));
//插入错误的url
l.loadFile(new URLRequest("http://error.error.cn/error.htm"));
//插入可用的url
l.loadFile(new URLRequest("http://static3.photo.sina.com.cn/bmiddle/54a5adcd44b9926c6faf2"));
//插入可用的url 这里插入紧急级别为0(就是最高级别的)的URL 程序会停止当前下载过程,然后执行该下载
l.loadFile(new URLRequest("https://mail.google.com/mail/help/images/logo.gif"),0);

function onComplete(e:LoadLineEvent) {
       trace(e.currentTragetURL.url);
       if (e.isSuccess && e.currentTragetURL.url =="http://static16.photo.sina.com.cn/bmiddle/54a5adcd44b992988febf") {
              var bbb:* = e.content;
              bbb.y=0;
              addChild(bbb);
       }
}
function onProgress(event:LoadLineProgressEvent) {
       trace("progressHandler: bytesLoaded=" + event.bytesLoaded + " bytesTotal=" + event.bytesTotal);
}

[ 本帖最后由 ppanyong 于 2008-5-8 08:31 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
在这里用Timer有什么意义??
原来5s可以下载完成的任务队列现在可能要花10s

在一个任务完成的时候直接触发下一个任务的下载就好了,为什么要等待Timer?
使用timer其实是想仿照时间片的做法,这样做的好处是为以后自定义超时时间做基础 而且该类可以方便升级成类似多线程的下载模式 0.5s的间隔我认为不是导致多花时间 另外Timer有助于使用优先级模式的插队策略。
引用:
原帖由 ppanyong 于 2008-5-6 19:59 发表
这样做的好处是为以后自定义超时时间做基础
而且该类可以方便升级成类似多线程的下载模式
0.5s的间隔我认为不是导致多花时间
另外Timer有助于使用优先级模式的插队策略。
1. 做超时判定的Timer和你现在代码里的Timer所执行的逻辑完全不同,跟你现在的Timer没有什么关系
2. Flash的编程模型不支持多线程,所谓的多线程无非是利用Loader的并发而已,这种"多线程"与Timer无关
3. 这取决于需求,而不是你的“认为”
4. 如果你不采用并发而使用队列,那么插队策略更与Timer无关

TOP

还在为头像烦恼?还在为不能关注好友动态烦忧?快来蓝色理想家园吧!
除了楼上所说的, 另外我没有看出你的代码中. static var key 的作用.
as3 不支持私有构造函数, 我想既然不支持, 比起拐弯抹角的实现他, 不如直接略过更好.

TOP

引用:
原帖由 mirycat 于 2008-5-6 23:09 发表
除了楼上所说的, 另外我没有看出你的代码中. static var key 的作用.
as3 不支持私有构造函数, 我想既然不支持, 比起拐弯抹角的实现他, 不如直接略过更好.
在这里使用key的目的确实是因为as3 不支持私有构造函数,所以没办法实现单例的效果,我这样做的目的是使程序运行环境中队列保持唯一,不会出现2个队列实例,一个实例方便调度啊。

TOP

引用:
原帖由 jinni 于 2008-5-6 22:33 发表


1. 做超时判定的Timer和你现在代码里的Timer所执行的逻辑完全不同,跟你现在的Timer没有什么关系
2. Flash的编程模型不支持多线程,所谓的多线程无非是利用Loader的并发而已,这种"多线程"与Timer无关
3. 这取 ...
感谢 神 的关注。我承认我这样的设计原理确实是想利用timer省去麻烦的链接表操作。另外我说的“多线程”就是你所说的Loader并发,相比较链接表 现在的模式 处理控制2到3个并发下载可能会相对容易一点,这点应该可以理解。

TOP

可以参见我在广州发言的时候的那个方法

做一个Dictionary存进去所有的地址字符串,
第一次load的时候就delete掉其中的响应字符串,这个操作作为一个单元操作。
以后依次在加载完成的时候对这个集合做遍历并取出任意一个地址进行加载。
知道这个集合为空的时候告知加载完成。
紫色的風.net
迷一样打不开的地址

TOP

demo
9个swf的序列加载
www.purplewind.net/layout/Main.html
紫色的風.net
迷一样打不开的地址

TOP

引用:
原帖由 enc0717 于 2008-5-7 11:15 发表
可以参见我在广州发言的时候的那个方法

做一个Dictionary存进去所有的地址字符串,
第一次load的时候就delete掉其中的响应字符串,这个操作作为一个单元操作。
以后依次在加载完成的时候对这个集合做遍历并取出 ...
还不如楼主的做法呢~

TOP

早上就测试了一下 在1M带宽 网络环境基本一直的情况下 我重复5次测试的平均结果是 队列下载200K图片9张内容用时28秒 比并发下载用16秒 几乎要过半了。我已将轮训时间下降到10ns 基本可以忽略。所以在外网情况下,要和是不要使用队列下载吧。

TOP

引用:
原帖由 jinni 于 2008-5-7 12:21 发表


还不如楼主的做法呢~
愿闻其详。
紫色的風.net
迷一样打不开的地址

TOP

我觉得没什么 “还不如”
jinni 的语气确实有点牛
是不是技术牛人都是这样?
我觉得 Einesce的做法就是去除了Timer的循环 其他和大家的想法是一样的。
不知道 Einesce在实际使用中 用过么,我测试后的结果 觉得队列下载太慢了

TOP

9楼就是实际应用。

神牛不牛是另外一个问题,不过偶比他帅,哇哈哈哈。所以他老是跟我过不去。

[ 本帖最后由 enc0717 于 2008-5-7 14:26 编辑 ]
紫色的風.net
迷一样打不开的地址

TOP

简单问题复杂化

TOP

最后的结论呢?想知道“Loader下载会无缘无故暂停,既不报超时也不报错”的解决办法。

TOP

引用:
原帖由 cc1007 于 2008-5-7 15:40 发表
最后的结论呢?想知道“Loader下载会无缘无故暂停,既不报超时也不报错”的解决办法。
你没有用心看。
标题就是解决办法。
不过是我们的实现方案有些不同的讨论而已。
紫色的風.net
迷一样打不开的地址

TOP

引用:
原帖由 enc0717 于 2008-5-7 14:25 发表
9楼就是实际应用。

神牛不牛是另外一个问题,不过偶比他帅,哇哈哈哈。所以他老是跟我过不去。
坦白讲,我不太知道你是谁,所以当然更不知道你长什么样,
只是当你出现在管理团队区的时候似乎对你有点印象,但真是想不起来了,老啦
不过既然曾“在广州发言”,莫非是某位广州市委领导?
另外我帅的人太多太多,我要是因为这个跟谁过不去,我早气死了

TOP

再发一个Loader类 加了超时的机制 还有可以定义加载程序域的设置。里面有getClass方法。
复制内容到剪贴板
代码:
package com.FSC.UI.InterActiveObject.DisplayObjectContainer.SWFLoader
{
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.events.Event;
    import flash.events.TimerEvent;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.utils.Timer;
    import com.FSC.UI.InterActiveObject.DisplayObjectContainer.SWFLoader.*
    
    /**
     * SWF加载器 增加了超时判断
     *
     * @author    eidiot (http://eidiot.net)
     * @author  扩展 andypan
     * @date    070601
     * @version    1.0.070601
     */    
    public class SWFLoader extends EventDispatcher
    {
        /** 加载到子域 */
        public static var TARGET_CHILD : String = "child";
        /** 加载到同域 */
        public static var TARGET_SAME : String = "same";
        /** 加载到新域 */
        public static var TARGET_NEW : String = "new";
        
        /*计时器*/
        private var _timer:Timer;
        /*监控时间*/
        private var _dtime:Number=10000;
        private var _currentUrl:String;
        private var context : LoaderContext;
        private var loader : Loader;
        
        public function set dTime(v:Number):void
        {
            _dtime = v;
            if(_timer==null){
                _timer = new Timer(_dtime)
            }else{
                _timer.stop();
                _timer.delay = _dtime;
                _timer.reset();
            }
        }
        
        public function get dTime():Number
        {
            return _dtime;
        }
        
        /**
         * 构造函数
         */        
        public function SWFLoader()
        {
            super();
        }
        
        /**
         * 加载SWF
         *
         * @param p_url        SWF地址
         * @param p_target    加载到哪个ApplicationDomain
         */        
        public function load(p_url : String, p_target = "child") : void
        {
            loader = new Loader();
            context = new LoaderContext();
            switch (p_target)
            {
                case SWFLoader.TARGET_CHILD :
                    context.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
                    break;
                case SWFLoader.TARGET_SAME :
                    context.applicationDomain = ApplicationDomain.currentDomain;
                    break;
                case SWFLoader.TARGET_NEW :
                    context.applicationDomain = new ApplicationDomain();
                    break;
            }
            _currentUrl = p_url;    
            _try();
        }
        
        /**
         * 轮询下载过程中的变化
         */
        private function onTimer(e:TimerEvent){
            if(e.currentTarget.currentCount>2){
                //trace("warning:下载超时");
                removeLoadEvent(loader.loaderInfo);
                loader.close();
                this.dispatchEvent(new LoadEvent(LoadEvent.OVERTIME,null));
            }    
        }
        
        private function _try(){
            this.initLoadEvent(loader.contentLoaderInfo);
            loader.load(new URLRequest(_currentUrl), context);
        }
        
        /**
         * 获取当前ApplicationDomain内的类定义
         *
         * @param p_name    类名称,必须包含完整的命名空间,如 net.eidiot.net.SWFLoader
         * @param p_info    加载swf的LoadInfo,不指定则从当前域获取
         * @return            获取的类定义,如果不存在返回null
         */        
        public function getClass(p_name : String, p_info : LoaderInfo = null) : Class
        {
            try
            {
                if (p_info == null)
                    return ApplicationDomain.currentDomain.getDefinition(p_name) as Class;
                return p_info.applicationDomain.getDefinition(p_name) as Class;
            } catch (p_e : ReferenceError)
            {
                trace("warning:定义 " + p_name + " 不存在");
                return null;
            }
            return null;
        }
        /**
         * 允许用户重试下载
         */
        public function reTry():void
        {
            _try();
            if(_timer!=null){
                _timer.reset();
                _timer.start();
            }
        }
        
        /**
         * @private
         * 监听加载事件
         *
         * @param p_info    加载对象的LoaderInfo
         */        
        private function initLoadEvent(p_info : LoaderInfo) : void
        {
            _timer = new Timer(_dtime);
            _timer.addEventListener(TimerEvent.TIMER,onTimer);
            p_info.addEventListener(ProgressEvent.PROGRESS, this.onProgress);
            p_info.addEventListener(Event.COMPLETE, this.onComplete);
            p_info.addEventListener(IOErrorEvent.IO_ERROR, this.onError);
            p_info.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.onError);
        }
        /**
         * @private
         * 移除加载事件
         *
         * @param p_info    加载对象的LoaderInfo
         */    
        private function removeLoadEvent(p_info : LoaderInfo) : void
        {
            if(_timer!=null)
                _timer.removeEventListener(TimerEvent.TIMER,onTimer);
            p_info.removeEventListener(Event.COMPLETE, this.onComplete);
            p_info.removeEventListener(ProgressEvent.PROGRESS, this.onProgress);
            p_info.removeEventListener(IOErrorEvent.IO_ERROR, this.onError);
            p_info.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, this.onError);
        }
        
        /* 加载事件 */
        private function onComplete(p_e : Event) : void
        {
            var info : LoaderInfo = p_e.currentTarget as LoaderInfo;
            this.removeLoadEvent(info);
            this.dispatchEvent(new LoadEvent(LoadEvent.COMPLETE, info));
        }
        /**
         * 下载过程
         */
        private function onProgress(p_e : ProgressEvent) : void
        {
            if(_timer!=null){
                _timer.reset();
                _timer.start();
            }
            this.dispatchEvent(p_e);
        }
        private function onError(p_e : Event) : void
        {
            var info : LoaderInfo = p_e.currentTarget as LoaderInfo;
            this.removeLoadEvent(info);
            this.dispatchEvent(p_e);
        }
    }
}

TOP

建议楼主改进一下排队机制:同时由2个方向并发进行加载,一个从队列开始向中间,另一个从队列末尾向中间
这样往往可以更充分的利用带宽。

TOP

这应该不是FLASH的BUG吧,应该是IE的限制吧?
最好不要用TIMER和SETINTERVAL。
怎么做都好。
--oo-----------------------------------

TOP

我还以为是并发下载....搞半天还是队列
我要威望....>

TOP

TOP

队列下载也有它的用处。一切从需求出发。
你可以写一个类,提供参数控制同时并发的列数(当有N个下载任务时可以指定最多1到N个并发)。
同时加上超时处理的设置(比如,超时重试[重试次数],或者超时当作complete处理等等),这个类就比较完善了。
hey~同志们还好么?

TOP

借楼主的帖子问个问题
前几天做上传的时候,也是用队列保存上传对象.如果队列中的上传对象是一次选择的上传队列的话没问题,如果队列中保存的是多次选择上传队列的时候就中途会卡住也不报错.选择的filereferencelist是在初始化的时候定义好的,也就是同一个filereferencelist

TOP