cocos热更新切换地址不生效问题


我刚写过一篇

原理就是想办法,在热更新之前把project.manifest中的资源地址替换掉

我也这么试过,加载localManifest以后,动态替换里面的url,但是并不管用image

热更新分版本部署、回滚、跨版本热更新,都是很合理的需求

可以考虑一下我的插件

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赞

多谢多谢,我改一下试试 :smiling_face_with_three_hearts:

就是顺序问题,实在是没想到,把改完的manifest用am.loadLocalManifest居然不管用,再次感谢 :100: