HotUpdate.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import { _decorator, Component, Node, Label, ProgressBar, Asset, game, sys, EventHandler } from 'cc';
  2. import { JSB } from 'cc/env';
  3. import { Log } from '../core/utils/Log';
  4. const { ccclass, property } = _decorator;
  5. @ccclass('HotUpdate')
  6. export class HotUpdate extends Component {
  7. @property({ tooltip: "project.manifest文件", type: Asset })
  8. manifestUrl: Asset = null!;
  9. @property({ tooltip: "检查版本后是否自动更新,否则请手动调用hotUpdate方法" })
  10. autoUpdate: boolean = true;
  11. @property({ tooltip: "检查版本回调", type: EventHandler })
  12. onCheck: EventHandler;
  13. @property({ tooltip: "热更新出错回调", type: EventHandler })
  14. onUpdateError: EventHandler;
  15. @property({ tooltip: "热更新进度回调,最大值为1", type: EventHandler })
  16. onUpdateProgress: EventHandler;
  17. @property({ tooltip: "热更新检查完成回调(只有无需热更新才会有此回调,否则是重启回调)", type: EventHandler })
  18. onVersionIsNew: EventHandler;
  19. @property({ tooltip: "热更新完成回调后会重启应用", type: EventHandler })
  20. onRestart: EventHandler;
  21. public versionIsNew: boolean = false;
  22. private am: jsb.AssetsManager = null!;
  23. private checkCb(event: any) {
  24. console.log('Code: ' + event.getEventCode());
  25. this.onCheck && this.onCheck.emit([event]);
  26. switch (event.getEventCode()) {
  27. case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
  28. Log.warn("找不到manifest文件,热更新跳过");
  29. break;
  30. case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
  31. case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
  32. Log.warn("无法下载manifest文件,热更新跳过");
  33. break;
  34. case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
  35. Log.log("热更新检查完成,已经是最新版本");
  36. this.versionIsNew = true;
  37. this.onVersionIsNew && this.onVersionIsNew.emit([event]);
  38. break;
  39. case jsb.EventAssetsManager.NEW_VERSION_FOUND:
  40. Log.log(`发现最新版本,即将开始热更新。总共 ${Math.ceil(this.am.getTotalBytes() / 1024)} KB`);
  41. if (this.autoUpdate) {
  42. this.hotUpdate();
  43. return;
  44. }
  45. break;
  46. default:
  47. return;
  48. }
  49. this.am.setEventCallback(null!);
  50. }
  51. private updateCb(event: any) {
  52. var needRestart = false;
  53. var failed = false;
  54. switch (event.getEventCode()) {
  55. case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
  56. Log.warn("找不到manifest文件,热更新跳过");
  57. this.onUpdateError && this.onUpdateError.emit([event]);
  58. failed = true;
  59. break;
  60. case jsb.EventAssetsManager.UPDATE_PROGRESSION:
  61. Log.log(`正在更新,进度: ${event.getPercent()}`);
  62. this.onUpdateProgress && this.onUpdateProgress.emit([event]);
  63. var msg = event.getMessage();
  64. if (msg) {
  65. Log.warn(`热更新失败 ${msg}`);
  66. this.onUpdateError && this.onUpdateError.emit([event]);
  67. }
  68. break;
  69. case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
  70. case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
  71. Log.warn("无法下载manifest文件,热更新跳过");
  72. this.onUpdateError && this.onUpdateError.emit([event]);
  73. failed = true;
  74. break;
  75. case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
  76. Log.log("热更新检查完成,已经是最新版本");
  77. failed = true;
  78. this.versionIsNew = true;
  79. this.onVersionIsNew && this.onVersionIsNew.emit([event]);
  80. break;
  81. case jsb.EventAssetsManager.UPDATE_FINISHED:
  82. Log.log("热更新完成");
  83. needRestart = true;
  84. this.onRestart && this.onRestart.emit([event]);
  85. break;
  86. case jsb.EventAssetsManager.UPDATE_FAILED:
  87. Log.warn(`热更新失败 ${event.getMessage()}`);
  88. this.onUpdateError && this.onUpdateError.emit([event]);
  89. break;
  90. case jsb.EventAssetsManager.ERROR_UPDATING:
  91. Log.warn(`资源更新失败,资源编号:${event.getAssetId()},错误信息:${event.getMessage()}`);
  92. this.onUpdateError && this.onUpdateError.emit([event]);
  93. break;
  94. case jsb.EventAssetsManager.ERROR_DECOMPRESS:
  95. Log.log(`${event.getMessage()}`);
  96. failed = true;
  97. this.onUpdateError && this.onUpdateError.emit([event]);
  98. break;
  99. default:
  100. break;
  101. }
  102. if (failed) {
  103. this.am.setEventCallback(null!);
  104. }
  105. if (needRestart) {
  106. this.am.setEventCallback(null!);
  107. // Prepend the manifest's search path
  108. var searchPaths = jsb.fileUtils.getSearchPaths();
  109. var newPaths = this.am.getLocalManifest().getSearchPaths();
  110. console.log(JSON.stringify(newPaths));
  111. Array.prototype.unshift.apply(searchPaths, newPaths);
  112. // This value will be retrieved and appended to the default search path during game startup,
  113. // please refer to samples/js-tests/main.js for detailed usage.
  114. // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect.
  115. localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
  116. jsb.fileUtils.setSearchPaths(searchPaths);
  117. // restart game.
  118. setTimeout(() => {
  119. game.restart();
  120. }, 1000)
  121. }
  122. }
  123. /**
  124. * 检查热更新
  125. * @returns
  126. */
  127. public checkUpdate() {
  128. if (this.am.getState() === jsb.AssetsManager.State.UNINITED) {
  129. var url = this.manifestUrl.nativeUrl;
  130. this.am.loadLocalManifest(url);
  131. }
  132. if (!this.am.getLocalManifest() || !this.am.getLocalManifest().isLoaded()) {
  133. Log.warn("加载manifest文件失败");
  134. return;
  135. }
  136. this.am.setEventCallback(this.checkCb.bind(this));
  137. this.am.checkUpdate();
  138. }
  139. /**
  140. * 执行热更新
  141. */
  142. public hotUpdate() {
  143. this.am.setEventCallback(this.updateCb.bind(this));
  144. if (this.am.getState() === jsb.AssetsManager.State.UNINITED) {
  145. var url = this.manifestUrl.nativeUrl;
  146. this.am.loadLocalManifest(url);
  147. }
  148. this.am.update();
  149. }
  150. // use this for initialization
  151. onLoad() {
  152. // Hot update is only available in Native build
  153. if (!JSB) {
  154. this.versionIsNew = true;
  155. return;
  156. }
  157. // Setup your own version compare handler, versionA and B is versions in string
  158. // if the return value greater than 0, versionA is greater than B,
  159. // if the return value equals 0, versionA equals to B,
  160. // if the return value smaller than 0, versionA is smaller than B.
  161. // Init with empty manifest url for testing custom manifest
  162. this.am = new jsb.AssetsManager('', ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'happyFarm-remote-asset'), (versionA: string, versionB: string) => {
  163. console.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
  164. var vA = versionA.split('.');
  165. var vB = versionB.split('.');
  166. for (var i = 0; i < vA.length; ++i) {
  167. var a = parseInt(vA[i]);
  168. var b = parseInt(vB[i] || '0');
  169. if (a === b) {
  170. continue;
  171. }
  172. else {
  173. return a - b;
  174. }
  175. }
  176. if (vB.length > vA.length) {
  177. return -1;
  178. }
  179. else {
  180. return 0;
  181. }
  182. });
  183. // Setup the verification callback, but we don't have md5 check function yet, so only print some message
  184. // Return true if the verification passed, otherwise return false
  185. this.am.setVerifyCallback(function (path: string, asset: any) {
  186. // When asset is compressed, we don't need to check its md5, because zip file have been deleted.
  187. var compressed = asset.compressed;
  188. // Retrieve the correct md5 value.
  189. var expectedMD5 = asset.md5;
  190. // asset.path is relative path and path is absolute.
  191. var relativePath = asset.path;
  192. // The size of asset file, but this value could be absent.
  193. var size = asset.size;
  194. if (compressed) {
  195. Log.log("Verification passed : " + relativePath);
  196. return true;
  197. }
  198. else {
  199. Log.log("Verification passed : " + relativePath + ' (' + expectedMD5 + ')');
  200. return true;
  201. }
  202. });
  203. this.checkUpdate();
  204. Log.log('Hot update is ready, please check or directly update.');
  205. }
  206. onDestroy() {
  207. JSB && this.am.setEventCallback(null!);
  208. }
  209. }