AIR(As3) - 通过iPad应用程序下载大文件

时间:2013-01-29 12:45:41

标签: actionscript-3 flash mobile air

首先,我想指定我不能发布源代码,因为项目很大。 我正在尝试在iPad设备上下载一个大文件(500多MB)。 最初我尝试使用URLLoader,但我意识到iPad设备的内存资源非常有限。我认为URLStream将以块的形式下载文件,而使用FileStream我可以在设备上保存这些块(如AS3: URLStream saving files to desktop?),但我错了,当我尝试下载一个大文件时设备崩溃,因为设备的RAM是不够的(更确切地说,这变得太大了:System.privateMemory) 有没有人知道如何以块的形式下载文件,是否可以不使用“套接字连接”?

提前致谢。

编辑: 这是我使用的代码(注释行是FileStream在文件下载后才关闭的版本。



    package components.streamDownloader
    {
        import flash.events.Event;
        import flash.events.EventDispatcher;
        import flash.events.IOErrorEvent;
        import flash.events.OutputProgressEvent;
        import flash.events.ProgressEvent;
        import flash.events.SecurityErrorEvent;
        import flash.filesystem.File;
        import flash.filesystem.FileMode;
        import flash.filesystem.FileStream;
        import flash.net.URLRequest;
        import flash.net.URLStream;
        import flash.system.System;
        import flash.utils.ByteArray;


    /**
     * 
     */
    public class StreamDownloader extends EventDispatcher
    {

        [Event(name="DownloadComplete", type="com.tatstyappz.net.DownloadEvent")]

        [Event(name="Error", type="com.tatstyappz.net.DownloadEvent")]


        //--------------------------------------------------------------------------
        //
        //  Constructor
        //
        //--------------------------------------------------------------------------

        public function StreamDownloader()
        {

        }


        //--------------------------------------------------------------------------
        //
        //  Variables
        //
        //--------------------------------------------------------------------------

        private var file:File;

        //private var fileStream:FileStream;

        private var urlRequest:URLRequest;

        private var urlStream:URLStream;

        private var waitingForDataToWrite:Boolean = false;


        //--------------------------------------------------------------------------
        //
        //  API
        //
        //--------------------------------------------------------------------------

        public function download(urlRequest:URLRequest, file:File):void {


            init();

            this.urlRequest = urlRequest;
            this.file = file;
            //fileStream.open(file, FileMode.WRITE);
            urlStream.load(urlRequest);
        }   


        //--------------------------------------------------------------------------
        //
        //  Event handlers
        //
        //--------------------------------------------------------------------------    

        //----------------------------------
        //  urlStream events
        //----------------------------------

        protected function urlStream_openHandler(event:Event):void
        {
            waitingForDataToWrite = false;
            dispatchEvent(event.clone());
        }

        protected function urlStream_progressHandler(event:ProgressEvent):void
        {


            trace("MEMORY:", System.totalMemoryNumber / 1024 / 1024, "MEMORY P:", System.privateMemory / 1024 / 1024, "FREE MEMORY:", System.freeMemory / 1024 / 1024, "PROGRESS:", event.bytesLoaded / event.bytesTotal );




            if(waitingForDataToWrite){
                writeToDisk();
            }       
        }

        protected function urlStream_completeHandler(event:Event):void
        {
            if(urlStream.bytesAvailable > 0)
            {
                writeToDisk();
            }
            //fileStream.close();

            destory();

            dispatchEvent(event.clone());

            // dispatch additional DownloadEvent
            dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.DOWNLOAD_COMPLETE, urlRequest, file));        
        }

        protected function urlStream_securityErrorHandler(event:SecurityErrorEvent):void
        {
            dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.ERROR, urlRequest, file, event.errorID.toString()));
            destory();
        }

        protected function urlStream_ioErrorHandler(event:IOErrorEvent):void
        {
            dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.ERROR, urlRequest, file, event.errorID.toString()));
            destory();
        }   


        //----------------------------------
        //  fileStream events
        //----------------------------------

        protected function fileStream_outputProgressHandler(event:OutputProgressEvent):void
        {
            waitingForDataToWrite = true;
        }   

        protected function fileStream_ioErrorHandler(event:IOErrorEvent):void
        {
            dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.ERROR, urlRequest, file, event.errorID.toString()));
            destory();
        }   


        //--------------------------------------------------------------------------
        //
        //  Utils
        //
        //--------------------------------------------------------------------------

        private function init():void
        {
            urlStream = new URLStream();
            //fileStream = new FileStream();

            urlStream.addEventListener(Event.OPEN, urlStream_openHandler);
            urlStream.addEventListener(ProgressEvent.PROGRESS, urlStream_progressHandler); 
            urlStream.addEventListener(Event.COMPLETE, urlStream_completeHandler);
            urlStream.addEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
            urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, urlStream_securityErrorHandler);

            //fileStream.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, fileStream_outputProgressHandler)
            //fileStream.addEventListener(IOErrorEvent.IO_ERROR, fileStream_ioErrorHandler);        
        }

        private function destory():void
        {
            urlStream.removeEventListener(Event.OPEN, urlStream_openHandler);
            urlStream.removeEventListener(ProgressEvent.PROGRESS, urlStream_progressHandler); 
            urlStream.removeEventListener(Event.COMPLETE, urlStream_completeHandler);
            urlStream.removeEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
            urlStream.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, urlStream_securityErrorHandler);

            //fileStream.removeEventListener(OutputProgressEvent.OUTPUT_PROGRESS, fileStream_outputProgressHandler)
            //fileStream.removeEventListener(IOErrorEvent.IO_ERROR, fileStream_ioErrorHandler); 

            urlStream = null;
            //fileStream = null;
        }

        private function writeToDisk():void {
            /*var fileData:ByteArray = new ByteArray();
            urlStream.readBytes(fileData, 0, urlStream.bytesAvailable);
            fileStream.writeBytes(fileData,0,fileData.length);
            waitingForDataToWrite = false;*/

            var bytes:ByteArray = new ByteArray();
            urlStream.readBytes( bytes );

            var fs:FileStream = new FileStream();
            fs.open( file, FileMode.APPEND );
            fs.writeBytes( bytes );
            fs.close();
        }




    }
    }


