import { _decorator, Component, Node, Label, ProgressBar, Asset, game, sys, EventHandler } from 'cc'; import { JSB } from 'cc/env'; import { Log } from '../core/utils/Log'; const { ccclass, property } = _decorator; @ccclass('HotUpdate') export class HotUpdate extends Component { @property({ tooltip: "project.manifest文件", type: Asset }) manifestUrl: Asset = null!; @property({ tooltip: "检查版本后是否自动更新,否则请手动调用hotUpdate方法" }) autoUpdate: boolean = true; @property({ tooltip: "检查版本回调", type: EventHandler }) onCheck: EventHandler; @property({ tooltip: "热更新出错回调", type: EventHandler }) onUpdateError: EventHandler; @property({ tooltip: "热更新进度回调,最大值为1", type: EventHandler }) onUpdateProgress: EventHandler; @property({ tooltip: "热更新检查完成回调(只有无需热更新才会有此回调,否则是重启回调)", type: EventHandler }) onVersionIsNew: EventHandler; @property({ tooltip: "热更新完成回调后会重启应用", type: EventHandler }) onRestart: EventHandler; public versionIsNew: boolean = false; private am: jsb.AssetsManager = null!; private checkCb(event: any) { console.log('Code: ' + event.getEventCode()); this.onCheck && this.onCheck.emit([event]); switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: Log.warn("找不到manifest文件,热更新跳过"); break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: Log.warn("无法下载manifest文件,热更新跳过"); break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: Log.log("热更新检查完成,已经是最新版本"); this.versionIsNew = true; this.onVersionIsNew && this.onVersionIsNew.emit([event]); break; case jsb.EventAssetsManager.NEW_VERSION_FOUND: Log.log(`发现最新版本,即将开始热更新。总共 ${Math.ceil(this.am.getTotalBytes() / 1024)} KB`); if (this.autoUpdate) { this.hotUpdate(); return; } break; default: return; } this.am.setEventCallback(null!); } private updateCb(event: any) { var needRestart = false; var failed = false; switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: Log.warn("找不到manifest文件,热更新跳过"); this.onUpdateError && this.onUpdateError.emit([event]); failed = true; break; case jsb.EventAssetsManager.UPDATE_PROGRESSION: Log.log(`正在更新,进度: ${event.getPercent()}`); this.onUpdateProgress && this.onUpdateProgress.emit([event]); var msg = event.getMessage(); if (msg) { Log.warn(`热更新失败 ${msg}`); this.onUpdateError && this.onUpdateError.emit([event]); } break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: Log.warn("无法下载manifest文件,热更新跳过"); this.onUpdateError && this.onUpdateError.emit([event]); failed = true; break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: Log.log("热更新检查完成,已经是最新版本"); failed = true; this.versionIsNew = true; this.onVersionIsNew && this.onVersionIsNew.emit([event]); break; case jsb.EventAssetsManager.UPDATE_FINISHED: Log.log("热更新完成"); needRestart = true; this.onRestart && this.onRestart.emit([event]); break; case jsb.EventAssetsManager.UPDATE_FAILED: Log.warn(`热更新失败 ${event.getMessage()}`); this.onUpdateError && this.onUpdateError.emit([event]); break; case jsb.EventAssetsManager.ERROR_UPDATING: Log.warn(`资源更新失败,资源编号:${event.getAssetId()},错误信息:${event.getMessage()}`); this.onUpdateError && this.onUpdateError.emit([event]); break; case jsb.EventAssetsManager.ERROR_DECOMPRESS: Log.log(`${event.getMessage()}`); failed = true; this.onUpdateError && this.onUpdateError.emit([event]); break; default: break; } if (failed) { this.am.setEventCallback(null!); } if (needRestart) { this.am.setEventCallback(null!); // Prepend the manifest's search path var searchPaths = jsb.fileUtils.getSearchPaths(); var newPaths = this.am.getLocalManifest().getSearchPaths(); console.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. localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths)); jsb.fileUtils.setSearchPaths(searchPaths); // restart game. setTimeout(() => { game.restart(); }, 1000) } } /** * 检查热更新 * @returns */ public checkUpdate() { 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()) { Log.warn("加载manifest文件失败"); return; } this.am.setEventCallback(this.checkCb.bind(this)); this.am.checkUpdate(); } /** * 执行热更新 */ public hotUpdate() { this.am.setEventCallback(this.updateCb.bind(this)); if (this.am.getState() === jsb.AssetsManager.State.UNINITED) { var url = this.manifestUrl.nativeUrl; this.am.loadLocalManifest(url); } this.am.update(); } // use this for initialization onLoad() { // Hot update is only available in Native build if (!JSB) { this.versionIsNew = true; return; } // Setup your own version compare handler, versionA and B is versions in string // if the return value greater than 0, versionA is greater than B, // if the return value equals 0, versionA equals to B, // if the return value smaller than 0, versionA is smaller than B. // Init with empty manifest url for testing custom manifest this.am = new jsb.AssetsManager('', ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'happyFarm-remote-asset'), (versionA: string, versionB: string) => { console.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + 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; } }); // 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(function (path: string, asset: any) { // 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) { Log.log("Verification passed : " + relativePath); return true; } else { Log.log("Verification passed : " + relativePath + ' (' + expectedMD5 + ')'); return true; } }); this.checkUpdate(); Log.log('Hot update is ready, please check or directly update.'); } onDestroy() { JSB && this.am.setEventCallback(null!); } }