Hot.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { error, log, native, sys } from "cc";
  2. import { oops } from "../../../../../extensions/oops-plugin-framework/assets/core/Oops";
  3. /** 热更参数 */
  4. export class HotOptions {
  5. /** 获取到版本号信息 */
  6. onVersionInfo: Function | null = null;
  7. /** 发现新版本,请更新 */
  8. onNeedToUpdate: Function | null = null;
  9. /** 和远程版本一致,无须更新 */
  10. onNoNeedToUpdate: Function | null = null;
  11. /** 更新失败 */
  12. onUpdateFailed: Function | null = null;
  13. /** 更新完成 */
  14. onUpdateSucceed: Function | null = null;
  15. /** 更新进度 */
  16. onUpdateProgress: Function | null = null;
  17. check() {
  18. for (let key in this) {
  19. if (key !== 'check') {
  20. if (!this[key]) {
  21. log(`参数HotOptions.${key}未设置!`);
  22. return false;
  23. }
  24. }
  25. }
  26. return true
  27. }
  28. }
  29. /** 热更管理 */
  30. export class Hot {
  31. private assetsMgr: native.AssetsManager = null!;
  32. private options: HotOptions | null = null;
  33. private state = Hot.State.None;
  34. private storagePath: string = "";
  35. private manifest: string = "";
  36. static State = {
  37. None: 0,
  38. Check: 1,
  39. Update: 2,
  40. }
  41. /** 热更初始化 */
  42. init(opt: HotOptions) {
  43. if (!sys.isNative) {
  44. return;
  45. }
  46. if (!opt.check()) {
  47. return;
  48. }
  49. this.options = opt;
  50. if (this.assetsMgr) {
  51. return;
  52. }
  53. oops.res.load('project', (err: Error | null, res: any) => {
  54. if (err) {
  55. error("【热更新界面】缺少热更新配置文件");
  56. return;
  57. }
  58. this.showSearchPath();
  59. this.manifest = res.nativeUrl;
  60. this.storagePath = `${native.fileUtils.getWritablePath()}oops_framework_remote`;
  61. this.assetsMgr = new native.AssetsManager(this.manifest, this.storagePath, (versionA, versionB) => {
  62. console.log("【热更新】客户端版本: " + versionA + ', 当前最新版本: ' + versionB);
  63. this.options?.onVersionInfo && this.options.onVersionInfo({ local: versionA, server: versionB });
  64. let vA = versionA.split('.');
  65. let vB = versionB.split('.');
  66. for (let i = 0; i < vA.length; ++i) {
  67. let a = parseInt(vA[i]);
  68. let b = parseInt(vB[i] || '0');
  69. if (a !== b) {
  70. return a - b;
  71. }
  72. }
  73. if (vB.length > vA.length) {
  74. return -1;
  75. }
  76. else {
  77. return 0;
  78. }
  79. });
  80. // 设置验证回调,如果验证通过,则返回true,否则返回false
  81. this.assetsMgr.setVerifyCallback((path: string, asset: jsb.ManifestAsset) => {
  82. // 压缩资源时,我们不需要检查其md5,因为zip文件已被删除
  83. var compressed = asset.compressed;
  84. // 检索正确的md5值
  85. var expectedMD5 = asset.md5;
  86. // 资源路径是相对路径,路径是绝对路径
  87. var relativePath = asset.path;
  88. // 资源文件的大小,但此值可能不存在
  89. var size = asset.size;
  90. return true;
  91. });
  92. var localManifest = this.assetsMgr.getLocalManifest();
  93. console.log('【热更新】热更资源存放路径: ' + this.storagePath);
  94. console.log('【热更新】本地资源配置路径: ' + this.manifest);
  95. console.log('【热更新】本地包地址: ' + localManifest.getPackageUrl());
  96. console.log('【热更新】远程 project.manifest 地址: ' + localManifest.getManifestFileUrl());
  97. console.log('【热更新】远程 version.manifest 地址: ' + localManifest.getVersionFileUrl());
  98. this.checkUpdate();
  99. });
  100. }
  101. /** 删除热更所有存储文件 */
  102. clearHotUpdateStorage() {
  103. native.fileUtils.removeDirectory(this.storagePath);
  104. }
  105. // 检查更新
  106. checkUpdate() {
  107. if (!this.assetsMgr) {
  108. console.log('【热更新】请先初始化')
  109. return;
  110. }
  111. if (this.assetsMgr.getState() === jsb.AssetsManager.State.UNINITED) {
  112. error('【热更新】未初始化')
  113. return;
  114. }
  115. if (!this.assetsMgr.getLocalManifest().isLoaded()) {
  116. console.log('【热更新】加载本地 manifest 失败 ...');
  117. return;
  118. }
  119. this.assetsMgr.setEventCallback(this.onHotUpdateCallBack.bind(this));
  120. this.state = Hot.State.Check;
  121. // 下载version.manifest,进行版本比对
  122. this.assetsMgr.checkUpdate();
  123. }
  124. /** 开始更热 */
  125. hotUpdate() {
  126. if (!this.assetsMgr) {
  127. console.log('【热更新】请先初始化')
  128. return
  129. }
  130. this.assetsMgr.setEventCallback(this.onHotUpdateCallBack.bind(this));
  131. this.state = Hot.State.Update;
  132. this.assetsMgr.update();
  133. }
  134. private onHotUpdateCallBack(event: native.EventAssetsManager) {
  135. let code = event.getEventCode();
  136. switch (code) {
  137. case native.EventAssetsManager.ALREADY_UP_TO_DATE:
  138. console.log("【热更新】当前版本与远程版本一致且无须更新");
  139. this.options?.onNoNeedToUpdate && this.options.onNoNeedToUpdate(code)
  140. break;
  141. case native.EventAssetsManager.NEW_VERSION_FOUND:
  142. console.log('【热更新】发现新版本,请更新');
  143. this.options?.onNeedToUpdate && this.options.onNeedToUpdate(code, this.assetsMgr!.getTotalBytes());
  144. break;
  145. case native.EventAssetsManager.ASSET_UPDATED:
  146. console.log('【热更新】资产更新');
  147. break;
  148. case native.EventAssetsManager.UPDATE_PROGRESSION:
  149. if (this.state === Hot.State.Update) {
  150. // event.getPercent();
  151. // event.getPercentByFile();
  152. // event.getDownloadedFiles() + ' / ' + event.getTotalFiles();
  153. // event.getDownloadedBytes() + ' / ' + event.getTotalBytes();
  154. console.log('【热更新】更新中...', event.getDownloadedFiles(), event.getTotalFiles(), event.getPercent());
  155. this.options?.onUpdateProgress && this.options.onUpdateProgress(event);
  156. }
  157. break;
  158. case native.EventAssetsManager.UPDATE_FINISHED:
  159. this.onUpdateFinished();
  160. break;
  161. default:
  162. this.onUpdateFailed(code);
  163. break;
  164. }
  165. }
  166. private onUpdateFailed(code: any) {
  167. this.assetsMgr.setEventCallback(null!)
  168. this.options?.onUpdateFailed && this.options.onUpdateFailed(code);
  169. }
  170. private onUpdateFinished() {
  171. this.assetsMgr.setEventCallback(null!);
  172. let searchPaths = native.fileUtils.getSearchPaths();
  173. let newPaths = this.assetsMgr.getLocalManifest().getSearchPaths();
  174. Array.prototype.unshift.apply(searchPaths, newPaths);
  175. localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
  176. native.fileUtils.setSearchPaths(searchPaths);
  177. console.log('【热更新】更新成功');
  178. this.options?.onUpdateSucceed && this.options.onUpdateSucceed();
  179. }
  180. private showSearchPath() {
  181. console.log("========================搜索路径========================");
  182. let searchPaths = native.fileUtils.getSearchPaths();
  183. for (let i = 0; i < searchPaths.length; i++) {
  184. console.log("[" + i + "]: " + searchPaths[i]);
  185. }
  186. console.log("======================================================");
  187. }
  188. }