热更新范例 和AssetBundle热更新MD5Cache冲突问题

Creator 版本: 2.4.2

目标平台:android

问题:之前老的方式热更新编译的时候不能勾选MD5Cache,通过AssetBundle热更新子游戏,编译需要勾选MD5Cache,两者冲突

为啥需要两个热更新方式同时使用,是因为老的更新方式用来更新内置的包比如resources内容变化等需要更新,AssetBundle方式用来更新子游戏

如果只使用老的更新方式,那子游戏还得做资源分离,那新版本AssetBundle也没有意义,如果只使用AssetBundle热更新模式,那内置的包main,resources等没办法更新

论坛有讨论过修改main.js来加载远程内置main,resources等包的方法,但是这个加载远程的包,版本也是写死在main.js里的 无法从服务器动态获取

那么请问各位大大,Md5Cache之间经常切换,必然 会出现问题,热更新出问题可不是小问题 ,怎么平衡这之间的矛盾,既能更新子游戏,又能更新内置的main,resources等包, 难道还得回到低版本那种子游戏更新模式么???

2赞

打两次包.
勾选md5cache 生成远程子包
不勾选, 生成主包.

,

1赞

顶,虽然两次构建可以,但是这样容易出现人为错误。

建议引擎组能多一个选项,MD5和脚本加密这两个地方可以勾选区分远程Bundle和内置Bundle采用不同的处理···

@jare @EndEvil

我是没搞明白为何原生不能勾选md5,兼容一下让所有的平台都支持md5不好吗

我之前问过,
原生平台可以勾选 MD5

麻烦认真看下楼主说的

论坛有讨论过修改main.js来加载远程内置main,resources等包的方法,但是这个加载远程的包,版本也是写死在main.js里的 无法从服务器动态获取

楼主说的是 远程加载内置包.

我说的是用传统的方式热更新, 勾选md5cache 一样可以实现. .

1赞

你是对的,抱歉

这个我是写插件强行支持 MD5Cache

要修改下,官方提供的version_generator.js文件,让它在manifest文件中记录main.js文件

/**
 * @description 这是一个用于生成 Manfiest 文件的 NodeJS 脚本。使用方式如下:
 * @example node version_generator.js -v 1.0.0 -u http://your-server-address/remote-assets/ -s native/package/ -d assets/
 * @param `-v` 指定 Manifest 文件的主版本号。
 * @param `-u` 指定服务器远程包的地址,这个地址需要和最初发布版本中 Manifest 文件的远程包地址一致,否则无法检测到更新。
 * @param `-s` 本地原生打包版本的目录相对路径。
 * @param `-d` 保存 Manifest 文件的地址。
 * 
 * 
 * !!!!!改动
 * #1  加变量,地址更改时就改address就行了
 * 
 * #2 输出文件夹 dest 文件夹创建时存在不会报错
 * 
 * #3 增加变量dest1,设置assests下 project.manifest的路径。
 *    放心,fs.writeFile 方法不会造成uuid的更改
 *    我看有人直接把dest设为assets,这样的话assets就多了 version 和它的meta文件,这是没用的,只是热更用来放服务器上的。
 *
 * #4 
 *    增加 readFile 方法,热更指定文件<用于勾选md5 cache后更新找不到资源的问题,更新main.js文件>(build/jsb-link/main.js)
 *    manifest 记录 build/jsb-link/main.js的信息
 * 
 * 
 * 使用方法:
 * 1.第一次定版(1.0.0)后,creator build一下, 配置version_generator.js 里面的参数(dest、dest1、src、address、version), node 执行该脚本
 * 2.将生成的project.manifest 挂到你个热更新组件上
 * 3.再次build下项目(项目在第2步变动了),AS编译、生成APK
 * 
 * 有热更新了(如1.0.1)
 * 1.定版后,先build下项目,配置version_generator.js 里面的参数(address、version), node 执行该脚本
 * 2.将assets、src  main.js  project.manifest version.mainfest放到热更服务器下
 * 3. (不发apk可以不打)再次build下项目(项目在第1步变动了),AS编译、生成APK
 */

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');

// #1
var address = "http://192.168.140.52:5151/games/tests/2.0.1/";
var manifest = {
    packageUrl: address,
    remoteManifestUrl: address + 'project.manifest',
    remoteVersionUrl: address + 'version.manifest',
    version: '2.0.1',
    assets: {},
    searchPaths: []
};

