使用方法:
1、所有js必須使用zip打包,但不強求只打包為1個文件。但不同zip不要有重復的js。
2、非js可以用zip,也可以直接列出。
由於確保所有資源都下載完成后才解壓js,所以玩家即使N次更新失敗,還是會妥妥的停留在上一版。
一、cocos2d-js 動態更新的基本思路
動態更新的好處不言而喻,不需要重新上架審核,能節省很多時間,也能讓用戶盡快使用上最新的版本,減少下載的成本。
- 官方BETA版本后提供了AssetsManager類,可以完成動態更新的步驟,說明:https://github.com/chukong/cocos-docs/blob/master/manual/framework/html5/v3/assets-manager/zh.md
- cocos2d程序安裝后,以Android為例,程序存在於2個地方:apk安裝目錄(/data/dalvik-cache),apk數據目錄(/data/data/[包名])
- AssetsManager根據projec.manifest文件的配置,把新文件下載到apk數據目錄,並默認把這個下載目錄設置為最優先搜索的地方。
- project.json文件中指定的js文件,將在程序main.js啟動前就加載完。main.js不需要寫到這個list中。所以需要動態更新的js,不能列在這個json中。
- 除了main.js外,把其他js列到一個文件中:src/jsList.js。AssetsManager檢查完之后,先加載這個jsList.js,然后根據里邊的配置再加載全部js。
二、程序發布步驟
本文參考:https://github.com/faint2death/cocos2d-js/blob/master/assetsmanager.md,但配置的方式不一樣,本文更偏於使用官方的配置。按參考文章的寫法,更新多次之后,project.manifest文件會很大,這影響用戶更新的速度。
1、修改main.js,加載AssetsManager功能
cc.game.onStart = function(){ cc.view.setDesignResolutionSize(800, 450, cc.ResolutionPolicy.SHOW_ALL); cc.view.resizeWithBrowserSize(true); var failCount = 0; var maxFailCount = 1; //最大錯誤重試次數 /** * 自動更新js和資源 */ var AssetsManagerLoaderScene = cc.Scene.extend({ _am:null, _progress:null, _percent:0, run:function(){ if (!cc.sys.isNative) { this.loadGame(); return; } var layer = new cc.Layer(); this.addChild(layer); this._progress = new cc.LabelTTF.create("update 0%", "Arial", 12); this._progress.x = cc.winSize.width / 2; this._progress.y = cc.winSize.height / 2 + 50; layer.addChild(this._progress); var storagePath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "./"); this._am = new jsb.AssetsManager("res/project.manifest", storagePath); this._am.retain(); if (!this._am.getLocalManifest().isLoaded()) { cc.log("Fail to update assets, step skipped."); this.loadGame(); } else { var that = this; var listener = new cc.EventListenerAssetsManager(this._am, function(event) { switch (event.getEventCode()){ case cc.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: cc.log("No local manifest file found, skip assets update."); that.loadGame(); break; case cc.EventAssetsManager.UPDATE_PROGRESSION: that._percent = event.getPercent(); cc.log(that._percent + "%"); var msg = event.getMessage(); if (msg) { cc.log(msg); } break; case cc.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case cc.EventAssetsManager.ERROR_PARSE_MANIFEST: cc.log("Fail to download manifest file, update skipped."); that.loadGame(); break; case cc.EventAssetsManager.ALREADY_UP_TO_DATE: cc.log("ALREADY_UP_TO_DATE."); that.loadGame(); break; case cc.EventAssetsManager.UPDATE_FINISHED: cc.log("Update finished."); that.loadGame(); break; case cc.EventAssetsManager.UPDATE_FAILED: cc.log("Update failed. " + event.getMessage()); failCount++; if (failCount < maxFailCount) { that._am.downloadFailedAssets(); } else { cc.log("Reach maximum fail count, exit update process"); failCount = 0; that.loadGame(); } break; case cc.EventAssetsManager.ERROR_UPDATING: cc.log("Asset update error: " + event.getAssetId() + ", " + event.getMessage()); that.loadGame(); break; case cc.EventAssetsManager.ERROR_DECOMPRESS: cc.log(event.getMessage()); that.loadGame(); break; default: break; } }); cc.eventManager.addListener(listener, 1); this._am.update(); cc.director.runScene(this); } this.schedule(this.updateProgress, 0.5); }, loadGame:function(){ //jsList是jsList.js的變量,記錄全部js。 cc.loader.loadJs(["src/jsList.js"], function(){ cc.loader.loadJs(jsList, function(){ cc.director.runScene(new MainScene()); }); }); }, updateProgress:function(dt){ this._progress.string = "update" + this._percent + "%"; }, onExit:function(){ cc.log("AssetsManager::onExit"); this._am.release(); this._super(); } }); var scene = new AssetsManagerLoaderScene(); scene.run(); }; cc.game.run();
2、建立jsList.js。使用固定名字jsList,這個跟第1步的代碼相對應。
var jsList = [ "src/resource.js", "src/app.js" ]
3、修改project.json。加入extensions模塊,刪除jsList的內容。
{ "project_type": "javascript", "debugMode" : 1, "showFPS" : true, "frameRate" : 60, "id" : "gameCanvas", "renderMode" : 0, "engineDir":"frameworks/cocos2d-html5", "modules" : ["cocos2d", "extensions"], //貌似這個對jsb是無效的,只有html5才有效 "jsList" : [ ] }
4、項目res目錄增加一個project.manifest文件,AssetsManager.js里會用到。url填寫自己服務器的地址,packageUrl是准備動態更新的文件的存放目錄。
{ "packageUrl" : "http://192.168.1.11:8000/res", "remoteManifestUrl" : "http://192.168.1.11:8000/res/project.manifest", "remoteVersionUrl" : "http://192.168.1.11:8000/res/version.manifest", "version" : "1.0.1", "engineVersion" : "3.0 rc0", "assets" : { }, "searchPaths" : [ ] }
5、打包程序。此時即使沒有網絡,也已經可以運行基礎版本。
三、動態更新測試
1、服務器放置version.manifest和新的project.manifest。
AssetsManager會先檢查version.manifest,判斷是否有更新。如果有,再拉取project.manifest。可以說version.manifest就是縮小版的project.manifest,只有頭幾行,兩者一致。
version.manifest:
{ "packageUrl" : "http://192.168.1.11:8000/res", "remoteManifestUrl" : "http://192.168.1.11:8000/res/project.manifest", "remoteVersionUrl" : "http://192.168.1.11:8000/res/version.manifest", "version" : "1.0.1", "engineVersion" : "3.0 rc0" }
project.manifest:
{ "packageUrl" : "http://192.168.1.11:8000/res", "remoteManifestUrl" : "http://192.168.1.11:8000/res/project.manifest", "remoteVersionUrl" : "http://192.168.1.11:8000/res/version.manifest", "version" : "1.0.1", "engineVersion" : "3.0 rc0", "assets" : { "src/app.zip" : { "md5" : "D07D260D8072F786A586A6A430D0E98B", "compressed" : true } }, "searchPaths" : [ ] }
manifest這里使用了官方說明沒有提到的compressed,src/app.zip並沒有在初始打包的程序中,這個只是更新用的。指定了compressed=true,AssetsManager下載后會自動解壓這個文件,並保留這個文件。這樣就可以減少網絡傳輸的文件大小。
app.zip壓縮的是app.js,解壓后將覆蓋初始化安裝的app.js,從而實現了動態更新。
這里可以多次更新,不斷更新version號即可,每次AssetsManager會檢查文件是否存在、文件md5是否一致,如果不存在或者md5不一致都會重新下載。
2、無需重新打包發布,直接打開cocos2d程序,可以看到update的字樣,如果打開了logcat,也可以看到對應的日志。