// 热更新管理 let HotUpdateMgr = { init () { if (!cc.sys.isNative) { return; } // 更新与热更新状态 JMC.UPDATE_STATUS // UNSTART: -1, // 未开始 // DOWNLOADING: -1, // 下载中 // DOWNLOAD_PAUSE: -1, // 下载暂停[暂未使用] // DOWNLOAD_FINISHED: -1, // 下载完成 // DOWNLOAD_FAILED: -1 // 下载失败 this.status = JMC.UPDATE_STATUS.UNSTART; this.stStatus = JMC.UPDATE_STATUS.UNSTART; // 缓存下载进度 this.realTotalBytes = 0; this.downloadedBytes = 0; // 热更新资源下载到本地的路径,需要修改这里的路径需要同时修改main.js this._updatePath = G.LaunchMgr.getHotfixPath(); // project.manifest默认路径,必须在resources中 let UpdateCfg = require('UpdateCfg'); this._localDefaultManifestPath = UpdateCfg.localManifestPath; // manifest的JSON对象,方便后续进行设置url等操作 this.localManifestObj = {}; this.checkResVersion(); }, /** * 开始更新 * * @author libo * @date 2019-03-15 * @returns am.getState() UNINITED 0 UNCHECKED 1 PREDOWNLOAD_VERSION 2 DOWNLOADING_VERSION 3 VERSION_LOADED 4 PREDOWNLOAD_MANIFEST 5 DOWNLOADING_MANIFEST 6 MANIFEST_LOADED 7 NEED_UPDATE 8 READY_TO_UPDATE 9 UPDATING 10 UNZIPPING 11 UP_TO_DATE 12 FAIL_TO_UPDATE 13 */ gotoUpdate () { G.LogUtils.log('[HotUpdateMgr] gotoUpdate'); // AssetsManager对象 this.am = new jsb.AssetsManager('', this._updatePath, G.UpdateUtils.compareVersionStr); if (this.remoteManifest) { this.am.loadLocalManifest(this.localManifest, this._updatePath); this.am.loadRemoteManifest(this.remoteManifest); } else { this.am.loadLocalManifest(this.localManifest, this._updatePath); } // 判断是否初始化完毕 if (!this.am.getLocalManifest() || !this.am.getLocalManifest().isLoaded()) { G.LogUtils.error('[HotUpdateMgr] gotoUpdate 加载manifest失败'); this.am = undefined; return; } this.am.setMaxConcurrentTask(2); this.am.setEventCallback(this._updateCb.bind(this)); this.am.update(); }, /** 更新进程回调 * @author libo * @date 2019-03-15 * @param {EventAssetsManagerEx} event event.getEventCode() ERROR_NO_LOCAL_MANIFEST 0 ERROR_DOWNLOAD_MANIFEST 1 ERROR_PARSE_MANIFEST 2 NEW_VERSION_FOUND 3 ALREADY_UP_TO_DATE 4 UPDATE_PROGRESSION 5 ASSET_UPDATED 6 ERROR_UPDATING 7 UPDATE_FINISHED 8 UPDATE_FAILED 9 ERROR_DECOMPRESS 10 event.getMessage() event.getPercent() event.getPercentByFile() event.getDownloadedBytes() event.getTotalBytes() event.getDownloadedFiles() event.getTotalFiles() */ _updateCb (event) { G.LogUtils.log('[HotUpdateMgr] updateCb Code:', event.getEventCode()); if (!this.am) { return; } let status = undefined; let reason = undefined; let errorMsg = event.getMessage(); switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: { // 没有本地配置,跳过更新 status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED; reason = 'ERROR_NO_LOCAL_MANIFEST:' + errorMsg; break; } case jsb.EventAssetsManager.ASSET_UPDATED: { // 忽略该状态,使用 UPDATE_PROGRESSION 即可 return; } case jsb.EventAssetsManager.UPDATE_PROGRESSION: { // 更新进度 let totalBytes = event.getTotalBytes(); let downloadedBytes = event.getDownloadedBytes(); if (totalBytes != 0) { downloadedBytes += this.realTotalBytes - event.getTotalBytes(); } if (downloadedBytes > 0) { this.downloadedBytes = downloadedBytes; } let downloadedMb = G.UpdateUtils.bytesToMb(this.downloadedBytes).toFixed(1); let totalMb = G.UpdateUtils.bytesToMb(totalBytes).toFixed(1); G.LogUtils.log(`[HotUpdateMgr] updateCb totalBytes: ${totalBytes} totalMb: ${totalMb}Mb downloadedBytes: ${ this.downloadedBytes } downloadedMb: ${downloadedMb}Mb`); status = JMC.UPDATE_STATUS.DOWNLOADING; reason = 'UPDATE_PROGRESSION'; break; } case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: { // 下载manifest失败,跳过更新 status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED; reason = 'ERROR_DOWNLOAD_MANIFEST:' + errorMsg; break; } case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: { // 解析manifest失败,跳过更新 status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED; reason = 'ERROR_PARSE_MANIFEST:' + errorMsg; break; } case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: { // 已经更新到最终版本,跳过更新 status = JMC.UPDATE_STATUS.DOWNLOAD_FINISHED; reason = 'ALREADY_UP_TO_DATE'; break; } case jsb.EventAssetsManager.UPDATE_FINISHED: { // 更新完成,需要重启 status = JMC.UPDATE_STATUS.DOWNLOAD_FINISHED; reason = 'UPDATE_FINISHED'; break; } case jsb.EventAssetsManager.UPDATE_FAILED: { // 更新失败,需要断点续传 // (服务器需要支持 Accept-Ranges,否则从头开始下载) // this.am.downloadFailedAssets(); status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED; reason = 'UPDATE_FINISHED:' + errorMsg; break; } case jsb.EventAssetsManager.ERROR_UPDATING: { // 部分资源下载失败 status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED; reason = 'ERROR_UPDATING:' + errorMsg; break; } case jsb.EventAssetsManager.ERROR_DECOMPRESS: { // 解压缩失败 status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED; reason = 'ERROR_DECOMPRESS:' + errorMsg; break; } default: { // 忽略其它状态 return; } } this.status = status; // 统计状态更新 this._stStatusChange(status, reason); // 更新结束的处理 if (status == JMC.UPDATE_STATUS.DOWNLOAD_FINISHED) { this.am.setEventCallback(undefined); this.am = undefined; this.needRestart = true; this.updateResVersion(); cc.game.emit('e_mgr_launch_update_finished'); } else if (status == JMC.UPDATE_STATUS.DOWNLOAD_FAILED) { this.am.setEventCallback(undefined); this.am = undefined; cc.game.emit('e_mgr_launch_update_failed'); } else if (status == JMC.UPDATE_STATUS.DOWNLOADING) { cc.game.emit('e_mgr_launch_update_progression'); } }, /** * 加载本地manifest * * @author libo * @date 2019-03-15 * @returns {object} manifest的JSON对象 */ loadLocalManifestObj () { let localManifestPath = this._updatePath + '/project.manifest'; G.LogUtils.log('[HotUpdateMgr] loadLocalManifestObj localManifestPath', localManifestPath); // 热更新路径没有manifest就去读resources的manifest if (!jsb.fileUtils.isFileExist(localManifestPath)) { localManifestPath = cc.url.raw(this._localDefaultManifestPath); // resources没有就返回空对象 if (!jsb.fileUtils.isFileExist(localManifestPath)) { G.LogUtils.error('[HotUpdateMgr] loadLocalManifestObj 配置错误', localManifestPath); return {}; } } // 读取manifest并转换成json对象 let str = jsb.fileUtils.getStringFromFile(localManifestPath); return JSON.parse(str); }, /** * 替换manifest对象 * * @author libo * @date 2019-03-21 * @param {json} obj */ updateLocalManifestObj (obj) { this.localManifestObj = obj; this.localManifest = new jsb.Manifest(JSON.stringify(obj), this._updatePath); }, /** * 将远程manifest load 进AssetsManage中 * * @author libo * @date 2019-03-25 * @param {json} obj */ loadRemoteManifestObj (obj) { this.remoteManifest = new jsb.Manifest(JSON.stringify(obj), this._updatePath); }, /** * 替换manifest对象 * * @author libo * @date 2019-03-21 * @param {String} remoteManifestStr */ updateRemoteManifest (remoteManifestStr) { // 计算更新大小 let localManifest = this.localManifestObj; let remoteManifest = JSON.parse(remoteManifestStr); this.realTotalBytes = this._calculateUpdateSize(localManifest, remoteManifest); // 存储manifest let cacheManifestPath = this._getHotfixTempManifestPath(); jsb.fileUtils.writeStringToFile(remoteManifestStr, cacheManifestPath); // 加载热更新的配置到内存 this.loadRemoteManifestObj(remoteManifest); }, // 加载缓存的manifest loadTempManifest () { let cacheManifestPath = this._getHotfixTempManifestPath(); if (jsb.fileUtils.isFileExist(cacheManifestPath)) { let remoteManifestStr = jsb.fileUtils.getStringFromFile(cacheManifestPath); this.updateRemoteManifest(remoteManifestStr); } }, // 获取缓存的manifest md5 getTempManifestMd5 () { // 缓存manifest路径 let cacheManifestPath = this._getHotfixTempManifestPath(); if (jsb.fileUtils.isFileExist(cacheManifestPath)) { let remoteManifestStr = jsb.fileUtils.getStringFromFile(cacheManifestPath); let hexHash = MD5.hex(remoteManifestStr); return hexHash; } else { return ''; } }, // 热更新完毕后重启应用 restartApp () { if (this.didRestart) { return; } this.didRestart = true; // 在本地储存一个标志,用于闪屏界面判断是否需要跳过 cc.sys.localStorage.setItem('HOT_UPDATE_SKIP_SPLASH', true); cc.audioEngine.stopAll(); cc.game.restart(); }, // 判断是否下载成功 isDownloadFinished () { return this.status === JMC.UPDATE_STATUS.DOWNLOAD_FINISHED; }, // 判断是否下载失败 isDownloadFailed () { return this.status === JMC.UPDATE_STATUS.DOWNLOAD_FAILED; }, // 更新资源号并持久化 updateResVersion () { let curResVersion = G.RemoteUpdateInfoMgr.remoteUpdateInfo.newResVer; G.RES_VERSION = curResVersion; G.LogUtils.log('[HotUpdateMgr] updateResVersion 热更新完成,当前资源号', G.RES_VERSION); cc.sys.localStorage.setItem('HOT_UPDATE_RES_VERSION', curResVersion); }, // 更新资源号 checkResVersion () { G.LogUtils.log('[HotUpdateMgr] checkResVersion: 包体的G.RES_VERSION', G.RES_VERSION); // 上次热更新成功的包体内的代码版本号 let oldAppVersion = cc.sys.localStorage.getItem('HOT_UPDATE_VERSION_NAME'); // 上次热更新成功的包体内的混淆码 let oldHxCode = cc.sys.localStorage.getItem('HOT_UPDATE_HX_CODE'); // 当前包体内的代码版本号 let curAppVersion = G.APP_VERSION; // 当前包体内的混淆码 let curHxCode = G.HX_CODE; // 当前包体内的代码资源号 let curResVersion = G.RES_VERSION; G.LogUtils.log('[HotUpdateMgr] checkResVersion: oldAppVersion', oldAppVersion, 'oldHxCode', oldHxCode); G.LogUtils.log('[HotUpdateMgr] checkResVersion: curAppVersion', curAppVersion, 'curHxCode', curHxCode); // 更新本地的资源号 if ((oldAppVersion == curAppVersion) && (oldHxCode == curHxCode)) { // 上次热更新成功的资源号 let resVersion = cc.sys.localStorage.getItem('HOT_UPDATE_RES_VERSION'); if (resVersion) { if (typeof(resVersion) === 'string') resVersion = parseInt(resVersion); if (resVersion > curResVersion) G.RES_VERSION = resVersion; } } else { // 版本更新了,清除旧的热更新文件 this.clearOldHotfixPath(); this.clearOldHotfixTempPath(); cc.sys.localStorage.setItem('HOT_UPDATE_VERSION_NAME', curAppVersion); cc.sys.localStorage.setItem('HOT_UPDATE_HX_CODE', curHxCode); cc.sys.localStorage.setItem('HOT_UPDATE_RES_VERSION', curResVersion); } G.LogUtils.log('[HotUpdateMgr] checkResVersion: 热更新前的G.RES_VERSION', G.RES_VERSION); }, // 删除旧热更新路径下资源 clearOldHotfixPath () { G.LogUtils.log('[HotUpdateMgr] 清空热更新目录'); let tempDir = this._updatePath; jsb.fileUtils.removeDirectory(tempDir); jsb.fileUtils.createDirectory(tempDir); }, // 删除热更新temp路径下资源 clearOldHotfixTempPath () { G.LogUtils.log('[HotUpdateMgr] 清空热更新临时目录'); let tempDir = this._getHotfixTempPath(); jsb.fileUtils.removeDirectory(tempDir); jsb.fileUtils.createDirectory(tempDir); }, // 统计 热更新状态更新 _stStatusChange (status, reason) { if (this.stStatus === status) { return; } this.stStatus = status; }, // 获取热更新temp目录 _getHotfixTempPath () { return G.LaunchMgr.getHotfixTempPath(); }, // 热更新临时manifest路径 _getHotfixTempManifestPath () { return jsb.fileUtils.getWritablePath() + 'cache.manifest'; }, /** * 比对manifest,计算更新大小 * * @author libo * @date 2019-03-26 * @param {json} localManifest 本地manifest * @param {json} remoteManifest 远程manifest * @returns {number} 更新大小,单位字节 */ _calculateUpdateSize (localManifest, remoteManifest) { let remoteManifestAssets = remoteManifest.assets; let localManifestAssets = localManifest.assets; let size = 0; for (let key in remoteManifestAssets) { let remoteAsset = remoteManifestAssets[key]; let localAsset = localManifestAssets[key]; if (!localAsset || localAsset.md5 != remoteAsset.md5) { size += remoteAsset.size; } } return size; }, removeHotfixTempManifestPath () { let cacheManifestPath = this._getHotfixTempManifestPath(); if (jsb.fileUtils.isFileExist(cacheManifestPath)) { jsb.fileUtils.removeFile(cacheManifestPath); } } }; module.exports = HotUpdateMgr;