AIR 移动开发资源文件版本管理

游戏的版本管理本质上是游戏发布新版本时的资源管理。目前,许多移动端游戏在发布新版本时,都要求玩家重新下载APK,这是一件体验很差的事情,尤其是当APK文件动不动就有上100M时,这种差的体验会被加剧的增强。而更多的情况是新的版本文件和老的版本文件的差别可能仅仅只是几个资源文件(当然也有可能有代码的更新,对于swf文件来说,代码也只是一种资源)。所有,有没有办法,在更新版本的时候只更新我们改动过的资源文件?

未命名-1

整个框架分为三个层次:

第一层是一个游戏的壳子,它只包含大约两个文件,一个主文页Game.as和一个版本检查文件Version.as。只有这两个文件会被打包进APK里面。所以打包的APK大小加上AIR环境10M左右,非常小。

每次游戏启动时,Game会启动版本检查Version。通过远程加载一个Version.xml来获取所有资源文件的最新版本号,并与本地的资源版本文件进行对比,如果本地的资源文件不存在或者本地的资源文件不是最新版本,则从web上下载最新的资源文件并对本地的资源进行覆盖。当然这里的资源文件也包括第二层的主文件。当所有资源文件都检查更新完毕,Game会关闭Version,并启动第二层主文件。至于第二层主文页是什么,第一层完全不必知道。这样可以保证最大的灵活性。因为你可以随意的更新主文页内容

第二层是游戏的核心内容,包括游戏的主要框架和逻辑代码。具有很强的游戏的针对性。

第三层是游戏的不同模块。它依赖于第二层核心框架。

