RemoteUpdateInfoMgr.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // 远程更新信息管理
  2. let RemoteUpdateInfoMgr = {
  3. init () {
  4. if (!cc.sys.isNative) {
  5. return;
  6. }
  7. // 版本更新信息缓存
  8. this.remoteUpdateInfo = {};
  9. // 是否已准备好远程更新信息
  10. this.isReadyRemoteInfo = false;
  11. // 是否已准备好热更新
  12. this.isReadyHotUpdate = false;
  13. // 是否版本更新
  14. this.isVersionUpdate = false;
  15. // 是否下载完成了
  16. this.isDownloadFinish = false;
  17. // 是否热更新
  18. this.isHotUpdate = false;
  19. // 是否小资源热更新
  20. this.isSmallHotUpdate = false;
  21. // 是否大资源热更新
  22. this.isBigHotUpdate = false;
  23. },
  24. /**
  25. * 已准备好远程更新信息
  26. *
  27. * @author Pyden
  28. * @date 2020-02-21
  29. */
  30. didReadyRemoteInfo () {
  31. this.isReadyRemoteInfo = true;
  32. },
  33. /**
  34. * 已准备好热更新
  35. *
  36. * @author Pyden
  37. * @date 2020-02-21
  38. */
  39. didReadyHotUpdate () {
  40. this.isReadyHotUpdate = true;
  41. if (this.isHotUpdate) {
  42. // 确定大小更新类型
  43. this._checkHotUpdateType();
  44. // 小包或WiFi时,自动开始更新
  45. if (this.isSmallHotUpdate || cc.sys.getNetworkType() == cc.sys.NetworkType.LAN) {
  46. G.HotUpdateMgr.gotoUpdate();
  47. }
  48. }
  49. },
  50. /**
  51. * 根据更新大小判断更新类型
  52. *
  53. * @author libo
  54. * @date 2019-03-26
  55. */
  56. _checkHotUpdateType () {
  57. let updateMb = G.UpdateUtils.bytesToMb(G.HotUpdateMgr.realTotalBytes).toFixed(1);
  58. G.LogUtils.log('需要更新', updateMb, 'Mb', ' 小资源上限', this.remoteUpdateInfo.newSmallResLimit, 'Mb');
  59. if (updateMb > this.remoteUpdateInfo.newSmallResLimit) {
  60. G.LogUtils.log('大资源更新');
  61. this.isSmallHotUpdate = false;
  62. this.isBigHotUpdate = true;
  63. } else {
  64. G.LogUtils.log('小资源更新');
  65. this.isSmallHotUpdate = true;
  66. this.isBigHotUpdate = false;
  67. }
  68. },
  69. /**
  70. * 检查热更新
  71. *
  72. * @author Pyden
  73. * @date 2020-02-21
  74. * @returns
  75. */
  76. _checkHotUpdate () {
  77. let needUpdate = false;
  78. // 本函数将影响以下状态。先重置
  79. this.isHotUpdate = false;
  80. this.isReadyHotUpdate = false;
  81. this.isSmallHotUpdate = false;
  82. this.isBigHotUpdate = false;
  83. // 热更新判断
  84. if (this.remoteUpdateInfo.newResVer) {
  85. let lv = G.RES_VERSION.toString();
  86. let sv = this.remoteUpdateInfo.newResVer;
  87. G.LogUtils.log('RemoteUpdateInfoMgr._checkHotUpdate version', lv);
  88. G.LogUtils.log('RemoteUpdateInfoMgr._checkHotUpdate newResVer', sv);
  89. needUpdate = G.UpdateUtils.compareVersionStr(lv, sv) < 0;
  90. if (needUpdate) {
  91. this.isHotUpdate = true;
  92. // 本地manifest读取放到这里是为了兼容模拟器加载资源,放到构造函数中模拟器会读取资源失败
  93. let localManifestObj = G.HotUpdateMgr.loadLocalManifestObj();
  94. // 修改local manifest
  95. localManifestObj.packageUrl = this.remoteUpdateInfo.newResUrl;
  96. localManifestObj.remoteManifestUrl = cc.path.join(this.remoteUpdateInfo.newResUrl, 'project.manifest');
  97. localManifestObj.remoteVersionUrl = cc.path.join(this.remoteUpdateInfo.newResUrl, 'version.manifest');
  98. G.HotUpdateMgr.updateLocalManifestObj(localManifestObj);
  99. // lv 不超过 lastForceVer 的需要强更
  100. let lastForceVer = this.remoteUpdateInfo.lastForceVer;
  101. this.remoteUpdateInfo.newResForce = lastForceVer >= lv;
  102. let md5 = this.remoteUpdateInfo.newResFlistMd5;
  103. let remoteMd5 = G.HotUpdateMgr.getTempManifestMd5();
  104. if (md5 == remoteMd5) {
  105. G.HotUpdateMgr.loadTempManifest();
  106. this.didReadyHotUpdate();
  107. } else {
  108. G.LogUtils.error('检查本地缓存manifest: md5不匹配', md5, remoteMd5);
  109. // 需要先加载localManifestObj
  110. this.requestRemoteManifest();
  111. }
  112. }
  113. } else {
  114. // 后台无新版热更新信息
  115. this.didReadyHotUpdate();
  116. }
  117. return needUpdate;
  118. },
  119. /**
  120. * 检查版本更新
  121. *
  122. * @author Pyden
  123. * @date 2020-02-21
  124. */
  125. _checkVersionUpdate () {
  126. let needUpdate = false;
  127. // 本函数将影响以下状态。先重置
  128. this.isVersionUpdate = false;
  129. this.isDownloadFinish = false;
  130. // 版本更新判断
  131. if (this.remoteUpdateInfo.newAppVer) {
  132. let lv = G.MiddleDevice.getVersionName();
  133. let sv = this.remoteUpdateInfo.newAppVer;
  134. G.LogUtils.log('RemoteUpdateInfoMgr._checkVersionUpdate version', lv);
  135. G.LogUtils.log('RemoteUpdateInfoMgr._checkVersionUpdate newAppVer', sv);
  136. needUpdate = G.UpdateUtils.compareVersionStr(lv, sv) < 0;
  137. if (needUpdate) {
  138. // 强制更新必定不是静默更新
  139. if (this.remoteUpdateInfo.newAppForce) {
  140. this.remoteUpdateInfo.newAppSilence = false;
  141. }
  142. let isDownloadFinish = false;
  143. let isSupportUpdate = G.VersionUpdateMgr.isSupportUpdate();
  144. if (isSupportUpdate) {
  145. isDownloadFinish = G.VersionUpdateMgr.checkDownloaded(
  146. this.remoteUpdateInfo.newAppUrl,
  147. this.remoteUpdateInfo.newAppMd5
  148. );
  149. // 当APK没下载完成且是静默下载时,启动版本更新下载
  150. if (!isDownloadFinish && this.remoteUpdateInfo.newAppSilence) {
  151. G.VersionUpdateMgr.setSilenceDownload(true);
  152. G.VersionUpdateMgr.setWifiOnlyDownload(true);
  153. G.VersionUpdateMgr.download(this.remoteUpdateInfo.newAppUrl, this.remoteUpdateInfo.newAppMd5);
  154. }
  155. }
  156. this.isVersionUpdate = true;
  157. this.isDownloadFinish = isDownloadFinish;
  158. }
  159. }
  160. return needUpdate;
  161. },
  162. /**
  163. * 组装本地版本信息
  164. *
  165. * @author libo
  166. * @date 2019-03-20
  167. * @returns {url} 请求地址,地址中包含参数
  168. */
  169. _packLocalUpdateInfo () {
  170. // appVer 0: string #客户端当前APK版本
  171. // resVer 1: string #客户端当前脚本版本
  172. // channel 2: integer #渠道号
  173. // platform 3: integer #android=1,iOS=2
  174. // hxCode 4: integer #混淆码
  175. let appVer = G.MiddleDevice.getVersionName();
  176. let resVer = G.RES_VERSION;
  177. let hxCode = G.HX_CODE;
  178. let channel = G.MiddleDevice.getChannelId();
  179. let platform = G.LaunchMgr.getPatform();
  180. let param = `version_update?platform=${platform}&channel=${channel}&appVer=${appVer}&resVer=${resVer}&hxCode=${hxCode}`;
  181. let url = G.NetworkMgr.getParamsUrl();
  182. let remoteURL = cc.path.join(url, param);
  183. G.LogUtils.log('remoteURL ' + remoteURL);
  184. return remoteURL;
  185. },
  186. /**
  187. * 请求版本服务器,获取版本信息用于判断热更新和版本更新
  188. *
  189. * @author libo
  190. * @date 2019-03-20
  191. * callback
  192. * ok: 是否请求成功
  193. * errorMsg: 请求失败时的错误信息
  194. */
  195. requestRemoteUpdateInfo (callback) {
  196. // 本函数将影响以下状态。先重置
  197. this.isReadyRemoteInfo = false;
  198. this.isReadyHotUpdate = false;
  199. this.isVersionUpdate = false;
  200. this.isDownloadFinish = false;
  201. this.isHotUpdate = false;
  202. this.isSmallHotUpdate = false;
  203. this.isBigHotUpdate = false;
  204. G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteUpdateInfo');
  205. let updateCfg = require('UpdateCfg');
  206. let xhr = cc.loader.getXMLHttpRequest();
  207. xhr.timeout = updateCfg.remoteInfoTimeout;
  208. xhr.onload = (e) => {
  209. G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteUpdateInfo response', xhr.response);
  210. if (xhr.status == 200) {
  211. this._responseRemoteUpdateInfo(true, undefined, JSON.parse(xhr.response), callback);
  212. } else {
  213. this._responseRemoteUpdateInfo(false, '网络错误', undefined, callback);
  214. }
  215. };
  216. xhr.ontimeout = (e) => {
  217. G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteUpdateInfo 请求超时');
  218. this._responseRemoteUpdateInfo(false, '请求超时', undefined, callback);
  219. };
  220. xhr.onerror = (e) => {
  221. G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteUpdateInfo 请求报错');
  222. this._responseRemoteUpdateInfo(false, '请求报错', undefined, callback);
  223. };
  224. xhr.open('GET', this._packLocalUpdateInfo(), true);
  225. // 设置期望的返回数据类型 'json' 'text' 'document' ...
  226. xhr.responseType = 'text';
  227. // 设置请求头
  228. xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
  229. xhr.send();
  230. },
  231. /**
  232. * 请求回调
  233. *
  234. * @author Pyden
  235. * @date 2020-02-21
  236. * @param {Boolean} ok 是否请求成功
  237. * @param {String} errorMsg 请求失败时的错误信息
  238. * @param {Object} response 请求成功时的返回信息
  239. * @param {Function} callback 回调
  240. */
  241. _responseRemoteUpdateInfo (ok, errorMsg, response, callback) {
  242. // code 0: integer
  243. // sysTime 1: integer #服务器时间
  244. // newAppVer 2: string #版本更新,最新APK版本
  245. // newAppUrl 3: string #版本更新,app下载url
  246. // newAppMd5 4: string #版本更新,app的md5
  247. // newAppForce 5: boolean #版本更新,是否强制下载
  248. // newAppSilence 6: boolean #版本更新,是否静默下载
  249. // newAppSize 7: string #版本更新,app大小
  250. // newAppAward 8: string #版本更新,奖励文案
  251. // newAppBanner 9: string #版本更新,背景图路径
  252. // newAppContent 10: string #版本更新,附加信息(暂未使用)
  253. // newResVer 11: integer #热更新,客户端当前版本APK对应的 最新脚本版本
  254. // newResUrl 12: string #热更新,res下载url
  255. // newResFlistMd5 13: string #热更新,flist的md5
  256. // newResForce 14: boolean #热更新,是否强制下载
  257. // newSmallResLimit 15: integer #热更新小资源上限 单位是M
  258. // newResBanner 16: string #热更新,背景图路径
  259. // lastForceVer 17: string #最后一次强制热更新资源号
  260. if (ok) {
  261. this.remoteUpdateInfo = response.params;
  262. G.LogUtils.log('RemoteUpdateInfoMgr._responseRemoteUpdateInfo remoteUpdateInfo', this.remoteUpdateInfo);
  263. if (!cc.sys.isNative) {
  264. this.didReadyRemoteInfo();
  265. this.didReadyHotUpdate();
  266. return;
  267. }
  268. let needHotUpdate = this._checkHotUpdate();
  269. if (!needHotUpdate) {
  270. this._checkVersionUpdate();
  271. }
  272. }
  273. this.didReadyRemoteInfo();
  274. if (callback) {
  275. callback(ok, errorMsg);
  276. }
  277. },
  278. /**
  279. * 通过http请求下载远程manifest
  280. *
  281. * @author libo
  282. * @date 2019-03-26
  283. */
  284. requestRemoteManifest () {
  285. // 本函数将影响以下状态。先重置
  286. this.isSmallHotUpdate = false;
  287. this.isBigHotUpdate = false;
  288. // 先清空临时文件,避免之前的更新污染
  289. G.HotUpdateMgr.clearOldHotfixTempPath();
  290. // 获取新的远程manifest
  291. let url = G.HotUpdateMgr.localManifestObj.remoteManifestUrl;
  292. G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteManifest', url);
  293. let xhr = cc.loader.getXMLHttpRequest();
  294. xhr.timeout = 5000;
  295. xhr.onload = (e) => {
  296. G.LogUtils.log('RemoteUpdateInfoMgr.requestRemoteManifest response', xhr.response);
  297. if (xhr.status == 200) {
  298. this._responseRemoteManifest(true, undefined, xhr.response);
  299. } else {
  300. this._responseRemoteManifest(false, '网络错误', undefined);
  301. }
  302. };
  303. xhr.ontimeout = (e) => {
  304. G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteManifest 请求超时');
  305. this._responseRemoteManifest(false, '请求超时', undefined);
  306. };
  307. xhr.onerror = (e) => {
  308. G.LogUtils.error('RemoteUpdateInfoMgr.requestRemoteManifest 请求报错');
  309. this._responseRemoteManifest(false, '请求报错', undefined);
  310. };
  311. xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
  312. xhr.open('GET', url, true);
  313. // 设置期望的返回数据类型 'json' 'text' 'document' ...
  314. xhr.responseType = 'text';
  315. // 设置请求头
  316. xhr.send();
  317. },
  318. /**
  319. * 请求回调
  320. *
  321. * @author Pyden
  322. * @date 2020-02-21
  323. * @param {Boolean} ok 是否请求成功
  324. * @param {String} errorMsg 请求失败时的错误信息
  325. * @param {JSONString} response 请求成功时的返回信息
  326. * @returns
  327. */
  328. _responseRemoteManifest (ok, errorMsg, response) {
  329. if (ok) {
  330. G.LogUtils.log('RemoteUpdateInfoMgr._responseRemoteManifest', response);
  331. let hexHash = MD5.hex(response);
  332. let newResFlistMd5 = this.remoteUpdateInfo.newResFlistMd5;
  333. if (hexHash == newResFlistMd5) {
  334. G.HotUpdateMgr.updateRemoteManifest(response);
  335. } else {
  336. G.LogUtils.error('RemoteUpdateInfoMgr._responseRemoteManifest MD5不匹配', newResFlistMd5, hexHash);
  337. // 加载Manifest失败,不开启热更新
  338. this.isHotUpdate = false;
  339. }
  340. } else {
  341. // 加载Manifest失败,不开启热更新
  342. this.isHotUpdate = false;
  343. }
  344. this.didReadyHotUpdate();
  345. }
  346. };
  347. module.exports = RemoteUpdateInfoMgr;