// 远程更新信息管理 let RemoteUpdateInfoMgr = { init () { if (!cc.sys.isNative) { return; } // 版本更新信息缓存 this.remoteUpdateInfo = {}; // 是否已准备好远程更新信息 this.isReadyRemoteInfo = false; // 是否已准备好热更新 this.isReadyHotUpdate = false; // 是否版本更新 this.isVersionUpdate = false; // 是否下载完成了 this.isDownloadFinish = false; // 是否热更新 this.isHotUpdate = false; // 是否小资源热更新 this.isSmallHotUpdate = false; // 是否大资源热更新 this.isBigHotUpdate = false; }, /** * 已准备好远程更新信息 * * @author Pyden * @date 2020-02-21 */ didReadyRemoteInfo () { this.isReadyRemoteInfo = true; }, /** * 已准备好热更新 * * @author Pyden * @date 2020-02-21 */ didReadyHotUpdate () { this.isReadyHotUpdate = true; if (this.isHotUpdate) { // 确定大小更新类型 this._checkHotUpdateType(); // 小包或WiFi时,自动开始更新 if (this.isSmallHotUpdate || cc.sys.getNetworkType() == cc.sys.NetworkType.LAN) { G.HotUpdateMgr.gotoUpdate(); } } }, /** * 根据更新大小判断更新类型 * * @author libo * @date 2019-03-26 */ _checkHotUpdateType () { let updateMb = G.UpdateUtils.bytesToMb(G.HotUpdateMgr.realTotalBytes).toFixed(1); G.LogUtils.log('需要更新', updateMb, 'Mb', ' 小资源上限', this.remoteUpdateInfo.newSmallResLimit, 'Mb'); if (updateMb > this.remoteUpdateInfo.newSmallResLimit) { G.LogUtils.log('大资源更新'); this.isSmallHotUpdate = false; this.isBigHotUpdate = true; } else { G.LogUtils.log('小资源更新'); this.isSmallHotUpdate = true; this.isBigHotUpdate = false; } }, /** * 检查热更新 * * @author Pyden * @date 2020-02-21 * @returns */ _checkHotUpdate () { let needUpdate = false; // 本函数将影响以下状态。先重置 this.isHotUpdate = false; this.isReadyHotUpdate = false; this.isSmallHotUpdate = false; this.isBigHotUpdate = false; // 热更新判断 if (this.remoteUpdateInfo.newResVer) { let lv = G.RES_VERSION.toString(); let sv = this.remoteUpdateInfo.newResVer; G.LogUtils.log('RemoteUpdateInfoMgr._checkHotUpdate version', lv); G.LogUtils.log('RemoteUpdateInfoMgr._checkHotUpdate newResVer', sv); needUpdate = G.UpdateUtils.compareVersionStr(lv, sv) < 0; if (needUpdate) { this.isHotUpdate = true; // 本地manifest读取放到这里是为了兼容模拟器加载资源,放到构造函数中模拟器会读取资源失败 let localManifestObj = G.HotUpdateMgr.loadLocalManifestObj(); // 修改local manifest localManifestObj.packageUrl = this.remoteUpdateInfo.newResUrl; localManifestObj.remoteManifestUrl = cc.path.join(this.remoteUpdateInfo.newResUrl, 'project.manifest'); localManifestObj.remoteVersionUrl = cc.path.join(this.remoteUpdateInfo.newResUrl, 'version.manifest'); G.HotUpdateMgr.updateLocalManifestObj(localManifestObj); // lv 不超过 lastForceVer 的需要强更 let lastForceVer = this.remoteUpdateInfo.lastForceVer; this.remoteUpdateInfo.newResForce = lastForceVer >= lv; let md5 = this.remoteUpdateInfo.newResFlistMd5; let remoteMd5 = G.HotUpdateMgr.getTempManifestMd5(); if (md5 == remoteMd5) { G.HotUpdateMgr.loadTempManifest(); this.didReadyHotUpdate(); } else { G.LogUtils.error('检查本地缓存manifest: md5不匹配', md5, remoteMd5); // 需要先加载localManifestObj this.requestRemoteManifest(); } } } else { // 后台无新版热更新信息 this.didReadyHotUpdate(); } return needUpdate; }, /** * 检查版本更新 * * @author Pyden * @date 2020-02-21 */ _checkVersionUpdate () { let needUpdate = false; // 本函数将影响以下状态。先重置 this.isVersionUpdate = false; this.isDownloadFinish = false; // 版本更新判断 if (this.remoteUpdateInfo.newAppVer) { let lv = G.MiddleDevice.getVersionName(); let sv = this.remoteUpdateInfo.newAppVer; G.LogUtils.log('RemoteUpdateInfoMgr._checkVersionUpdate version', lv); G.LogUtils.log('RemoteUpdateInfoMgr._checkVersionUpdate newAppVer', sv); needUpdate = G.UpdateUtils.compareVersionStr(lv, sv) < 0; if (needUpdate) { // 强制更新必定不是静默更新 if (this.remoteUpdateInfo.newAppForce) { this.remoteUpdateInfo.newAppSilence = false; } let isDownloadFinish = false; let isSupportUpdate = G.VersionUpdateMgr.isSupportUpdate(); if (isSupportUpdate) { isDownloadFinish = G.VersionUpdateMgr.checkDownloaded( this.remoteUpdateInfo.newAppUrl, this.remoteUpdateInfo.newAppMd5 ); // 当APK没下载完成且是静默下载时,启动版本更新下载 if (!isDownloadFinish && this.remoteUpdateInfo.newAppSilence) { G.VersionUpdateMgr.setSilenceDownload(true); G.VersionUpdateMgr.setWifiOnlyDownload(true); G.VersionUpdateMgr.download(this.remoteUpdateInfo.newAppUrl, this.remoteUpdateInfo.newAppMd5); } } this.isVersionUpdate = true; this.isDownloadFinish = isDownloadFinish; } } return needUpdate; }, /** * 组装本地版本信息 * * @author libo * @date 2019-03-20 * @returns {url} 请求地址,地址中包含参数 */ _packLocalUpdateInfo () { // appVer 0: string #客户端当前APK版本 // resVer 1: string #客户端当前脚本版本 // channel 2: integer #渠道号 // platform 3: integer #android=1,iOS=2 // hxCode 4: integer #混淆码 let appVer = G.MiddleDevice.getVersionName(); let resVer = G.RES_VERSION; let hxCode = G.HX_CODE; let channel = G.MiddleDevice.getChannelId(); let platform = G.LaunchMgr.getPatform(); let param = `version_update?platform=${platform}&channel=${channel}&appVer=${appVer}&resVer=${resVer}&hxCode=${hxCode}`; let url = G.NetworkMgr.getParamsUrl(); let remoteURL = cc.path.join(url, param); G.LogUtils.log('remoteURL ' + remoteURL); return remoteURL; }, /** * 请求版本服务器,获取版本信息用于判断热更新和版本更新 * * @author libo * @date 2019-03-20 * callback * ok: 是否请求成功 * errorMsg: 请求失败时的错误信息 */ requestRemoteUpdateInfo (callback) { // 本函数将影响以下状态。先重置 this.isReadyRemoteInfo = false; this.isReadyHotUpdate = false; this.isVersionUpdate = false; this.isDownloadFinish = false; this.isHotUpdate = false; this.isSmallHotUpdate = false; this.isBigHotUpdate = false; G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteUpdateInfo'); let updateCfg = require('UpdateCfg'); let xhr = cc.loader.getXMLHttpRequest(); xhr.timeout = updateCfg.remoteInfoTimeout; xhr.onload = (e) => { G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteUpdateInfo response', xhr.response); if (xhr.status == 200) { this._responseRemoteUpdateInfo(true, undefined, JSON.parse(xhr.response), callback); } else { this._responseRemoteUpdateInfo(false, '网络错误', undefined, callback); } }; xhr.ontimeout = (e) => { G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteUpdateInfo 请求超时'); this._responseRemoteUpdateInfo(false, '请求超时', undefined, callback); }; xhr.onerror = (e) => { G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteUpdateInfo 请求报错'); this._responseRemoteUpdateInfo(false, '请求报错', undefined, callback); }; xhr.open('GET', this._packLocalUpdateInfo(), true); // 设置期望的返回数据类型 'json' 'text' 'document' ... xhr.responseType = 'text'; // 设置请求头 xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.send(); }, /** * 请求回调 * * @author Pyden * @date 2020-02-21 * @param {Boolean} ok 是否请求成功 * @param {String} errorMsg 请求失败时的错误信息 * @param {Object} response 请求成功时的返回信息 * @param {Function} callback 回调 */ _responseRemoteUpdateInfo (ok, errorMsg, response, callback) { // code 0: integer // sysTime 1: integer #服务器时间 // newAppVer 2: string #版本更新,最新APK版本 // newAppUrl 3: string #版本更新,app下载url // newAppMd5 4: string #版本更新,app的md5 // newAppForce 5: boolean #版本更新,是否强制下载 // newAppSilence 6: boolean #版本更新,是否静默下载 // newAppSize 7: string #版本更新,app大小 // newAppAward 8: string #版本更新,奖励文案 // newAppBanner 9: string #版本更新,背景图路径 // newAppContent 10: string #版本更新,附加信息(暂未使用) // newResVer 11: integer #热更新,客户端当前版本APK对应的 最新脚本版本 // newResUrl 12: string #热更新,res下载url // newResFlistMd5 13: string #热更新,flist的md5 // newResForce 14: boolean #热更新,是否强制下载 // newSmallResLimit 15: integer #热更新小资源上限 单位是M // newResBanner 16: string #热更新,背景图路径 // lastForceVer 17: string #最后一次强制热更新资源号 if (ok) { this.remoteUpdateInfo = response.params; G.LogUtils.log('RemoteUpdateInfoMgr._responseRemoteUpdateInfo remoteUpdateInfo', this.remoteUpdateInfo); if (!cc.sys.isNative) { this.didReadyRemoteInfo(); this.didReadyHotUpdate(); return; } let needHotUpdate = this._checkHotUpdate(); if (!needHotUpdate) { this._checkVersionUpdate(); } } this.didReadyRemoteInfo(); if (callback) { callback(ok, errorMsg); } }, /** * 通过http请求下载远程manifest * * @author libo * @date 2019-03-26 */ requestRemoteManifest () { // 本函数将影响以下状态。先重置 this.isSmallHotUpdate = false; this.isBigHotUpdate = false; // 先清空临时文件,避免之前的更新污染 G.HotUpdateMgr.clearOldHotfixTempPath(); // 获取新的远程manifest let url = G.HotUpdateMgr.localManifestObj.remoteManifestUrl; G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteManifest', url); let xhr = cc.loader.getXMLHttpRequest(); xhr.timeout = 5000; xhr.onload = (e) => { G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteManifest response', xhr.response); if (xhr.status == 200) { this._responseRemoteManifest(true, undefined, xhr.response); } else { this._responseRemoteManifest(false, '网络错误', undefined); } }; xhr.ontimeout = (e) => { G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteManifest 请求超时'); this._responseRemoteManifest(false, '请求超时', undefined); }; xhr.onerror = (e) => { G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteManifest 请求报错'); this._responseRemoteManifest(false, '请求报错', undefined); }; xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.open('GET', url, true); // 设置期望的返回数据类型 'json' 'text' 'document' ... xhr.responseType = 'text'; // 设置请求头 xhr.send(); }, /** * 请求回调 * * @author Pyden * @date 2020-02-21 * @param {Boolean} ok 是否请求成功 * @param {String} errorMsg 请求失败时的错误信息 * @param {JSONString} response 请求成功时的返回信息 * @returns */ _responseRemoteManifest (ok, errorMsg, response) { if (ok) { G.LogUtils.log('RemoteUpdateInfoMgr._responseRemoteManifest', response); let hexHash = MD5.hex(response); let newResFlistMd5 = this.remoteUpdateInfo.newResFlistMd5; if (hexHash == newResFlistMd5) { G.HotUpdateMgr.updateRemoteManifest(response); } else { G.LogUtils.error('RemoteUpdateInfoMgr._responseRemoteManifest MD5不匹配', newResFlistMd5, hexHash); // 加载Manifest失败,不开启热更新 this.isHotUpdate = false; } } else { // 加载Manifest失败,不开启热更新 this.isHotUpdate = false; } this.didReadyHotUpdate(); } }; module.exports = RemoteUpdateInfoMgr;