package
{
    import com.furusystems.dconsole2.DConsole;
    
    import flash.display.Bitmap;
    import flash.display.Loader;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    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.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.utils.ByteArray;

    /**
     * 版本管理 
     * @author lvmangogo
     * 
     */    
    public class Version extends EventDispatcher
    {
        /**
         * version 的名字 
         */        
        private var mSource:String;
        /**
         * 远程version
         */        
        private var mRemote:String;
        private var mState:int;
        /**
         * 本地 版本 
         */        
        private var mLocal:Object;
        /**
         * 远程版本 
         */        
        private var mServer:Object;
        private var mDLL:Object;
        private var mDebug:Boolean;
        
        private static const LOAD_LOCAL:int = 0;
        private static const LOAD_SERVER:int = 1;
        private static const LOAD_RESOURCE:int = 2;
        private static const OPEN_RESOURCE:int = 3;

        /**
         * 需要更新的列表 
         */        
        private var mLoadItems:Array;
        /**
         * 当前正在更新的资源 
         */        
        private var mResource:String
        /**
         * 当前资源的名字 
         */        
        private var mName:String;
        /**
         * 远程资源目录 
         */        
        private var mRoot:String;
        
        /**
         * 所有要更新的资源数目 
         */        
        private var mAllItem:int;
        
        private var mFile:File;
        private var mStream:FileStream;
        private var mURLLoader:URLLoader;

        /**
         * URL -> Key 
         */        
        private var mObject:Object;
        
        /**
         * Key -> Resource 
         */        
        private var mResult:Object;

        private var mLoader:Loader;

        private var mLoadContent:LoaderContext;
        
        private var FILE:File;
        
        private var mGroup:Object;
        /**
         *  
         * @param source  本地的版本文件
         * @param remote  远程的版本文件
         * @param root    资源更新址
         * @param debug
         * 
         */        
        public function Version(source:String,remote:String,root:String,debug:Boolean = false,file:File = null)
        {
            this.mSource = source;
            this.mRemote = remote;
            this.mRoot = root;
            this.mDebug = debug;
            this.FILE = file;
            
            mStream = new FileStream();
            mStream.addEventListener(IOErrorEvent.IO_ERROR,onIOError);
            mStream.addEventListener(Event.COMPLETE,onComplete);
            mStream.addEventListener(ProgressEvent.PROGRESS,onProgress);
            
            mURLLoader = new URLLoader();
            mURLLoader.addEventListener(Event.COMPLETE,onComplete);
            mURLLoader.addEventListener(IOErrorEvent.IO_ERROR,onIOError);
            mURLLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onSecurityError);
            mURLLoader.addEventListener(ProgressEvent.PROGRESS,onProgress);
            
            mLoader = new Loader();
            mLoadContent = new LoaderContext();
            mLoadContent.allowCodeImport = true;
            mLoadContent.allowLoadBytesCodeExecution = true;
            mLoadContent.applicationDomain = ApplicationDomain.currentDomain;
            mLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoaderComplete);
            
            mResult = {};
        }
        
        public function get Group():Object
        {
            return mGroup;
        }

        public function set Group(value:Object):void
        {
            mGroup = value;
        }

        public function get AllItem():int
        {
            return mAllItem;
        }

        public function set AllItem(value:int):void
        {
            mAllItem = value;
        }

        public function get DLL():Object
        {
            return mDLL;
        }

        public function get Resource():Object
        {
            return mResult;
        }

        protected function onProgress(event:ProgressEvent):void
        {
            // TODO Auto-generated method stub
            //DConsole.print(event.bytesLoaded,event.bytesTotal);
            //this.dispatchEvent(event);
        }
        
        private function onSecurityError(e:SecurityErrorEvent):void
        {
            switch(mState)
            {
                case LOAD_LOCAL://本地文件
                {
                    CONFIG::debug
                    {
                        DConsole.print("本地版本文件不存在","Game","Trace");
                    }
                    mLocal = {};
                    mStream.close();
                    beginLoadServer();
                    break;
                }
                case LOAD_SERVER://远程文件
                {
                    CONFIG::debug
                    {
                        DConsole.print("更新远程服务器版本文件失败","Game","Trace");
                    }
                    mDLL={};
                    mServer = mLocal;
                    beginLoadResource();
                    break;
                }
                case LOAD_RESOURCE://远程资源
                {
                    CONFIG::debug
                    {
                        DConsole.print("更新资源失败  " + mState + ":" + mResource,"Game","Trace");
                    }
                    loadNextResource();
                    break;
                }
            }
        }
        
        protected function onIOError(event:IOErrorEvent = null):void
        {
            switch(mState)
            {
                case LOAD_LOCAL://本地文件
                {
                    CONFIG::debug
                    {
                        DConsole.print("本地版本文件不存在","Game","Trace");
                    }
                    mLocal = {};
                    mStream.close();
                    beginLoadServer();
                    break;
                }
                case LOAD_SERVER://远程文件
                {
                    CONFIG::debug
                    {
                        DConsole.print("更新远程服务器版本文件失败","Game","Trace");
                    }
                    mDLL={};
                    mServer = mLocal;
                    beginLoadResource();
                    break;
                }
                case LOAD_RESOURCE://远程资源
                {
                    CONFIG::debug
                    {
                        DConsole.print("更新资源失败  " + mState + ":" + mResource,"Game","Trace");
                    }
                    loadNextResource();
                    break;
                }
                case OPEN_RESOURCE://本地资源
                {
                    CONFIG::debug
                    {
                        DConsole.print("打开资源失败  " + mState + ":" + mResource,"Game","Trace");
                    }
                    mStream.close();
                    loadNextResource();
                    break;
                }
            }
        }
        
        
        protected function onComplete(event:Event):void
        {
            switch(mState)
            {
                case LOAD_LOCAL:
                {
                    CONFIG::debug
                    {
                        DConsole.print("获取本地版本文件成功","Game","Trace");
                    }
                    mLocal = {};
                    while(mStream.bytesAvailable)
                    {
                        mLocal[mStream.readUTF()] = mStream.readUTF();
                    }
                    mStream.close();
                    beginLoadServer();
                    break;
                }
                case LOAD_SERVER:
                {
                    CONFIG::debug
                    {
                        DConsole.print("获取远程版本文件成功","Game","Trace");
                    }
                    mServer = {};
                    mGroup = {};
                    
                    var xml:XML=new XML(mURLLoader.data);
                    var list:XMLList = xml.child("group"),temp:XMLList;
                    var object:Object;
                    for each(var group:XML in list)
                    {
                        object = {};
                        temp = group.child("resource");
                        for each(var resource:XML in temp)
                        {
                            object[resource.@name] = mServer[resource.@name]=String(resource.@value);
                        }
                        mGroup[group.@id] = object
                    }
                    
                    mDLL={};
                    list=xml.child("dll");
                    for each (xml in list)
                    {
                        mDLL[xml.@name]=String(xml.@value);
                    }
                    beginLoadResource();
                    break;
                }
                case LOAD_RESOURCE:
                {
                    CONFIG::debug
                    {
                        DConsole.print("更新资源成功  " + mState + ":" + mResource + "  开始写入文件","Game","Trace");
                    }
                    
                    mLocal[mObject[mResource]] = mResource;
                    mFile = FILE.resolvePath(mResource.split("?v=")[0])
                    CONFIG::debug
                    {
                        DConsole.print("文件路径:" + mFile.nativePath,"Game","Trace");
                    }
                    //写入文件
                    
                    mStream.open(mFile,FileMode.WRITE);
                    mStream.writeBytes(mURLLoader.data as ByteArray);
                    mStream.close();
                    
                    CONFIG::debug
                    {
                        DConsole.print("写入文件" + mFile.nativePath + "结束","Game","Trace");
                    }
                    
                    onResourceLoaded(mURLLoader.data as ByteArray);
                    break;
                }
                case OPEN_RESOURCE:
                {
                    CONFIG::debug
                    {
                        DConsole.print("打开资源成功  " + mState + ":" + mResource + "","Game","Trace");
                    }
                    var byte:ByteArray = new ByteArray();
                    mStream.readBytes(byte);
                    onResourceLoaded(byte);
                    break;
                }
            }
        }
        
        private function onResourceLoaded(byte:ByteArray):void
        {
            var extenstion:String = mName.substring(mName.lastIndexOf(".") + 1);
            switch(extenstion)
            {
                case "jpg":
                case "jpeg":
                case "png":
                case "gif":
                case "swf":
                {
                    mLoader.loadBytes(byte,mLoadContent);
                    break;
                }
                case "xml":
                {
                    mResult[mObject[mResource]] = new XML(byte);
                    loadNextResource();
                    break;
                }
                case "text":
                {
                    byte.position = 0;
                    mResult[mObject[mResource]] = byte.readMultiByte(byte.bytesAvailable,"utf-8");
                    loadNextResource();
                    break;
                }
                default:
                {
                    mResult[mObject[mResource]] = byte;
                    loadNextResource();
                }
            }
            this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS,false,false,mAllItem - mLoadItems.length,mAllItem));
        }
        
        private function onLoaderComplete(event:Event):void
        {
            if(mLoader.content is Bitmap)
            {
                mResult[mObject[mResource]] = (mLoader.content as Bitmap).bitmapData;
            }
            else
            {
                mResult[mObject[mResource]] = mLoader.content;
            }
            loadNextResource();
        }
        
        /**
         * 开始加再需要更新的资源文件 
         * 如果远程版本文件的资源 与 本地不一样  或者本地没有这个资源  需要更新
         */        
        private function beginLoadResource():void
        {
            if(mDebug) mLocal = mServer;
            
            mLoadItems = [];
            mObject = {};
            var cDLL:String = null;
            for(var string:String in mServer)
            {
                ////如果本地没有这个资源  或者本地资源与服务器资源不一至  或者 该资源属于初始化资源
                if(mLocal[string] == null || mLocal[string] != mServer[string] || mGroup["init"][string])
                {
                    if(string == "client")
                    {
                        cDLL = mServer[string]
                    }
                    else
                    {
                        mLoadItems.push(mServer[string]);
                    }
                    mObject[mServer[string]] = string;
                }
            }
            if(cDLL != null) mLoadItems.push(cDLL);
            
            mAllItem = mLoadItems.length;
            loadNextResource();
            CONFIG::debug
            {
                DConsole.print("总共需要更新的资源数 " + mAllItem + "","Game","Trace");
            }
        }
        
        private function loadNextResource():void
        {
            mState = LOAD_RESOURCE;
            if(mLoadItems.length)
            {
                mResource = mLoadItems.pop();
                mName = mResource.split("?v=")[0];
                if(mLocal[mObject[mResource]] == null || mLocal[mObject[mResource]] != mServer[mObject[mResource]])//如果本地没有这个资源  或者本地资源与服务器资源不一至
                {
                    mURLLoader.dataFormat = URLLoaderDataFormat.BINARY;
                    mURLLoader.load(new URLRequest(mRoot + mResource));
                }
                else//直接加载本地的
                {
                    mFile = FILE.resolvePath(mName);
                    if(!mFile.exists)//数据目录里没有??
                    {
                        mFile = File.applicationDirectory.resolvePath(mName);
                    }
                    
                    if(!mFile.exists)//如吧 这个文件我确实没有找到  可能哪个地方出了问题
                    {
                        mURLLoader.dataFormat = URLLoaderDataFormat.BINARY;
                        mURLLoader.load(new URLRequest(mRoot + mResource));
                    }
                    else
                    {
                        mState = OPEN_RESOURCE;
                        mStream.openAsync(mFile,FileMode.READ);
                    }
                }
            }
            else
            {
                complete();
                this.dispatchEvent(new Event(Event.COMPLETE));
            }
        }
        
        /**
         * 加载远程版本文件 
         * 
         */        
        private function beginLoadServer():void
        {
            mURLLoader.load(new URLRequest(mRemote));
            mURLLoader.dataFormat = URLLoaderDataFormat.TEXT;
            mState = LOAD_SERVER;
        }
        
        //============================================================================================
        //  public
        //============================================================================================
        public function update():void
        {
            mState = LOAD_LOCAL;
            mFile = FILE.resolvePath(mSource);
            if(!mFile.exists)
            {
                mFile = File.applicationDirectory.resolvePath(mSource);
            }
            mStream.openAsync(mFile,FileMode.READ);
        }
        
        
        /**
         * 加载完成后 重新生成本地版本文件 
         * 
         */        
        public function complete():void
        {
            mFile = FILE.resolvePath(mSource)
            mStream.open(mFile,FileMode.WRITE);
            
            const byte:ByteArray = new ByteArray();
            for(var key:String in mLocal)
            {
                byte.writeUTF(key);
                byte.writeUTF(mLocal[key]);        
            }
            
            mStream.writeBytes(byte);
            mStream.close();
        }
        
        public function dispose():void
        {
            mStream.removeEventListener(IOErrorEvent.IO_ERROR,onIOError);
            mStream.removeEventListener(Event.COMPLETE,onComplete);
            mStream.removeEventListener(ProgressEvent.PROGRESS,onProgress);
            mStream = null;
            mFile = null;
            
            mLoader.contentLoaderInfo.removeEventListener(Event.COMPLETE,onLoaderComplete);
            mLoader = null;
            mLoadContent = null;
            
            mURLLoader.removeEventListener(Event.COMPLETE,onComplete);
            mURLLoader.removeEventListener(IOErrorEvent.IO_ERROR,onIOError);
            mURLLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR,onSecurityError);
            mURLLoader.removeEventListener(ProgressEvent.PROGRESS,onProgress);
            
            mURLLoader = null;
            mLocal = null;
            mServer = null;
            mDLL = null;
            mObject = null;
            mResult = null;
            FILE = null;
        }
    }
}

 

posted @ 2013-09-03 14:42  川上约  阅读(304)  评论(0)    收藏  举报