我刚写过一篇
原理就是想办法,在热更新之前把project.manifest中的资源地址替换掉
我也这么试过,加载localManifest以后,动态替换里面的url,但是并不管用
热更新分版本部署、回滚、跨版本热更新,都是很合理的需求
可以考虑一下我的插件
1赞
不管用肯定是你替换的不对
要不看下楼上的热更新插件
要不就看下我这个开源库
https://github.com/gongxh0901/kunpolibrary/tree/main/src/hotupdate
这句话可能有迷惑性
,但你保持一致就行了,保持一致的前提下,并没有限制你不能加子目录的 所以你把每个版本安排在不同的子目录下时可以的了,
至于‘这个地址需要和最初发布版本中 Manifest 文件的远程包地址一致’ 这句话,需要官方解释一下具体限制的原因了
地址是可以替换的,并不需要一致,看你怎么处理喽
建议多看源码,其中涉及到缓存问题,可以参考以下的代码,核心原理就是需要手动加载manifest后修改其中的远程地址再初始化
/**加载本地manifest */
private async _loadLocalManifest(): Promise<boolean> {
const local_manifest_url = this._local_manifest_url;
if (!local_manifest_url) return false;
if (this._am.getState() !== native.AssetsManager.State.UNINITED) {
Log.i('local manifest inited!');
return this._am.getLocalManifest()?.isLoaded();
}
//默认的地址
this._am.loadLocalManifest(local_manifest_url);
const manifest = this._am.getLocalManifest();
Log.i('local version url:' + manifest?.getVersionFileUrl());
Log.i('local maninfest url:' + manifest?.getManifestFileUrl());
Log.i('local version:' + manifest?.getVersion());
let remote_manifest_url = this._remote_manifest_url;
if (!remote_manifest_url || !manifest) {
Log.i('load local manifest: ' + local_manifest_url);
return manifest?.isLoaded();
}
//加载本地manifest
let content = '', cache_path = this._storage_path + 'project.manifest';
Log.i('cache project.manifest path:' + cache_path);
if (native.fileUtils.isFileExist(cache_path)) {//加载热更缓存
Log.i('read cache project.manifest!');
content = native.fileUtils.getStringFromFile(cache_path);
}
else if (native.fileUtils.isFileExist(local_manifest_url)) {
content = native.fileUtils.getStringFromFile(local_manifest_url);
}
else {
return false;
}
//解析改变远程地址
const json_data = WDJson.parse(content);
if (!json_data) return false;
if (!remote_manifest_url.endsWith('/')) remote_manifest_url += '/';
json_data.remoteVersionUrl = remote_manifest_url + 'version.manifest';
json_data.remoteManifestUrl = remote_manifest_url + 'project.manifest';
json_data.packageUrl = remote_manifest_url;
Log.i('version:' + json_data.version);
Log.i('change remote url:' + remote_manifest_url);
Log.i('new remote version url:' + json_data.remoteVersionUrl);
Log.i('new remote manifest url:' + json_data.remoteManifestUrl);
// const manifest = new native.Manifest(json_str, this._storage_path);
// this._am.loadLocalManifest(manifest, this._storage_path);
manifest.parseJSONString(WDJson.stringify(json_data), this._storage_path);
return this._am.getLocalManifest()?.isLoaded();
}
1赞
// Learn TypeScript:
// - https://docs.cocos.com/creator/2.4/manual/en/scripting/typescript.html
// Learn Attribute:
// - https://docs.cocos.com/creator/2.4/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - https://docs.cocos.com/creator/2.4/manual/en/scripting/life-cycle-callbacks.html
const {ccclass, property} = cc._decorator;
@ccclass
export default class CheckUpdate extends cc.Component {
@property(cc.Label)
label: cc.Label = null;
private VERSION_KEY = "HotUpdateVersion";
private _localVersion = "1";
private _remoteVersion = "1";
private _updating = false;
private _canRetry = false;
private _storagePath = '';
private _remotePackageUrl = null!;
private _am: jsb.AssetsManager = null!;
// private _checkListener = null;
private _updateListener = null;
private versionCompareHandle: (versionA: string, versionB: string) => number = null!;
onLoad() {
console.log('33333333333333333333');
this.initAsync();
}
async initAsync(){
let t = this;
if (!jsb) {
return;
}
t._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remote-assets');
console.log('Storage path for remote asset : ' + t._storagePath);
t._localVersion = localStorage.getItem(t.VERSION_KEY) || "1";
this.saveSearchPaths();
t.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;
}
// return t._localVersion!=t._remoteVersion? -1:0;
};
let remote_asset_url = "http://192.168.1.206:8888/remote-assets/test/"
let remoteVersionFile = remote_asset_url+"version.txt?t=" + Date.now();
await new Promise((resolve, reject) => {
cc.assetManager.loadRemote<cc.TextAsset>(remoteVersionFile, (err, data) => {
if (err) {
console.error("加载版本控制文件失败:",remoteVersionFile, err);
t.updatelabel("加载版本控制文件失败:"+remoteVersionFile);
reject(err);
} else {
// data 是一个 TextAsset,需取 .text
t._remoteVersion = data.text.trim(); // 注意 trim()
console.log("remoteVersion",t._remoteVersion);
resolve(1);
}
});
});
//manifest 结构
// var customManifestStr = JSON.stringify({
// "packageUrl": "http://192.168.55.13:5502/remote-assets/",
// "remoteManifestUrl": "http://192.168.55.13:5502/remote-assets/project.manifest",
// "remoteVersionUrl": "http://192.168.55.13:5502/remote-assets/version.manifest",
// "version": "1.0.0",
// "assets": {
// "src/application.js": {
// "size": 5514,
// "md5": "d09753aaed7c55c4566cecf766cbc5c3"
// },
// },
// "searchPaths": []
// });
t._remotePackageUrl = remote_asset_url + t._remoteVersion+"/";
console.log("remotePackageUrl",t._remotePackageUrl);
var searchPaths = jsb.fileUtils.getSearchPaths();
console.log("getSearchPaths111",searchPaths);
//经测试下面这两种写法效果一样
t._am = new jsb.AssetsManager("", t._storagePath, t.versionCompareHandle);
// t._am = new jsb.AssetsManager(t._remotePackageUrl+"project.manifest", t._storagePath, t.versionCompareHandle);
// 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
t._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) {
t.updatelabel("Verification passed : " + relativePath);
return true;
}
else {
t.updatelabel("Verification passed : " + relativePath + ' (' + expectedMD5 + ')');
return true;
}
});
console.log('Hot update is ready, please check or directly update.');
t.hotUpdate();
}
private saveSearchPaths(){
const mainBundlePath = '\@assets/';
var searchPaths = {
updatePath:this._storagePath,
mainBundlePath:mainBundlePath
}
localStorage.setItem("HotUpdateSearchPaths", JSON.stringify(searchPaths));
}
onDestroy() {
if (this._updateListener) {
this._am.setEventCallback(null!);
this._updateListener = null;
}
}
loadLocalManifest():boolean{
let t = this;
if (t._am.getState() === jsb.AssetsManager.State.UNINITED) {
let localManifestPath = t._storagePath + `/project.manifest`;
if (!jsb.fileUtils.isFileExist(localManifestPath)){
localManifestPath = cc.url.raw('resources/native/project.manifest')
}
console.log("initial Manifest Path",localManifestPath)
const manifestStr = jsb.fileUtils.getStringFromFile(localManifestPath);
if (manifestStr) {
const newManifest = t.getNewManifest(manifestStr, t._storagePath);
t._am.loadLocalManifest(newManifest,t._storagePath);
}else {
t.updatelabel('Failed to read initial manifest');
return false;
}
}
if (!t._am.getLocalManifest() || !t._am.getLocalManifest().isLoaded()) {
t.updatelabel('Failed to load local manifest ...');
return false;
}
return true;
}
private getNewManifest(manifestStr:string,storagePath):jsb.Manifest{
let t = this;
// let json = JSON.parse(manifestStr);
// json.packageUrl = t._remotePackageUrl;
// json.remoteManifestUrl = t._remotePackageUrl+"project.manifest";
// json.remoteVersionUrl = t._remotePackageUrl+"version.manifest";
// console.log("manifest packageUrl",json.packageUrl);
// console.log("manifest remoteManifestUrl",json.remoteManifestUrl);
// console.log("manifest remoteVersionUrl",json.remoteVersionUrl);
// return new jsb.Manifest(JSON.stringify(json), storagePath)
return new jsb.Manifest(manifestStr, storagePath)
}
hotUpdate() {
let t = this;
t.updatelabel('start hotupdate...');
if (t._am && !t._updating) {
t.updatelabel('start hotupdate111');
let inited = t.loadLocalManifest();
if(!inited){
console.log("checkUpdate","loadLocalManifest失败");
return;
}
t._am.setEventCallback(t.updateCb.bind(t));
t._am.update();
t._updating = true;
}else{
console.log("hotUpdate failed","t._am=",t._am,"_updating="+t._updating)
}
}
updateCb(event: jsb.EventAssetsManager) {
let t = this;
var needRestart = false;
var failed = false;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
console.log("updateCb failed: ","ERROR_NO_LOCAL_MANIFEST", event.getMessage());
this.updatelabel('No local manifest file found, hot update skipped.');
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
const percent = event.getPercentByFile();//event.getPercent();
if(percent && percent > 0){
console.log("updateCb : ", `${(percent * 100).toFixed(2)}%`);
this.updatelabel(`${(percent * 100).toFixed(2)}%`);
}
//数量进度
if(event.getTotalFiles()>0){
console.log("下载进度", event.getDownloadedFiles() + ' / ' + event.getTotalFiles());
}
var msg = event.getMessage();
if (msg) {
this.updatelabel('Updated file: ' + msg);
// cc.log(event.getPercent()/100 + '% : ' + msg);
}
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
console.log("updateCb failed: ","ERROR_DOWNLOAD_MANIFEST", event.getMessage());
this.updatelabel('ERROR_DOWNLOAD_MANIFEST');
failed = true;
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
console.log("updateCb failed: ","ERROR_PARSE_MANIFEST", event.getMessage());
this.updatelabel('ERROR_PARSE_MANIFEST');
failed = true;
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.updatelabel('ALREADY_UP_TO_DATE');
console.log("updateCb","********now is latest********");
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
this.updatelabel('UPDATE_FINISHED');
console.log("updateCb","********finished********");
needRestart = true;
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
this.updatelabel('UPDATE_FAILED');
console.log("updateCb failed: ","UPDATE_FAILED", event.getMessage());
this._updating = false;
this._canRetry = true;
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
console.log("updateCb failed: ","ERROR_UPDATING", event.getAssetId() + ', ' + event.getMessage());
this.updatelabel('ERROR_UPDATING');
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
console.log("updateCb failed: ","ERROR_DECOMPRESS", event.getMessage());
this.updatelabel('ERROR_DECOMPRESS');
break;
default:
console.log("updateCb: ","unknown", event.getEventCode(), event.getMessage());
break;
}
if (failed) {
this._am.setEventCallback(null!);
this._updateListener = null;
this._updating = false;
}
if (needRestart) {
this._am.setEventCallback(null!);
this._updateListener = null;
t._localVersion = t._remoteVersion;
localStorage.setItem(t.VERSION_KEY, t._localVersion);
// restart game.
setTimeout(() => {
console.log("restart cocos");
cc.game.restart();
}, 1000)
}
}
retry() {
let t = this;
if (!this._updating && this._canRetry) {
this._canRetry = false;
t.updatelabel('Retry failed Assets...');
this._am.downloadFailedAssets();
}
}
private updatelabel(str: string){
if (this.label != null) {
this.label.string = str;
}
}
}
哥们,要不你帮我看看,我是哪处理的不对了,不切换远程文件夹的情况我试过,没毛病。只要切换了,他就一直是在用上一个版本的manifest做对比
是,结果是这样,但官方的这句话是什么意思?如果没影响 它不可能这么明确的表示出来吧
private getNewManifest(manifestStr:string,storagePath):jsb.Manifest{
let t = this;
// let json = JSON.parse(manifestStr);
// json.packageUrl = t._remotePackageUrl;
// json.remoteManifestUrl = t._remotePackageUrl+"project.manifest";
// json.remoteVersionUrl = t._remotePackageUrl+"version.manifest";
// console.log("manifest packageUrl",json.packageUrl);
// console.log("manifest remoteManifestUrl",json.remoteManifestUrl);
// console.log("manifest remoteVersionUrl",json.remoteVersionUrl);
// return new jsb.Manifest(JSON.stringify(json), storagePath)
return new jsb.Manifest(manifestStr, storagePath)
}
很明显你加载的就是本地的 project.manifest,并没有修改他指向的远程资源的地址
这处理的一样吧
这个兄弟写的已经很清楚了
你需要先用本地的url加载manifest然后修改manifest的内容,不然会由于初始化时序的问题导致修改的内容不生效,当时也是搞了好久
1赞
多谢多谢,我改一下试试 
就是顺序问题,实在是没想到,把改完的manifest用am.loadLocalManifest居然不管用,再次感谢 