2 个答案:

答案 0 :(得分:3)

正如我在对csomakk的评论中所说,我已经使用URLStream分块方法通过AIR成功下载了300多个MB文件,用于桌面,iOS和Android。

伪代码:

var stream:URLStream = new URLStream();
stream.addEventListener( PROGRESS, progressHandler );
stream.addEventListener( COMPLETE, completeHandler );
stream.load( url );

private function progressHandler( e:ProgressEvent ):void {
    this.writeDataToDisk();
}

private function completeHandler( e:Event ):void {
    this.writeDataToDisk();
}

private function writeDataToDisk():void {
    var bytes:ByteArray = new ByteArray();
    this.stream.readBytes( bytes );

    var fs:FileStream = new FileStream();
    fs.open( file, FileMode.APPEND );
    fs.writeBytes( bytes );
    fs.close();
}

这个基本逻辑可以正常工作,最高可达300MB(可能还会更远。虽然我应该测试一下,现在考虑一下)。这写得相当快,所以可能会有一些错误,我肯定会做一些事情,但你明白了。但是你明白了。

如果这不起作用,我们需要你做一些事情:

  1. 发布任何错误
  2. file.size / 1024 / 1024 + "MB"之后追踪fs.close()并查看崩溃前的距离
  3. 追踪System.memory / 1024 / 1024 + "MB" after the fs.close()`以便我们可以监控内存使用情况
  4. 对于2和3,我们应该只在发生崩溃之前需要最后的跟踪语句。

    或者,您应该知道在应用程序中无法对该500MB文件执行任何操作。由于它的大小,Flash根本不会加载它。我设法逃脱300MB视频文件的唯一原因是我们从磁盘流式传输它们,而不是将所有内容存储到内存中。

答案 1 :(得分:1)

由于某些原因我不允许在Josh的回答中发表评论,因此我将我的版本添加为单独的答案。但这很大程度上取决于他的建议。 该代码也可以在GitHub上找到:https://github.com/shishenkov/ActionscriptClasses/blob/master/us/flashmx/net/LargeFileLoader.as

先决条件 - URLStream是一个很棒的类,但它有一个小故障导致内存泄漏/堆积,以防止大文件正确加载。我在这里分享的课程已经过测试,能够将一系列1.5GB文件下载到iPad Air 2(64GB)而不会出现问题。我假设较大的文件也可以,因为它实际上克服了RAM存储限制(在故障修复之前它崩溃了大约200MB)。

故障 - 你复制加载字节的原始数据字节数组永远不会被GC处理掉(如下所示:http://blogs.splunk.com/2007/11/16/flashas3-urlstream-memory-leak/)所以,解决方法是实现一个使用Josh技术和类的类确保写入后字节被丢弃。

...这是代码(注意:这仍然是预生产):

package us.flashmx.net 
{
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.HTTPStatusEvent;
    import flash.events.IEventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    import flash.net.URLRequest;
    import flash.net.URLStream;
    import flash.system.System;
    import flash.utils.ByteArray;

    /**
     * ...
     * @author  Nick Shishenkov <n@vc.am>
     */
    public class LargeFileLoader extends EventDispatcher 
    {
        private var _url:String             = "";
        private var _filePath:String        = "";
        private var _fileStream:FileStream  = new FileStream;
        private var _urlStream:URLStream    = new URLStream;
        private var _localFile:File;
        private var _bytesLoaded:Number;

        public function LargeFileLoader() 
        {
            super(null);

            //
            _urlStream.addEventListener(Event.OPEN, _onOpen, false, int.MIN_VALUE, true);
            _urlStream.addEventListener(ProgressEvent.PROGRESS, _onProgress, false, int.MIN_VALUE, true);
            _urlStream.addEventListener(Event.COMPLETE, _onComplete, false, int.MIN_VALUE, true);
            _urlStream.addEventListener(IOErrorEvent.IO_ERROR, _onError, false, int.MIN_VALUE, true);
            _urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _onSecurityError, false, int.MIN_VALUE, true);
            _urlStream.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, _onHTTPStatus, false, int.MIN_VALUE, true);
            _urlStream.addEventListener(HTTPStatusEvent.HTTP_STATUS, _onHTTPStatus, false, int.MIN_VALUE, true);
        }

        private function _onHTTPStatus(e:HTTPStatusEvent):void 
        {
            dispatchEvent(e.clone());
        }

        public function load(remoteURL:String, localPath:String, overwrite:Boolean = true):void
        {
            _url        = remoteURL;
            _filePath   = localPath;
            //
            _localFile      = new File(_filePath);
            _bytesLoaded    = 0;

            //
            if (overwrite && _localFile.exists)
            {
                _localFile.deleteFile();
            }
            //
            _urlStream.load(new URLRequest(url));
            _fileStream.open(_localFile, FileMode.APPEND);
        }

        private function _onOpen(e:Event):void 
        {
            dispatchEvent(e.clone());
        }

        private function _onSecurityError(e:SecurityErrorEvent):void 
        {
            dispatchEvent(e.clone());
        }

        private function _onError(e:IOErrorEvent):void 
        {
            dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.text));
        }

        private function _onProgress(e:ProgressEvent):void 
        {
            //
            trace(" -> _onProgress: " + _urlStream.length + " | " + e.bytesLoaded + " / " + e.bytesTotal);
            //
            _writeStreamBytes();
            //
            dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, e.bytesLoaded, e.bytesTotal));
        }

        private function _onComplete(e:Event):void 
        {
            _writeStreamBytes();
            //
            dispatchEvent(new Event(Event.COMPLETE));
        }

        private function _writeStreamBytes():void
        {
            var bytes:ByteArray = new ByteArray();
            _urlStream.readBytes( bytes );
            _fileStream.writeBytes( bytes );

            //
            _bytesLoaded    += bytes.length;

            //clear buffer (if the array stays non-null it will lead to a memmory leak
            bytes   = null;

        }

        public function get url():String 
        {
            return _url;
        }

        public function get filePath():String 
        {
            return _filePath;
        }

        public function get bytesLoaded():Number 
        {
            //_localFile.size;
            return _bytesLoaded;
        }


        public function dispose():void
        {
            try{ _fileStream.close(); }catch (err:Error){};

            //
            try{ _urlStream.close(); }catch (err:Error){};

            //
            _urlStream.removeEventListener(Event.OPEN, _onOpen);
            _urlStream.removeEventListener(ProgressEvent.PROGRESS, _onProgress);
            _urlStream.removeEventListener(Event.COMPLETE, _onComplete);
            _urlStream.removeEventListener(IOErrorEvent.IO_ERROR, _onError);
            _urlStream.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, _onSecurityError);
            _urlStream.removeEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, _onHTTPStatus);
            _urlStream.removeEventListener(HTTPStatusEvent.HTTP_STATUS, _onHTTPStatus);

            //
            _urlStream  = null;
            _fileStream = null;

            //
            System.gc();
        }
    }

}

我用Scout CC运行了几次设备测试,内存始终保持不变(没有任何累积)。我将在本周晚些时候测试一些较旧的iOS设备。记录:我使用的是Adobe AIR 24.0.0.180

这是一个示例用途:

package us.flashmx.net 
{
    import flash.display.DisplayObject;
    import flash.events.Event;
    import flash.events.ProgressEvent;

    /**
     * ...
     * @author ...
     */
    public class LargeFileLoader_DEMO extends DisplayObject 
    {
        private var _largeFilesLoader:LargeFileLoader;

        public function LargeFileLoader_DEMO() 
        {
            super();
            //
            init_largeFilesLoader("http://A.Large.File.URL/", "/The/Absolute/Local/Path");
        }

        public function dispose_largeFilesLoader():void
        {
            //
            if (_largeFilesLoader != null)
            {
                //clear listeners
                _largeFilesLoader.removeEventListener(ProgressEvent.PROGRESS, _onFileLoaderProgress);
                _largeFilesLoader.removeEventListener(Event.COMPLETE, _onFileLoaderComplete);
                //dispose
                _largeFilesLoader.dispose();
                //free mem
                _largeFilesLoader   = null;
            }           
        }

        private function init_largeFilesLoader(fURL:String, fPath:String):void
        {
            //
            _largeFilesLoader   = new LargeFileLoader;

            //
            _largeFilesLoader.addEventListener(ProgressEvent.PROGRESS, _onFileLoaderProgress, false, int.MIN_VALUE, true);
            _largeFilesLoader.addEventListener(Event.COMPLETE, _onFileLoaderComplete, false, int.MIN_VALUE, true);

            //
            _largeFilesLoader.load(fURL, fPath);
        }

        private function _onFileLoaderComplete(e:Event):void 
        {
            trace("All done!");
            dispose_largeFilesLoader();
        }

        private function _onFileLoaderProgress(e:ProgressEvent):void 
        {
            _largeFilesLoader.bytesLoaded;
        }
    }

}

......我希望有所帮助!

欢呼声 -Nick