admin管理员组

文章数量:1575992

https://blog.csdn/huanghuipost/article/details/102569135

 huanghuipost 

苦海无涯,特此记录。

先吐槽一下。官方的文档要么就是古董,要么就是分散。对于我这个新上手的人来说,上来cocosCreator版本就是2.1.2了,而参考文档还是1.X的版本、几年前的说明。

1、准备工作

1、cocos Creator 版本 V2.1.2

2、Android 原生打包环境-->自行百度(主要是sdk,nkd,ant)

3、cocosCreator 热更新插件 《热更新manifest生成工具》

4、hfs网络文件服务器 2.3(自行下载、安装。主要用做本地简单服务器的搭建)

2、预备知识

1、阅读一下官方文档

https://docs.cocos/creator/manual/zh/advanced-topics/hot-update.html

https://docs.cocos/creator/manual/zh/advanced-topics/assets-manager.html

读完之后,是不是就知道更新大致需要的文档了

2、从官方文档中给出的大致过程



 3、开始热更新

新建一个cocosCreator 项目。

1、开始还是先搭建一个简单的服务器

1、在硬盘中创建一个目录,用来热更新的目录

 2、打开hfs.exe,将刚刚的目录拖入hfs左侧空白就会自动生成

这样就简单的生成了资源更新服务器了。

2、生成Manifest文档和更新资源包(官方是代码生成的,这里我使用的cocosCreator商店的工具)

->1、创建  version_generator.js 文件 (官方代码仓库也有)

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
 
var manifest = {
	//服务器上资源文件存放路径(src,res的路径)
    packageUrl: 'http://192.168.1.120:8080/hotUpdate/',
    remoteManifestUrl: 'http://192.168.1.120:8080/hotUpdate/project.manifest',
    remoteVersionUrl: 'http://192.168.1.120:8080/hotUpdate/version.manifest',
    version: '1.0.2',
    assets: {},
    searchPaths: []
};
 
//生成的manifest文件存放目录
var dest = 'assets/';
//项目构建后资源的目录
var src = 'build/jsb-default/';
 
/**
 * node version_generator.js -v 1.0.0 -u http://your-server-address/tutorial-hot-update/remote-assets/ -s native/package/ -d assets/
 */
 
// Parse arguments
var i = 2;
while ( i < process.argv.length) {
    var arg = process.argv[i];
 
    switch (arg) {
    case '--url' :
    case '-u' :
        var url = process.argv[i+1];
        manifest.packageUrl = url;
        manifest.remoteManifestUrl = url + 'project.manifest';
        manifest.remoteVersionUrl = url + 'version.manifest';
        i += 2;
        break;
    case '--version' :
    case '-v' :
        manifest.version = process.argv[i+1];
        i += 2;
        break;
    case '--src' :
    case '-s' :
        src = process.argv[i+1];
        i += 2;
        break;
    case '--dest' :
    case '-d' :
        dest = process.argv[i+1];
        i += 2;
        break;
    default :
        i++;
        break;
    }
}
 
 
function readDir (dir, obj) {
    var stat = fs.statSync(dir);
    if (!stat.isDirectory()) {
        return;
    }
    var subpaths = fs.readdirSync(dir), subpath, size, md5, compressed, relative;
    for (var i = 0; i < subpaths.length; ++i) {
        if (subpaths[i][0] === '.') {
            continue;
        }
        subpath = path.join(dir, subpaths[i]);
        stat = fs.statSync(subpath);
        if (stat.isDirectory()) {
            readDir(subpath, obj);
        }
        else if (stat.isFile()) {
            // Size in Bytes
            size = stat['size'];
            md5 = crypto.createHash('md5').update(fs.readFileSync(subpath)).digest('hex');
            compressed = path.extname(subpath).toLowerCase() === '.zip';
 
            relative = path.relative(src, subpath);
            relative = relative.replace(/\\/g, '/');
            relative = encodeURI(relative);
            obj[relative] = {
                'size' : size,
                'md5' : md5
            };
            if (compressed) {
                obj[relative]pressed = true;
            }
        }
    }
}
 
var mkdirSync = function (path) {
    try {
        fs.mkdirSync(path);
    } catch(e) {
        if ( e.code != 'EEXIST' ) throw e;
    }
}
 
// Iterate res and src folder
readDir(path.join(src, 'src'), manifest.assets);
readDir(path.join(src, 'res'), manifest.assets);
 
