HotUpdateMgr.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. // 热更新管理
  2. let HotUpdateMgr = {
  3. init () {
  4. if (!cc.sys.isNative) {
  5. return;
  6. }
  7. // 更新与热更新状态 JMC.UPDATE_STATUS
  8. // UNSTART: -1, // 未开始
  9. // DOWNLOADING: -1, // 下载中
  10. // DOWNLOAD_PAUSE: -1, // 下载暂停[暂未使用]
  11. // DOWNLOAD_FINISHED: -1, // 下载完成
  12. // DOWNLOAD_FAILED: -1 // 下载失败
  13. this.status = JMC.UPDATE_STATUS.UNSTART;
  14. this.stStatus = JMC.UPDATE_STATUS.UNSTART;
  15. // 缓存下载进度
  16. this.realTotalBytes = 0;
  17. this.downloadedBytes = 0;
  18. // 热更新资源下载到本地的路径,需要修改这里的路径需要同时修改main.js
  19. this._updatePath = G.LaunchMgr.getHotfixPath();
  20. // project.manifest默认路径,必须在resources中
  21. let UpdateCfg = require('UpdateCfg');
  22. this._localDefaultManifestPath = UpdateCfg.localManifestPath;
  23. // manifest的JSON对象,方便后续进行设置url等操作
  24. this.localManifestObj = {};
  25. this.checkResVersion();
  26. },
  27. /**
  28. * 开始更新
  29. *
  30. * @author libo
  31. * @date 2019-03-15
  32. * @returns
  33. am.getState()
  34. UNINITED 0
  35. UNCHECKED 1
  36. PREDOWNLOAD_VERSION 2
  37. DOWNLOADING_VERSION 3
  38. VERSION_LOADED 4
  39. PREDOWNLOAD_MANIFEST 5
  40. DOWNLOADING_MANIFEST 6
  41. MANIFEST_LOADED 7
  42. NEED_UPDATE 8
  43. READY_TO_UPDATE 9
  44. UPDATING 10
  45. UNZIPPING 11
  46. UP_TO_DATE 12
  47. FAIL_TO_UPDATE 13
  48. */
  49. gotoUpdate () {
  50. G.LogUtils.log('[HotUpdateMgr] gotoUpdate');
  51. // AssetsManager对象
  52. this.am = new jsb.AssetsManager('', this._updatePath, G.UpdateUtils.compareVersionStr);
  53. if (this.remoteManifest) {
  54. this.am.loadLocalManifest(this.localManifest, this._updatePath);
  55. this.am.loadRemoteManifest(this.remoteManifest);
  56. } else {
  57. this.am.loadLocalManifest(this.localManifest, this._updatePath);
  58. }
  59. // 判断是否初始化完毕
  60. if (!this.am.getLocalManifest() || !this.am.getLocalManifest().isLoaded()) {
  61. G.LogUtils.error('[HotUpdateMgr] gotoUpdate 加载manifest失败');
  62. this.am = undefined;
  63. return;
  64. }
  65. this.am.setMaxConcurrentTask(2);
  66. this.am.setEventCallback(this._updateCb.bind(this));
  67. this.am.update();
  68. },
  69. /**
  70. 更新进程回调
  71. * @author libo
  72. * @date 2019-03-15
  73. * @param {EventAssetsManagerEx} event
  74. event.getEventCode()
  75. ERROR_NO_LOCAL_MANIFEST 0
  76. ERROR_DOWNLOAD_MANIFEST 1
  77. ERROR_PARSE_MANIFEST 2
  78. NEW_VERSION_FOUND 3
  79. ALREADY_UP_TO_DATE 4
  80. UPDATE_PROGRESSION 5
  81. ASSET_UPDATED 6
  82. ERROR_UPDATING 7
  83. UPDATE_FINISHED 8
  84. UPDATE_FAILED 9
  85. ERROR_DECOMPRESS 10
  86. event.getMessage()
  87. event.getPercent()
  88. event.getPercentByFile()
  89. event.getDownloadedBytes()
  90. event.getTotalBytes()
  91. event.getDownloadedFiles()
  92. event.getTotalFiles()
  93. */
  94. _updateCb (event) {
  95. G.LogUtils.log('[HotUpdateMgr] updateCb Code:', event.getEventCode());
  96. if (!this.am) {
  97. return;
  98. }
  99. let status = undefined;
  100. let reason = undefined;
  101. let errorMsg = event.getMessage();
  102. switch (event.getEventCode()) {
  103. case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: {
  104. // 没有本地配置,跳过更新
  105. status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED;
  106. reason = 'ERROR_NO_LOCAL_MANIFEST:' + errorMsg;
  107. break;
  108. }
  109. case jsb.EventAssetsManager.ASSET_UPDATED: {
  110. // 忽略该状态,使用 UPDATE_PROGRESSION 即可
  111. return;
  112. }
  113. case jsb.EventAssetsManager.UPDATE_PROGRESSION: {
  114. // 更新进度
  115. let totalBytes = event.getTotalBytes();
  116. let downloadedBytes = event.getDownloadedBytes();
  117. if (totalBytes != 0) {
  118. downloadedBytes += this.realTotalBytes - event.getTotalBytes();
  119. }
  120. if (downloadedBytes > 0) {
  121. this.downloadedBytes = downloadedBytes;
  122. }
  123. let downloadedMb = G.UpdateUtils.bytesToMb(this.downloadedBytes).toFixed(1);
  124. let totalMb = G.UpdateUtils.bytesToMb(totalBytes).toFixed(1);
  125. G.LogUtils.log(`[HotUpdateMgr] updateCb totalBytes: ${totalBytes} totalMb: ${totalMb}Mb downloadedBytes: ${
  126. this.downloadedBytes
  127. } downloadedMb: ${downloadedMb}Mb`);
  128. status = JMC.UPDATE_STATUS.DOWNLOADING;
  129. reason = 'UPDATE_PROGRESSION';
  130. break;
  131. }
  132. case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: {
  133. // 下载manifest失败,跳过更新
  134. status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED;
  135. reason = 'ERROR_DOWNLOAD_MANIFEST:' + errorMsg;
  136. break;
  137. }
  138. case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: {
  139. // 解析manifest失败,跳过更新
  140. status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED;
  141. reason = 'ERROR_PARSE_MANIFEST:' + errorMsg;
  142. break;
  143. }
  144. case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: {
  145. // 已经更新到最终版本,跳过更新
  146. status = JMC.UPDATE_STATUS.DOWNLOAD_FINISHED;
  147. reason = 'ALREADY_UP_TO_DATE';
  148. break;
  149. }
  150. case jsb.EventAssetsManager.UPDATE_FINISHED: {
  151. // 更新完成,需要重启
  152. status = JMC.UPDATE_STATUS.DOWNLOAD_FINISHED;
  153. reason = 'UPDATE_FINISHED';
  154. break;
  155. }
  156. case jsb.EventAssetsManager.UPDATE_FAILED: {
  157. // 更新失败,需要断点续传
  158. // (服务器需要支持 Accept-Ranges,否则从头开始下载)
  159. // this.am.downloadFailedAssets();
  160. status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED;
  161. reason = 'UPDATE_FINISHED:' + errorMsg;
  162. break;
  163. }
  164. case jsb.EventAssetsManager.ERROR_UPDATING: {
  165. // 部分资源下载失败
  166. status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED;
  167. reason = 'ERROR_UPDATING:' + errorMsg;
  168. break;
  169. }
  170. case jsb.EventAssetsManager.ERROR_DECOMPRESS: {
  171. // 解压缩失败
  172. status = JMC.UPDATE_STATUS.DOWNLOAD_FAILED;
  173. reason = 'ERROR_DECOMPRESS:' + errorMsg;
  174. break;
  175. }
  176. default: {
  177. // 忽略其它状态
  178. return;
  179. }
  180. }
  181. this.status = status;
  182. // 统计状态更新
  183. this._stStatusChange(status, reason);
  184. // 更新结束的处理
  185. if (status == JMC.UPDATE_STATUS.DOWNLOAD_FINISHED) {
  186. this.am.setEventCallback(undefined);
  187. this.am = undefined;
  188. this.needRestart = true;
  189. this.updateResVersion();
  190. cc.game.emit('e_mgr_launch_update_finished');
  191. } else if (status == JMC.UPDATE_STATUS.DOWNLOAD_FAILED) {
  192. this.am.setEventCallback(undefined);
  193. this.am = undefined;
  194. cc.game.emit('e_mgr_launch_update_failed');
  195. } else if (status == JMC.UPDATE_STATUS.DOWNLOADING) {
  196. cc.game.emit('e_mgr_launch_update_progression');
  197. }
  198. },
  199. /**
  200. * 加载本地manifest
  201. *
  202. * @author libo
  203. * @date 2019-03-15
  204. * @returns {object} manifest的JSON对象
  205. */
  206. loadLocalManifestObj () {
  207. let localManifestPath = this._updatePath + '/project.manifest';
  208. G.LogUtils.log('[HotUpdateMgr] loadLocalManifestObj localManifestPath', localManifestPath);
  209. // 热更新路径没有manifest就去读resources的manifest
  210. if (!jsb.fileUtils.isFileExist(localManifestPath)) {
  211. localManifestPath = cc.url.raw(this._localDefaultManifestPath);
  212. // resources没有就返回空对象
  213. if (!jsb.fileUtils.isFileExist(localManifestPath)) {
  214. G.LogUtils.error('[HotUpdateMgr] loadLocalManifestObj 配置错误', localManifestPath);
  215. return {};
  216. }
  217. }
  218. // 读取manifest并转换成json对象
  219. let str = jsb.fileUtils.getStringFromFile(localManifestPath);
  220. return JSON.parse(str);
  221. },
  222. /**
  223. * 替换manifest对象
  224. *
  225. * @author libo
  226. * @date 2019-03-21
  227. * @param {json} obj
  228. */
  229. updateLocalManifestObj (obj) {
  230. this.localManifestObj = obj;
  231. this.localManifest = new jsb.Manifest(JSON.stringify(obj), this._updatePath);
  232. },
  233. /**
  234. * 将远程manifest load 进AssetsManage中
  235. *
  236. * @author libo
  237. * @date 2019-03-25
  238. * @param {json} obj
  239. */
  240. loadRemoteManifestObj (obj) {
  241. this.remoteManifest = new jsb.Manifest(JSON.stringify(obj), this._updatePath);
  242. },
  243. /**
  244. * 替换manifest对象
  245. *
  246. * @author libo
  247. * @date 2019-03-21
  248. * @param {String} remoteManifestStr
  249. */
  250. updateRemoteManifest (remoteManifestStr) {
  251. // 计算更新大小
  252. let localManifest = this.localManifestObj;
  253. let remoteManifest = JSON.parse(remoteManifestStr);
  254. this.realTotalBytes = this._calculateUpdateSize(localManifest, remoteManifest);
  255. // 存储manifest
  256. let cacheManifestPath = this._getHotfixTempManifestPath();
  257. jsb.fileUtils.writeStringToFile(remoteManifestStr, cacheManifestPath);
  258. // 加载热更新的配置到内存
  259. this.loadRemoteManifestObj(remoteManifest);
  260. },
  261. // 加载缓存的manifest
  262. loadTempManifest () {
  263. let cacheManifestPath = this._getHotfixTempManifestPath();
  264. if (jsb.fileUtils.isFileExist(cacheManifestPath)) {
  265. let remoteManifestStr = jsb.fileUtils.getStringFromFile(cacheManifestPath);
  266. this.updateRemoteManifest(remoteManifestStr);
  267. }
  268. },
  269. // 获取缓存的manifest md5
  270. getTempManifestMd5 () {
  271. // 缓存manifest路径
  272. let cacheManifestPath = this._getHotfixTempManifestPath();
  273. if (jsb.fileUtils.isFileExist(cacheManifestPath)) {
  274. let remoteManifestStr = jsb.fileUtils.getStringFromFile(cacheManifestPath);
  275. let hexHash = MD5.hex(remoteManifestStr);
  276. return hexHash;
  277. } else {
  278. return '';
  279. }
  280. },
  281. // 热更新完毕后重启应用
  282. restartApp () {
  283. if (this.didRestart) {
  284. return;
  285. }
  286. this.didRestart = true;
  287. // 在本地储存一个标志,用于闪屏界面判断是否需要跳过
  288. cc.sys.localStorage.setItem('HOT_UPDATE_SKIP_SPLASH', true);
  289. cc.audioEngine.stopAll();
  290. cc.game.restart();
  291. },
  292. // 判断是否下载成功
  293. isDownloadFinished () {
  294. return this.status === JMC.UPDATE_STATUS.DOWNLOAD_FINISHED;
  295. },
  296. // 判断是否下载失败
  297. isDownloadFailed () {
  298. return this.status === JMC.UPDATE_STATUS.DOWNLOAD_FAILED;
  299. },
  300. // 更新资源号并持久化
  301. updateResVersion () {
  302. let curResVersion = G.RemoteUpdateInfoMgr.remoteUpdateInfo.newResVer;
  303. G.RES_VERSION = curResVersion;
  304. G.LogUtils.log('[HotUpdateMgr] updateResVersion 热更新完成,当前资源号', G.RES_VERSION);
  305. cc.sys.localStorage.setItem('HOT_UPDATE_RES_VERSION', curResVersion);
  306. },
  307. // 更新资源号
  308. checkResVersion () {
  309. G.LogUtils.log('[HotUpdateMgr] checkResVersion: 包体的G.RES_VERSION', G.RES_VERSION);
  310. // 上次热更新成功的包体内的代码版本号
  311. let oldAppVersion = cc.sys.localStorage.getItem('HOT_UPDATE_VERSION_NAME');
  312. // 上次热更新成功的包体内的混淆码
  313. let oldHxCode = cc.sys.localStorage.getItem('HOT_UPDATE_HX_CODE');
  314. // 当前包体内的代码版本号
  315. let curAppVersion = G.APP_VERSION;
  316. // 当前包体内的混淆码
  317. let curHxCode = G.HX_CODE;
  318. // 当前包体内的代码资源号
  319. let curResVersion = G.RES_VERSION;
  320. G.LogUtils.log('[HotUpdateMgr] checkResVersion: oldAppVersion', oldAppVersion, 'oldHxCode', oldHxCode);
  321. G.LogUtils.log('[HotUpdateMgr] checkResVersion: curAppVersion', curAppVersion, 'curHxCode', curHxCode);
  322. // 更新本地的资源号
  323. if ((oldAppVersion == curAppVersion) && (oldHxCode == curHxCode)) {
  324. // 上次热更新成功的资源号
  325. let resVersion = cc.sys.localStorage.getItem('HOT_UPDATE_RES_VERSION');
  326. if (resVersion) {
  327. if (typeof(resVersion) === 'string')
  328. resVersion = parseInt(resVersion);
  329. if (resVersion > curResVersion)
  330. G.RES_VERSION = resVersion;
  331. }
  332. } else {
  333. // 版本更新了,清除旧的热更新文件
  334. this.clearOldHotfixPath();
  335. this.clearOldHotfixTempPath();
  336. cc.sys.localStorage.setItem('HOT_UPDATE_VERSION_NAME', curAppVersion);
  337. cc.sys.localStorage.setItem('HOT_UPDATE_HX_CODE', curHxCode);
  338. cc.sys.localStorage.setItem('HOT_UPDATE_RES_VERSION', curResVersion);
  339. }
  340. G.LogUtils.log('[HotUpdateMgr] checkResVersion: 热更新前的G.RES_VERSION', G.RES_VERSION);
  341. },
  342. // 删除旧热更新路径下资源
  343. clearOldHotfixPath () {
  344. G.LogUtils.log('[HotUpdateMgr] 清空热更新目录');
  345. let tempDir = this._updatePath;
  346. jsb.fileUtils.removeDirectory(tempDir);
  347. jsb.fileUtils.createDirectory(tempDir);
  348. },
  349. // 删除热更新temp路径下资源
  350. clearOldHotfixTempPath () {
  351. G.LogUtils.log('[HotUpdateMgr] 清空热更新临时目录');
  352. let tempDir = this._getHotfixTempPath();
  353. jsb.fileUtils.removeDirectory(tempDir);
  354. jsb.fileUtils.createDirectory(tempDir);
  355. },
  356. // 统计 热更新状态更新
  357. _stStatusChange (status, reason) {
  358. if (this.stStatus === status) {
  359. return;
  360. }
  361. this.stStatus = status;
  362. },
  363. // 获取热更新temp目录
  364. _getHotfixTempPath () {
  365. return G.LaunchMgr.getHotfixTempPath();
  366. },
  367. // 热更新临时manifest路径
  368. _getHotfixTempManifestPath () {
  369. return jsb.fileUtils.getWritablePath() + 'cache.manifest';
  370. },
  371. /**
  372. * 比对manifest,计算更新大小
  373. *
  374. * @author libo
  375. * @date 2019-03-26
  376. * @param {json} localManifest 本地manifest
  377. * @param {json} remoteManifest 远程manifest
  378. * @returns {number} 更新大小,单位字节
  379. */
  380. _calculateUpdateSize (localManifest, remoteManifest) {
  381. let remoteManifestAssets = remoteManifest.assets;
  382. let localManifestAssets = localManifest.assets;
  383. let size = 0;
  384. for (let key in remoteManifestAssets) {
  385. let remoteAsset = remoteManifestAssets[key];
  386. let localAsset = localManifestAssets[key];
  387. if (!localAsset || localAsset.md5 != remoteAsset.md5) {
  388. size += remoteAsset.size;
  389. }
  390. }
  391. return size;
  392. },
  393. removeHotfixTempManifestPath () {
  394. let cacheManifestPath = this._getHotfixTempManifestPath();
  395. if (jsb.fileUtils.isFileExist(cacheManifestPath)) {
  396. jsb.fileUtils.removeFile(cacheManifestPath);
  397. }
  398. }
  399. };
  400. module.exports = HotUpdateMgr;