const {ccclass, property} = cc._decorator; /** * 热更新 */ @ccclass export default class HotUpdate extends cc.Component { @property(cc.Asset) public manifestUrl:cc.Asset = null; @property(cc.Node) public gxNode:cc.Node = null;//更新load // @property(cc.Label) // public mInfo:cc.Label = null; @property(cc.Node) public mMyNode:cc.Node = null; @property(cc.Label) public mV1:cc.Label = null; @property(cc.Label) public mV2:cc.Label = null; @property(cc.Label) public mLoadBfb:cc.Label = null; @property(cc.Label) public fileCount:cc.Label = null;//文件数量 @property(cc.ProgressBar) public byteProgress:cc.ProgressBar = null;//下载进度 // @property(cc.Node) // mLogContent:cc.Node = null;//更新日志 private _updating:boolean = false; private _am:jsb.AssetsManager = null;//jsb.AssetsManager private _canRetry:boolean = false;//是否可以重新下载 private _storagePath:string;//存储路径 private versionCompareHandle: (versionA: string, versionB: string) => number = null!; public login(){ if(cc.sys.isNative){ // cc.director.loadScene('anysdkLogin'); cc.director.loadScene('main'); }else{ cc.director.loadScene('main'); } } public exitGame(){ cc.game.end(); } public onLoad(){ let proto = cc.ProgressBar.prototype; let updateBarStatus = proto['_updateBarStatus']; proto['_updateBarStatus'] = function () { if (this.progress == 0) { this.progress = 0.001; } updateBarStatus.bind(this)(); } this.mV1.node.active = false this.mV2.node.active = false cc.game.setFrameRate(35); this.gxNode.active = false; if (!cc.sys.isNative) { this.login(); return; } this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'mmtg-remote-asset'); cc.log('Storage path for remote asset : ' + this._storagePath); let myself = this; this.versionCompareHandle = function (versionA: string, versionB: string) { console.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB); myself.mV1.node.active = true myself.mV2.node.active = true myself.mV1.string = '客户端版本:'+versionA myself.mV2.string = '服务器版本:'+versionB var vA = versionA.split('.'); var vB = versionB.split('.'); for (var i = 0; i < vA.length; ++i) { var a = parseInt(vA[i]); var b = parseInt(vB[i] || '0'); if (a === b) { continue; } else { return a - b; } } if (vB.length > vA.length) { return -1; } else { return 0; } }; // Init with empty manifest url for testing custom manifest this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle); // Setup the verification callback, but we don't have md5 check function yet, so only print some message // Return true if the verification passed, otherwise return false this._am.setVerifyCallback((path, asset)=> { // When asset is compressed, we don't need to check its md5, because zip file have been deleted. var compressed = asset.compressed; // Retrieve the correct md5 value. var expectedMD5 = asset.md5; // asset.path is relative path and path is absolute. var relativePath = asset.path; // The size of asset file, but this value could be absent. var size = asset.size; if (compressed) { // this.mInfo.string = "Verification passed : " + relativePath; return true; }else { // this.mInfo.string = "Verification passed : " + relativePath + ' (' + expectedMD5 + ')'; // if(!jsb.fileUtils.isFileExist(path)){ // return false; // } // let dataBinary = jsb.fileUtils.getDataFromFile(path); // let buffer = nodeJsBufferTool.from(dataBinary); // let md5Code = crypto.createHash('md5').update(buffer).digest('hex'); // if(md5Code !== asset.md5){ // cc.log('不一致文件:',path); // cc.log('md5Code = ',md5Code); // cc.log('asset = ',asset.md5); // // cc.log('dataBinary : ',dataBinary); // // cc.log('strData : ',strData); // return false; // } // cc.log('一致文件 :',path); // return true; // this.mInfo.string = "Verification passed : " + relativePath + ' (' + expectedMD5 + ')'; if(!jsb.fileUtils.isFileExist(path)){ return false; } let fileSize = jsb.fileUtils.getFileSize(path); if(fileSize != asset.size){ return false; } return true; } }); // this.mInfo.string = 'Hot update is ready, please check or directly update.'; if (cc.sys.os === cc.sys.OS_ANDROID) { // Some Android device may slow down the download process when concurrent tasks is too much. // The value may not be accurate, please do more test and find what's most suitable for your game. this._am.setMaxConcurrentTask(10); // this.mInfo.string = "Max concurrent tasks count have been limited to 2"; }else{ this._am.setMaxConcurrentTask(15); } this.fileCount.string = ''; this.mLoadBfb.string = ''; this.byteProgress.progress = 0; //判断是否需要下载新包更新 //1.获取app版本 //2.获取服务器最新版本 //3.打开下载页 // if (cc.sys.os === cc.sys.OS_ANDROID) { // this.getServerAppVersion((v1)=>{ // if(v1){ // let v2 = this.getAppVersion(); // let state = this.versionCompareHandle(v1,v2); // if(state > 0){ // //如果有新版本 // this.updateBtn.active = true; // }else{ // this.checkUpdate(); // } // }else{ // this.checkUpdate(); // } // }); // }else{ // this.checkUpdate(); // } this.checkUpdate(); } public checkUpdate() { if (this._updating) { // cc.log('Checking or updating ...'); this.pushLog('Checking or updating ...'); return; } if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { var url = this.manifestUrl.nativeUrl; this._am.loadLocalManifest(url); } if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) { // cc.log('Failed to load local manifest ...'); this.pushLog('Failed to load local manifest ...'); return; } this._am.setEventCallback(this.checkCb.bind(this)); this._am.checkUpdate(); this._updating = true; } public hotUpdate () { this.gxNode.active = true; this.fileCount.string = 'loading...'; if (this._am && !this._updating) { this._am.setEventCallback(this.updateCb.bind(this)); if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { // Resolve md5 url var url = this.manifestUrl; if (cc.loader.md5Pipe) { url = cc.loader.md5Pipe.transformURL(url); } this._am.loadLocalManifest(url); } this._am.update(); this._updating = true; } } public checkCb(event){ cc.log('Code: ' + event.getEventCode()); switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: cc.log("No local manifest file found, hot update skipped."); this.login(); break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: cc.log("Fail to download manifest file, hot update skipped."); this.login(); break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: cc.log("Already up to date with the latest remote version."); this.login(); break; case jsb.EventAssetsManager.NEW_VERSION_FOUND: cc.log("New version found, please try to update."); // this.panel.fileProgress.progress = 0; // this.panel.byteProgress.progress = 0; // this.updateBtn.active = true; // cc.log('getTotalBytes : ',event.getTotalBytes()); this._am.setEventCallback(null); this._updating = false; this.hotUpdate(); break; default: return; } } public updateCb(event) { var needRestart = false; var failed = false; switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: cc.log('No local manifest file found, hot update skipped.'); failed = true; break; case jsb.EventAssetsManager.UPDATE_PROGRESSION: // this.byteProgress.progress = event.getPercent(); let percentByFile = event.getPercentByFile(); if(!percentByFile){ percentByFile = 0; }else{ if(event.getDownloadedFiles() < event.getTotalFiles()-1){ this.fileCount.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles(); }else{ this.fileCount.string = '文件解压中...'; } } this.byteProgress.progress = percentByFile; this.mMyNode.x = 915*percentByFile // if(event.getPercent()){ // this.mLoadBfb.string = Math.floor(event.getPercentByFile()*100)+'%'; // } // this.mLoadBfb.string = Math.floor(percentByFile*100)+'%'; // var msg = event.getMessage(); // if (msg) { // // this.panel.info.string = 'Updated file: ' + msg; // cc.log(event.getPercent()/100 + '% : ' + msg); // } break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: cc.log('Fail to download manifest file, hot update skipped.'); failed = true; break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: cc.log('Already up to date with the latest remote version.'); failed = true; break; case jsb.EventAssetsManager.UPDATE_FINISHED: this.pushLog('Update finished. ' + event.getMessage()); needRestart = true; break; case jsb.EventAssetsManager.UPDATE_FAILED: this.pushLog('Update failed. ' + event.getMessage()); // this.mInfo.string = '更新失败,是否重试?'; this._updating = false; this._canRetry = true; this.retry(); // this.updateBtn.active = true; // needRestart = true; // this.hotUpdate(); break; case jsb.EventAssetsManager.ERROR_UPDATING: this.pushLog('Asset update error: ' + event.getAssetId() + ', ' + event.getMessage()); // needRestart = true; // this.hotUpdate(); break; case jsb.EventAssetsManager.ERROR_DECOMPRESS: cc.log(event.getMessage()); needRestart = true; break; default: break; } if (failed) { this._am.setEventCallback(null); this._updating = false; this.login(); } if (needRestart) { this._am.setEventCallback(null); // Prepend the manifest's search path var searchPaths = jsb.fileUtils.getSearchPaths(); var newPaths = this._am.getLocalManifest().getSearchPaths(); cc.log(JSON.stringify(newPaths)); Array.prototype.unshift.apply(searchPaths, newPaths); // This value will be retrieved and appended to the default search path during game startup, // please refer to samples/js-tests/main.js for detailed usage. // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect. cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths)); jsb.fileUtils.setSearchPaths(searchPaths); cc.audioEngine.stopAll(); setTimeout(() => { cc.game.restart(); }, 1000) } } // public loadCustomManifest() { // if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { // var manifest = new jsb.Manifest(customManifestStr, this._storagePath); // this._am.loadLocalManifest(manifest, this._storagePath); // this.panel.info.string = 'Using custom manifest'; // } // } public retry(){ if (!this._updating && this._canRetry) { this._canRetry = false; // cc.log('Retry failed Assets...'); this.pushLog('Retry failed Assets...'); this._am.downloadFailedAssets(); } } public onDestroy () { if (this._am) { this._am.setEventCallback(null); } } public pushLog(msg){ // let node = new cc.Node(); // node.anchorX = 0; // node.height = 32; // node.color = cc.color(0,0,0); // let log = node.addComponent(cc.Label) // log.overflow = cc.Label.Overflow.RESIZE_HEIGHT; // log.string = msg; // log.node.parent = this.mLogContent; } public onclickCancel(){ this.checkUpdate(); } }