参照 : https://github.com/cocos-creator/tutorial-hot-update。
一 , 模拟远程资源服务器Ⅰ, 方案 anywhere npm包
使用npm install anywhere -g 安装最新的服务器包, 如下
启动 anywhere 8860
a, 在目录服务器目录下执行命令 anywhere 8860, 如下
b, 可以在浏览器, 检查是否启动成功, 如下
命令准备 node version_generator.js -v 1.0.0 -u http://192.168.0.103:8860/remote-assets/ -s build/android/assets -d remote-assets
step1 , 我们先要发不一个App版本( 构建 + 生成 )
step2 , 将version_generator.js文件考到项目的根目录
step3, 在项目的根目录中使用命令 node version_generator.js -v 1.0.0 -u http://192.168.0.103:8860/remote-assets/ -s build/android//assets -d remote-assets/ 生成
-u : 资源服务器根目录有remote-assets文件夹
-d : 生成remote-assets资源文件
①, 使用夜神模拟器安装打好的App包 (此时是v1.0.0的包)
②,再次使用 version_generator.js 生成高级一版的更新包 , 步骤如下 (注意顺序)
Ⅰ, 将更新过的资源保存 , 如下所示
Ⅱ,"构建" , 注意此时不要再点击"生成"
Ⅲ,
a,修改HotUpdate.ts 如下:
b,使用 node version_generator.js -v 1.0.1 -u http://192.168.0.103:8860/remote-assets/ -s build/android/assets -d remote-assets , 生成更高级的更细包 , 注意v1.0.1 > v1.0.0
Ⅳ, 正式模式资源更新模拟器
1, 在anywhere 所在文件夹中, 将项目根目录下的remote-assets考入到其中
2, 启动anywhere
四, 结果
注: 资源服务器如下:
HotUpdate.ts
import { UpdatePanel } from './UpdatePanel';const jsb = (<any>window).jsb;// Custom manifest removed the following assets:// 1. res/raw-assets/2a/2a40e5e7-4c4a-4350-9e5d-76757755cdd2.png// 2. res/raw-assets/2d/2d86a854-63c4-4b90-8b88-a4328b8526c2.png// So when custom manifest used, you should be able to find them in downloaded remote assets// let UIRLFILE = "http://192.168.0.1:8080/server/" + gameName;// let remoteManifestUrl = this._storagePath + "/project.manifest";// this.manifestUrl = remoteManifestUrl;var customManifestStr = JSON.stringify({ "packageUrl": "http://192.168.0.103:8860/remote-assets/", "remoteManifestUrl": "http://192.168.0.103:8860/remote-assets/project.manifest", "remoteVersionUrl": "http://192.168.0.103:8860/remote-assets/version.manifest", "version": "1.0.1", "assets": { }, "searchPaths": []});import { _decorator, Component, Node, Label, ProgressBar, Asset, game, sys } from 'cc';const { ccclass, property } = _decorator;@ccclass('HotUpdate')export class HotUpdate extends Component { @property(UpdatePanel) panel: UpdatePanel = null!; @property(Asset) manifestUrl: Asset = null!; @property(Node) updateUI: Node = null!; private _updating = false; private _canRetry = false; private _storagePath = ''; private _am: jsb.AssetsManager = null!; private _checkListener = null; private _updateListener = null; private _failCount = 0; private versionCompareHandle: (versionA: string, versionB: string) => number = null!; checkCb(event: any) { console.log('Code: ' + event.getEventCode()); switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: this.panel.info.string = "No local manifest file found, hot update skipped."; break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: alert(`BBB : ${ JSON.stringify( event ) }`); this.panel.info.string = "Fail to download manifest file, hot update skipped."; break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: this.panel.info.string = "Already up to date with the latest remote version."; break; case jsb.EventAssetsManager.NEW_VERSION_FOUND: this.panel.info.string = 'New version found, please try to update. (' + Math.ceil(this._am.getTotalBytes() / 1024) + 'kb)'; this.panel.checkBtn.active = false; this.panel.fileProgress.progress = 0; this.panel.byteProgress.progress = 0; break; default: return; } this._am.setEventCallback(null!); this._checkListener = null; this._updating = false; } updateCb(event: any) { var needRestart = false; var failed = false; switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: this.panel.info.string = 'No local manifest file found, hot update skipped.'; failed = true; break; case jsb.EventAssetsManager.UPDATE_PROGRESSION: this.panel.byteProgress.progress = event.getPercent(); this.panel.fileProgress.progress = event.getPercentByFile(); this.panel.fileLabel.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles(); this.panel.byteLabel.string = event.getDownloadedBytes() + ' / ' + event.getTotalBytes(); console.log(this.panel.fileLabel.string, this.panel.byteLabel.string); var msg = event.getMessage(); if (msg) { this.panel.info.string = 'Updated file: ' + msg; // cc.log(event.getPercent()/100 + '% : ' + msg); } break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: alert(`AAA : ${ JSON.stringify( event ) }`); this.panel.info.string = 'Fail to download manifest file, hot update skipped.'; failed = true; break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: this.panel.info.string = 'Already up to date with the latest remote version.'; failed = true; break; case jsb.EventAssetsManager.UPDATE_FINISHED: this.panel.info.string = 'Update finished. ' + event.getMessage(); needRestart = true; break; case jsb.EventAssetsManager.UPDATE_FAILED: this.panel.info.string = 'Update failed. ' + event.getMessage(); this.panel.retryBtn.active = true; this._updating = false; this._canRetry = true; break; case jsb.EventAssetsManager.ERROR_UPDATING: this.panel.info.string = 'Asset update error: ' + event.getAssetId() + ', ' + event.getMessage(); break; case jsb.EventAssetsManager.ERROR_DECOMPRESS: this.panel.info.string = event.getMessage(); break; default: break; } if (failed) { this._am.setEventCallback(null!); this._updateListener = null; this._updating = false; } if (needRestart) { this._am.setEventCallback(null!); this._updateListener = null; // Prepend the manifest's search path var searchPaths = jsb.fileUtils.getSearchPaths(); var newPaths = this._am.getLocalManifest().getSearchPaths(); console.log(JSON.stringify(newPaths)); Array.prototype.unshift.apply(searchPaths, newPaths); // This value will be retrieved and appended to the default search path during game startup, // please refer to samples/js-tests/main.js for detailed usage. // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect. localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths)); jsb.fileUtils.setSearchPaths(searchPaths); // restart game. setTimeout(() => { game.restart(); }, 1000) } } loadCustomManifest() { if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { var manifest = new jsb.Manifest(customManifestStr, this._storagePath); this._am.loadLocalManifest(manifest, this._storagePath); this.panel.info.string = 'Using custom manifest'; } } retry() { if (!this._updating && this._canRetry) { this.panel.retryBtn.active = false; this._canRetry = false; this.panel.info.string = 'Retry failed Assets...'; this._am.downloadFailedAssets(); } } checkUpdate() { if (this._updating) { this.panel.info.string = 'Checking or updating ...'; return; } if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { var url = this.manifestUrl.nativeUrl; this._am.loadLocalManifest(url); } if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) { this.panel.info.string = 'Failed to load local manifest ...'; return; } this._am.setEventCallback(this.checkCb.bind(this)); this._am.checkUpdate(); this._updating = true; } hotUpdate() { if (this._am && !this._updating) { this._am.setEventCallback(this.updateCb.bind(this)); if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { var url = this.manifestUrl.nativeUrl; this._am.loadLocalManifest(url); } this._failCount = 0; this._am.update(); this.panel.updateBtn.active = false; this._updating = true; } } show() { if (this.updateUI.active === false) { this.updateUI.active = true; } } // use this for initialization onLoad() { // Hot update is only available in Native build if(!sys.isNat�ջ�,����ive){ return; } if (!jsb) { return; } this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'blackjack-remote-asset'); console.log('Storage path for remote asset : ' + this._storagePath); // Setup your own version compare handler, versionA and B is versions in string // if the return value greater than 0, versionA is greater than B, // if the return value equals 0, versionA equals to B, // if the return value smaller than 0, versionA is smaller than B. this.versionCompareHandle = function (versionA: string, versionB: string) { console.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB); var vA = versionA.split('.'); var vB = versionB.split('.'); for (var i = 0; i < vA.length; ++i) { var a = parseInt(vA[i]); var b = parseInt(vB[i] || '0'); if (a === b) { continue; } else { return a - b; } } if (vB.length > vA.length) { return -1; } else { return 0; } }; // Init with empty manifest url for testing custom manifest this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle); var panel = this.panel; // Setup the verification callback, but we don't have md5 check function yet, so only print some message // Return true if the verification passed, otherwise return false this._am.setVerifyCallback(function (path: string, asset: any) { // When asset is compressed, we don't need to check its md5, because zip file have been deleted. var compressed = asset.compressed; // Retrieve the correct md5 value. var expectedMD5 = asset.md5; // asset.path is relative path and path is absolute. var relativePath = asset.path; // The size of asset file, but this value could be absent. var size = asset.size; if (compressed) { panel.info.string = "Verification passed : " + relativePath; return true; } else { panel.info.string = "Verification passed : " + relativePath + ' (' + expectedMD5 + ')'; return true; } }); this.panel.info.string = 'Hot update is ready, please check or directly update.'; this.panel.fileProgress.progress = 0; this.panel.byteProgress.progress = 0; } onDestroy() { if (this._updateListener) { this._am.setEventCallback(null!); this._updateListener = null; } }}
注意 : 项目根目录下的 "extensions"文件夹