var destManifest = path.join(dest, 'project.manifest');
var destVersion = path.join(dest, 'version.manifest');
 
mkdirSync(dest);
 
fs.writeFile(destManifest, JSON.stringify(manifest), (err) => {
  if (err) throw err;
  console.log('Manifest successfully generated');
});
 
delete manifest.assets;
delete manifest.searchPaths;
fs.writeFile(destVersion, JSON.stringify(manifest), (err) => {
  if (err) throw err;
  console.log('Version successfully generated');
});

  *****该文件主要作用是生成资源的更新信息,将创建的 version_generator.js 文档放在主工程目录下

-->2、打开cocosCreator热更新工具

 设置当前版本号,

第一次打包版本号可以就从1.0.0开始,

资源服务器url  对应服务器搭建的目录我这里是http://192.168.1.120:8080/hotUpdate/

build资源路径就是我们在构建的时候生成的build文件模板

构建的时候,我选择的是default,所有在热更工具中build目录也是这个目录。

完成后,点击生成,再打开目录

 已经生成版本文件。里面就是版本更新文件,解压后可以看到有四项文件(Manifest文件,res,src)

到这里,需要打包用的初始Manifest文件就已经生成了。

可以将这两个*.manifest文件拷贝到assets目录下

接下来就是创建热更新组件来负责这个更新逻辑了。

打开前面新建的项目,新建一个场景,一个更新逻辑js

1、创建场景,和HotUpdate.js

//HotUpdate.js
/**
 * 负责热更新逻辑的组件
 */