// var dest = './remote-assets/';// 生成的manifest文件存放目录
var dest = './tttt/';// 生成的manifest文件存放目录
// var dest = './assets/';// 生成的manifest文件存放目录,多个version就多吧,无所谓;省事,不要复制了

// #3
var dest1 = "./assets/";// 项目下project.manifest 文件路径;

// var src = './jsb-default/';// 本地原生打包版本的目录相对路径,绝对路径也可以, 主要用到下面的assets 和 src
// var src = "E:/T/CCB/5/jsb-default/";

var src = "E:/T/CCB/6/jsb-link/";
// var src = "E:/T/CCB/6/jsb-default/";

// 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].compressed = true;
            }
        }
    }
}

// #4
function readFile(filepath, obj) {
    var stat = fs.statSync(filepath);
    if (!stat.isFile()) {
        return;
    }
    var size, md5, compressed, relative;
    // Size in Bytes
    size = stat['size'];
    md5 = crypto.createHash('md5').update(fs.readFileSync(filepath)).digest('hex');
    compressed = path.extname(filepath).toLowerCase() === '.zip';

    relative = path.relative(src, filepath);
    relative = relative.replace(/\\/g, '/');
    relative = encodeURI(relative);
    obj[relative] = {
        'size': size,
        'md5': md5
    };
    if (compressed) {
        obj[relative].compressed = true;
    }
}

var mkdirSync = function (path) {
    try {
        fs.mkdirSync(path);
    } catch (e) {
        if (e.code != 'EEXIST') throw e;
    }
}

// Iterate assets and src folder
readDir(path.join(src, 'src'), manifest.assets);
readDir(path.join(src, 'assets'), manifest.assets);
// #4
readFile(path.join(src, 'main.js'), manifest.assets);

// #2
var destManifest = path.join(dest, 'project.manifest');
var destVersion = path.join(dest, 'version.manifest');

// mkdirSync(dest);
fs.mkdirSync(path.dirname(destManifest),{recursive:true});

fs.writeFile(destManifest, JSON.stringify(manifest), (err) => {
    if (err) throw err;
    console.log('ttt Manifest successfully generated');
});

if (dest1) {
    var assetsManifest = path.join(dest1, 'project.manifest');
    fs.writeFile(assetsManifest, JSON.stringify(manifest), (err) => {
        if (err) throw err;
        console.log('assets 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');
});

勾选Md5 Cache后,按照 kenshin 大佬提供的修改方法 修改 jsb-link\frameworks\runtime-src\Classes\AppDelegate.cpp ,就可以正常CC 热更了

build后打apk包前修改,文件位置 .\build\jsb-link\frameworks\runtime-src\Classes\AppDelegate.cpp

kenshin评论原址


bool AppDelegate::applicationDidFinishLaunching()
{
se::ScriptEngine *se = se::ScriptEngine::getInstance();
    
    jsb_set_xxtea_key("21cf11ff-70b7-49");
    jsb_init_file_operation_delegate();
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    // Enable debugger here
    jsb_enable_debugger("0.0.0.0", 6086, false);
#endif
    
    se->setExceptionCallback([](const char *location, const char *message, const char *stack) {
        // Send exception information to server like Tencent Bugly.
        cocos2d::log("\nUncaught Exception:\n - location :  %s\n - msg : %s\n - detail : \n      %s\n", location, message, stack);
    });
    
    jsb_register_all_modules();
    
    se->start();
    
    

-----  start --------
    // 提前将热更新目录添加到搜索路径,这样main.js文件就可以更新,规避md5cache问题
    auto fs = FileUtils::getInstance();
    auto paths = fs->getSearchPaths();
    // paths.insert(paths.begin(), fs->getWritablePath()+ "<你的热更新缓存目录>") // jsb.assetManager 第二个参数值 如:官方教程里的那个名;
    paths.insert(paths.begin(), fs->getWritablePath()+ "blackjack-remote-asset");
    fs->setSearchPaths(paths);
------   end -------------

    se::AutoHandleScope hs;
    jsb_run_script("jsb-adapter/jsb-builtin.js");
    jsb_run_script("main.js");
    
    se->addAfterCleanupHook([]() {
        JSBClassType::destroy();
    });
    
    return true;
}