cc.Class({
    extends: cc.Component,
 
    properties: {
        manifestUrl: cc.RawAsset,  //本地project.manifest资源清单文件
        _updating: false,
        _canRetry: false,
        _storagePath: '',
        label: {
            default: null,
            type: cc.Label
        },
    },
 
    checkCb: function (event) {
        var self = this;
        cc.log('Code: ' + event.getEventCode());
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                self.label.string = '本地文件丢失';
                cc.log("No local manifest file found, hot update skipped.");
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                cc.log("Fail to download manifest file, hot update skipped.");
                self.label.string = '下载远程mainfest文件错误';
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                cc.log("Already up to date with the latest remote version.");
                self.label.string = '已经是最新版本';
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                cc.log('New version found, please try to update.');
                self.label.string = '有新版本发现,请点击更新';
                //this.hotUpdate();  暂时去掉自动更新
                break;
            default:
                return;
        }
 
        this._am.setEventCallback(null);
        //this._checkListener = null;
        this._updating = false;
    },
 
    updateCb: function (event) {
        var self = this; // 原作者啊,你这行代码忘加了,让我一顿好找啊[捂脸]
        var needRestart = false;
        var failed = false;
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                cc.log('No local manifest file found, hot update skipped...');
                self.label.string = '本地版本文件丢失,无法更新';
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                cc.log(event.getPercent());
                cc.log(event.getPercentByFile());
                cc.log(event.getDownloadedFiles() + ' / ' + event.getTotalFiles());
                cc.log(event.getDownloadedBytes() + ' / ' + event.getTotalBytes());
 
                var msg = event.getMessage();
                if (msg) {
                    cc.log('Updated file: ' + msg);
                }
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                cc.log('Fail to download manifest file, hot update skipped.');
                self.label.string = '下载远程版本文件失败';
                failed = true;
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                cc.log('Already up to date with the latest remote version.');
                self.label.string = '当前为最新版本';
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                cc.log('Update finished. ' + event.getMessage());
                self.label.string = '更新完成. ' + event.getMessage();
                needRestart = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                cc.log('Update failed. ' + event.getMessage());
                self.label.string = '更新失败. ' + event.getMessage();
                this._updating = false;
                this._canRetry = true;
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                cc.log('Asset update error: ' + event.getAssetId() + ', ' + event.getMessage());
                self.label.string = '资源更新错误: ' + event.getAssetId() + ', ' + event.getMessage();
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                cc.log(event.getMessage());
                self.label.string = event.getMessage();
                break;
            default:
                break;
        }
 
        if (failed) {
            //cc.eventManager.removeListener(this._updateListener);
            this._am.setEventCallback(null);
            //this._updateListener = null;
            this._updating = false;
        }
 
        if (needRestart) {
            //cc.eventManager.removeListener(this._updateListener);
            this._am.setEventCallback(null);
            //this._updateListener = null;
            // Prepend the manifest's search path
            var searchPaths = jsb.fileUtils.getSearchPaths();
            var newPaths = this._am.getLocalManifest().getSearchPaths();
            cc.log(JSON.stringify(newPaths));
            Array.prototype.unshift(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.
            cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
            jsb.fileUtils.setSearchPaths(searchPaths);
 
            cc.audioEngine.stopAll();
            cc.game.restart();
        }
    },
 
 
    retry: function () {
        if (!this._updating && this._canRetry) {
            this._canRetry = false;
 
            cc.log('Retry failed Assets...');
            this._am.downloadFailedAssets();
        }
    },
 
/*     checkForUpdate: function () {
        cc.log("start checking...");
        if (this._updating) {
            cc.log('Checking or updating ...');
            return;
        }
        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
            this._am.loadLocalManifest(this.manifestUrl);
            cc.log(this.manifestUrl);
        }
        if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
            cc.log('Failed to load local manifest ...');
            return;
        }
        this._checkListener = new jsb.EventListenerAssetsManager(this._am, this.checkCb.bind(this));
        this._checkListener.setEventCallback(this.checkCb.bind(this));
        //cc.eventManager.addListener(this._checkListener, 1);
        
        this._am.checkUpdate();
        this._updating = true;
    }, */
 
    checkForUpdate:function(){
/*         if (this._updating) {
            cc.log('Checking or updating ...');
            return;
        } */
 
        //cc.log("加载更新配置文件");
 
        //this._am.loadLocalManifest(this.manifestUrl);
        //cc.log(this.manifestUrl);
 
        //this.tipLabel.string = '检查更新';
        //cc.log("start checking...");
 
        //var state = this._am.getState()
 
        //if (state=== jsb.AssetsManager.State.UNINITED) {
 
        // Resolve md5 url
 
        console.log('检查更新')
 
        this._am.setEventCallback(this.checkCb.bind(this));
 
        this._failCount = 0;
 
        this._am.checkUpdate();
 
        this._updating = true;
 
        // }
 
    },
 
    hotUpdate: function () {
        if (this._am && !this._updating) {
            //this._updateListener = new jsb.EventListenerAssetsManager(this._am, this.updateCb.bind(this));
            this._am.setEventCallback(this.updateCb.bind(this));
            //cc.eventManager.addListener(this._updateListener, 1);
 
            this._am.loadLocalManifest(this.manifestUrl);
 
            this._failCount = 0;
            this._am.update();
            this._updating = true;
        }
    },
 
    show: function () {
        // if (this.updateUI.active === false) {
        //     this.updateUI.active = true;
        // }
    },
 
    changesence:function(){
        cc.log("改变场景");
        cc.director.loadScene("helloworld");
    },
 
    // use this for initialization
    onLoad: function () {
        var self = this;
        // Hot update is only available in Native build
        console.log("onloadUpdate");
        if (!cc.sys.isNative) {
            return;
        }
 
        this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remote-asset');
        cc.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, versionB) {
            cc.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
            self.label.string = "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);
        // 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, asset) {
            // When asset is compressed, we don't need to check its md5, because zip file have been deleted.
            var compressed = assetpressed;
            // 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) {
                cc.log("Verification passed : " + relativePath);
                return true;
            }
            else {
                cc.log("Verification passed : " + relativePath + ' (' + expectedMD5 + ')');
                return true;
            }
        }.bind(this));
 
        cc.log("Hot update is ready, please check or directly update.");
 
        if (cc.sys.os === cc.sys.OS_ANDROID) {
            // Some Android device may slow down the download process when concurrent tasks is too much.
            // The value may not be accurate, please do more test and find what's most suitable for your game.
            this._am.setMaxConcurrentTask(2);
            cc.log("Max concurrent tasks count have been limited to 2");
        }
 
        this._am.loadLocalManifest(this.manifestUrl);
        
        cc.log(this.manifestUrl);
 
        //检查更新
        this.checkUpdate()
    },
 
    checkUpdate:function() {
        console.log('检查更新')
 
        this._am.setEventCallback(this.checkCb.bind(this));
 
        this._failCount = 0;
 
        this._am.checkUpdate();
 
        this._updating = true;
    },
 
    onDestroy: function () {
        if (this._updateListener) {
            //cc.eventManager.removeListener(this._updateListener);
            this._am.setEventCallback(null);
            //this._updateListener = null;
        }
        //if (this._am && !cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {
        //    this._am.release();
        //}
    }
});

新建热更新场景,绑定脚本,创建元素三个按钮,一个标签。

将HotUpdate.js绑定到canvas上,将对应的节点添加到上去。

在场景文件中,创建三个按钮,一个标签。并分别绑定按钮事件。

 其中切换场景就是查看热更新效果。

这个project就是我们在使用热更新工具生成出来的manifest文件

第二步完成


接下来,再一次构建项目。注意顺序

在目录build\jsb-default中找到main.js的开头处添加代码

if (jsb) { 
	var hotUpdateSearchPaths = localStorage.getItem('HotUpdateSearchPaths');
	if (hotUpdateSearchPaths) { 
		jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
	}
} 

博主提醒:上边这段代码每次构建的时候会特么的消失,你得自己重新再加一遍,我在这被坑了很久[捂脸]

其实插件里有相关的自动插入这段代码的相关处理,采用的是文本替换的办法 (function{ ====> function{ + 插入代码),可惜cocos某次更新之后,这个文本替换的规则产生了位置的偏差(function{ 的位置变了),大家可以打开插件的 main.js 自行修正下:

let a=e.replace("window.boot = function () {","if (jsb) { \n        var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths'); \n        if (hotUpdateSearchPaths) { \n            jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths)); \n            console.log('[main.js] 热更新SearchPath: ' + JSON.parse(hotUpdateSearchPaths));\n        }else {\n            console.log('[main.js] 未获取到热更新资源路径!');\n        }\n    }else {\n        console.log('[main.js] 不是native平台!');\n    }\n\nwindow.boot = function () {")

替换规则变更为 window.boot = function(){ ====> window.boot = function(){ + 插入代码,其实这也是治标不治本,最好的办法是直接特定行插入一段代码

 这里你可能会疑惑,这个HotUpdateSearchPaths是什么,它是HotUpdate.js中updateCb函数中设置的对应的目录。

 这个时候回到刚刚编译的界面,记得不要再构建了,直接编译出包。

等待中。。。

出包后,放到手机上安装。这个包就是当做旧版本包来使用。前面设置的版本是1.0.0


其实到这里,热更新已经完成了。测试一下。。注意顺序

在项目中,修改helloWord场景的元素。然后构建(这个时候,不是新包,不需要修改main.js 中的内容),然后打开热更新工具,修改版本号(提高一个版本),生成。将生成的资源拷贝到搭建的服务器热更目录下,解压。

这个时候再打开手机上的apk,就会发现提示新版本下载。


 

热更到此结束(罗里吧嗦的一大堆)!

如果还是没有成功。那就再检查一下

1、在version_generator.js文件中

2、在构建好的目录中

 在该目录下的main.js中没有添加更新后的路径搜索代码

要还是崩。。。那就自己先看日志自己解决吧(其他的日志都在手机上会显示)。

 

最后提一下

以前很多前辈写的一写文章都是摸爬打滚出来的。很有参考价值。

但是,奈何敌不过CocosCreator自己的版本更新。

所以很多的bug都是没有试用新版本而已。及时关注他们的更新指南。

这是2.0的升级指南:https://docs.cocos/creator/manual/zh/release-notes/upgrade-guide-v2.0.html

里面就改进了监听(主要是有个问题困扰我很久,最后看网友说去看升级指南。。。,然后就看到问题所在了。)

 

最后感谢这位博主https://wwwblogs/gao88/p/11632626.html,给出了一个非常详细的过程。

 

最后指出一下,本博文只是简单的热更新。在代码中有很多屏蔽的地方,都是奔溃的。我只是让它实现了最基本的热更新。

还没有测试ios下,也没有太多的优化。比如进度条显示、文件显示。

 

后续再更新吧!

今天先到这里。

--------------------------------------------------------------------------------------------------------

丿丶淡忘 

https://blog.csdn/qq_40956352/article/details/90442679

Cocos Creator 热更新(动态修改热更地址)

入门CocosCreator大概一年,由于项目需要,要用到热更新,由于之前没接触过,于是根据官方文档把热更新走了一遍后,其中遇到各种问题,大大小小的坑也爬了不少,于是把它记录下来也方便自己以后查看,希望对需要的人也有点帮助。

环境准备

1.搭建好cocos打包环境(可自行百度)
2.热更环境(这里使用的是cocos官方商店插件,自己可以去商店下载安装即可,安装完成后重启Cocos,这里也不做过多介绍)

3. 下载官方热更范列教程
接下来进入正题。

热更流程

一.我们先打开官方教程,对照官方文档,了解一下大致流程,发现热更流程大致分为下面四点:
1.基于原生打包目录中的 res 和 src 目录生成本地 Manifest 文件。
2.创建一个热更新组件来负责热更新逻辑。
3.游戏发布后,若需要更新版本,则生成一套远程版本资源,包含 res 目录、src 目录和 Manifest 文件,将远程版本部署到服务端。
4.当热更新组件检测到服务端 Manifest 版本不一致时,就会开始热更新

具体请参考官方地址 热更新官方案列

下面详细介绍更新步骤
1.先构建

2.在项目下面打开热更新工具(前面在商店下载安装的热更新工具)

填写好对应的信息,点击生成即可。
3.如果第一次出包,则找到刚生成的热更新资源(在工程文件根目录下会生成packVersion,刚生成的资源包就在这个文件夹下),解压,然后发现四个文件,sre和res主要是资源文件和代码,project.manifest和version.mainfast
将project.manifest和version.manifest拷贝到工程目录下覆盖之前的(官方的mainfast直接写在了hotupdate.js里面,我们当然不能这样做,于是把他放在我的工程目录下),我目前工程放在这里,然后在重新构建。

4.在build文件夹下找到main.js文件

打开main.Js,在开头加上红色框里面的代码(不加的话会导致热更完下次打开游戏还是之前的资源)这里帮你写好了,直接复制即可(这里有一个问题注意,官方if (cc.sys.isNative) {}我按照这样写,android启动游戏会黑屏,然后改成if (jsb) {})

if (jsb) {
	var hotUpdateSearchPaths = localStorage.getItem('HotUpdateSearchPaths');
	if (hotUpdateSearchPaths) {
		jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
	}
}


这里是hotupdate.js代码

cc.Class({
    extends: cc.Component,

    properties: {
        panel: UpdatePanel,
        manifestUrl: {
            type: cc.Asset,
            default: null
        },
        updateUI: cc.Node,
        _updating: false,
        _canRetry: false,
        _storagePath: ''
    },

    checkCb: function (event) {
        cc.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:
                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.';
                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: function (event) {
        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();

                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:
                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.
            cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
            jsb.fileUtils.setSearchPaths(searchPaths);

            cc.audioEngine.stopAll();
            cc.game.restart();
        }
    },

    loadCustomManifest: function () {
        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: function () {
        if (!this._updating && this._canRetry) {
            this.panel.retryBtn.active = false;
            this._canRetry = false;
            
            this.panel.info.string = 'Retry failed Assets...';
            this._am.downloadFailedAssets();
        }
    },
    
    checkUpdate: function () {
        if (this._updating) {
            this.panel.info.string = 'Checking or updating ...';
            return;
        }
        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
            // Resolve md5 url
            var url = this.manifestUrl.nativeUrl;
            if (cc.loader.md5Pipe) {
                url = cc.loader.md5Pipe.transformURL(url);
            }
            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: function () {
        if (this._am && !this._updating) {
            this._am.setEventCallback(this.updateCb.bind(this));

            if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
                // Resolve md5 url
                var url = this.manifestUrl.nativeUrl;
                if (cc.loader.md5Pipe) {
                    url = cc.loader.md5Pipe.transformURL(url);
                }
                this._am.loadLocalManifest(url);
            }

            this._failCount = 0;
            this._am.update();
            this.panel.updateBtn.active = false;
            this._updating = true;
        }
    },
    
    show: function () {
        if (this.updateUI.active === false) {
            this.updateUI.active = true;
        }
    },

    // use this for initialization
    onLoad: function () {
        // Hot update is only available in Native build
        if (!cc.sys.isNative) {
            return;
        }
        this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'blackjack-remote-asset');
        cc.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, versionB) {
            cc.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, asset) {
            // When asset is compressed, we don't need to check its md5, because zip file have been deleted.
            var compressed = assetpressed;
            // 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.';

        if (cc.sys.os === cc.sys.OS_ANDROID) {
            // Some Android device may slow down the download process when concurrent tasks is too much.
            // The value may not be accurate, please do more test and find what's most suitable for your game.
            this._am.setMaxConcurrentTask(2);
            this.panel.info.string = "Max concurrent tasks count have been limited to 2";
        }
        
        this.panel.fileProgress.progress = 0;
        this.panel.byteProgress.progress = 0;
    },

    onDestroy: function () {
        if (this._updateListener) {
            this._am.setEventCallback(null);
            this._updateListener = null;
        }
    }
});

爬坑

当然按照这个流程热更新是没有问题得,但是我们当时有一个需求是需要根据服务器传过来得地址去动态选择热更新地址,那这个需求就不能满足我们了,于是又去各种查资料,发现只要能动态改变project.mainfast里面得地址就可以达到这个效果。于是就有两种情况。
① 用户从未进行过热更新,这个时候App内的.manifest文件只有一份,我们只需要修改这个.manifest文件即可。
②用户在安装该App期间使用过热更新,这时候App内部就会存在两份project.manifest文件了,此时我们要修改的project.manifest、version.manifest文件位于当初我们在项目中指定的热更新目录位置。如我的项目是:

这个目录根据自己写的实际情况来,然后就是修改project.mainfast的值(这里copy一下别人的代码)

这是连接地址

 modifyAppLoadUrlForManifestFile(newAppHotUpdateUrl, localManifestPath, resultCallback) {
    if (jsb.fileUtils.isFileExist(jsb.fileUtils.getWritablePath() + 'remoteAssets/project.manifest')) {
         console.log("有下载的manifest文件");
          let storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remoteAssets');
          console.log("StoragePath for remote asset : ", storagePath);
          let loadManifest = jsb.fileUtils.getStringFromFile(storagePath + '/project.manifest');
          let manifestObject = JSON.parse(loadManifest);
          manifestObject.packageUrl = "远程服务器地址';
          manifestObject.remoteManifestUrl = "远程服务器地址" + 'project.manifest';
          manifestObject.remoteVersionUrl = "远程服务器地址" + 'version.manifest';
        
          let afterString = JSON.stringify(manifestObject);
          let isWritten = jsb.fileUtils.writeStringToFile(afterString, storagePath + '/project.manifest');
          let initializedManifestPath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remoteAssets');
          if (!jsb.fileUtils.isDirectoryExist(initializedManifestPath)) jsb.fileUtils.createDirectory(initializedManifestPath);
          console.log("storagePath==", initializedManifestPath);
          console.log("没有下载的manifest文件", newAppHotUpdateUrl);
    
          //修改原始manifest文件
          let originManifestPath = localManifestPath;
          let originManifest = jsb.fileUtils.getStringFromFile(originManifestPath);
          let originManifestObject = JSON.parse(originManifest);
          originManifestObject.packageUrl = newAppHotUpdateUrl;
          originManifestObject.remoteManifestUrl = originManifestObject.packageUrl + 'project.manifest';
          originManifestObject.remoteVersionUrl = originManifestObject.packageUrl + 'version.manifest';
          let afterString = JSON.stringify(originManifestObject);
          let isWritten = jsb.fileUtils.writeStringToFile(afterString, initializedManifestPath + '/project.manifest');
      
          console.log("Written Status : ", isWritten);
        }
    },

修改完之后,在把这个project.mainfast转成mainfast文件,接下来就可以根据呢服务器传过来的地址动态修改热更地址了。

还有一个问题就是更新下载的资源是根据你远程project.mainfast的地址下载的。本地的project.mainfast主要用于下载远程project.mainfast,然后和远程比对MD5的值,不同的就下载。
到这里热更基本上流程已经走完了。
但是最后我们又遇到一个问题,就是热更过后,设置优先搜索路径之后(前面在main.js中加入的代码)会把你的热更路径设为优先搜索路径,下次遇到大版本更新的话,还是会优先从这个路径进行搜索,然后就会出现问题。所以,当遇到大版本更新的的时候,我们需要清除自己热更路径下资源。想要彻底清理一次本地的热更新缓存,那么怎么做呢,后来发现可以记录当前的游戏版本,检查 cc.sys.localStorage 中保存的版本是否匹配,如果不匹配则可以做一次清理操作:

 // 之前版本保存在 local Storage 中的版本号,如果没有认为是旧版本
 var previousVersion = parseFloat( cc.sys.localStorage.getItem('currentVersion') );
 // game.currentVersion 为该版本的常量
 if (previousVersion < game.currentVersion) {
     // 热更新的储存路径,如果旧版本中有多个,可能需要记录在列表中,全部清理
     jsb.fileUtils.removeDirectory(storagePath);
 }

好了,到这里基本上就结束了。(第一次发博客,如果发现有什么说的不对的地方,欢迎指正)

更多参考:

https://blog.csdn/qq2588258/article/details/94554112

本文标签: 前辈版本经验CocosCreator