App热更新开启MD5Cache的兼容问题

  • Creator 版本:2.4.9

  • 目标平台: iOS / Android

  • 重现方式:热更一个版本后用更高版本覆盖安装,安卓更新不到最新版本,ios报错黑屏

  • 首个报错:

  • 之前哪个版本是正常的:

  • 手机型号:

  • 手机浏览器:

  • 编辑器操作系统:

  • 重现概率:

1,app的热更不支持md5cache,这个见帖子https://forum.cocos.org/t/assetbundle-md5cache/98000/9
2,按上面步骤修改后,可以热更代码,但有一个致命问题,加入从1.0.0热更到2.0.0,一切正常,但如果此时商店更新到3.0.0,直接下载覆盖安装,安卓无法更新,ios直接黑屏报错
3,ios报错信息如下:
[ devtools://devtools/bundled/js_app.html?v8only=true&ws=0.0.0.0:6086/00010002-0003-4004-8005-000600070008 ] in chrome browser to debug! For help see https://nodejs.org/en/docs/inspector JS: Enable batch GL commands optimization! JS: [WARN]: hotUpdateSearchPaths ["/var/mobile/Containers/Data/Application/DDA77D5E-9EE7-4037-96B2-5290D5D4F28E/Documents/remotePath/",""] ScriptEngine::onGetStringFromFile src/settings.2b1ec.js not found, possible missing file. ScriptEngine::runScript script src/settings.2b1ec.js, buffer is empty! [ERROR] Failed to invoke require, location: /Users/hcm-b0205/Downloads/jsb-default/frameworks/cocos2d-x/cocos/scripting/js-bindings/manual/jsb_global.cpp:300 ScriptEngine::onGetStringFromFile ./jsb-videoplayer.js not found, possible missing file. ScriptEngine::runScript script ./jsb-videoplayer.js, buffer is empty! [ERROR] Failed to invoke require, location: /Users/hcm-b0205/Downloads/jsb-default/frameworks/cocos2d-x/cocos/scripting/js-bindings/manual/jsb_global.cpp:300 ScriptEngine::evalString catch exception: ERROR: Uncaught TypeError: Cannot read property ‘debug’ of undefined, location: main.js:0:0 STACK: [0]window.boot@main.js:124 [1]anonymous@main.js:178 Uncaught Exception: - location : (see stack) - msg : Uncaught TypeError: Cannot read property ‘debug’ of undefined - detail : [0]window.boot@main.js:124 [1]anonymous@main.js:178 JS: [ERROR]: (see stack) Uncaught TypeError: Cannot read property ‘debug’ of undefined [0]window.boot@main.js:124 [1]anonymous@main.js:178 ScriptEngine::evalString script main.js, failed! 2022-07-30 15:56:34.940702+0800 肥鹅健身房[35523:1794214] [TraitCollection] Class CKBrowserSwitcherViewController overrides the -traitCollection getter, which is not supported. If you’re trying to override traits, you must use the appropriate API.
4,这个问题必现,而且很严重,所以劳烦官方看看,多谢

问题已经复现,我们查一下;
安卓没法更新的提示是什么? 是 “Failed to load local manifest” 吗?

,我是这么做的,在应用开启的时候,校验一下应用版本,应用版本不一致,删除热更缓存,把当前最新的应用版本存起来,用于下次版本校验

安卓的没具体看报错,但应该也是找不到setting文件,我在想实在不行,就打包时候不勾选md5cache,这样包内资源能正常热更,剩下的远程动态资源自己后处理全部文件修改成md5模式,包括配置文件也修改,粗略看了下,这块生成规则情况比较多,不知道有没有什么成熟的方案或者代码参考

多谢,getengineversion是自己实现的吗,问题是当前安装包的版本号很难获取到吧

这个随便百度一下就能获取到了

android:


ios:

多谢,不过最后我用了另一种后处理的方式解决了该问题
1,打包依然不选择md5cache,这样支持包内资源的热更,也符合cc的设计规则
2,剩下的就是包外动态资源md5的处理,也就是说需要对这个远程ab生成对应的md5
3,参考了生成规则,在打包完成做后处理,目前能正常生成md5的文件及配置
4,代码
build-finished后调用versionMd5.build(buildRoot + “/remote”,version);

version-md5.js

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
const decodeUuid = require('../utils/decode-uuid');
var assetsRoot = "";
function md5Version(bundleRoot,version) {
    let confUrl = bundleRoot + "/config.json";
    if (!fs.existsSync(confUrl)) {
        return;
    }
    Editor.log('md5 remote bundle:', bundleRoot);
    let data = fs.readFileSync(confUrl, { encoding: "utf8" });
    let conf = JSON.parse(data);
    let versionData = { import: [], native: [] };
    conf.versions = versionData;
    versionDir(bundleRoot + "/import", conf, true);
    versionDir(bundleRoot + "/native", conf, false);

    let newpath=bundleRoot + "/config."+version+".json"
    fs.writeFileSync(confUrl, JSON.stringify(conf));
    fs.renameSync(confUrl,newpath);
    Editor.log('md5 remote suc:' + bundleRoot);
}

function versionDir(dir, bundleConf, isImport) {
    var stat = fs.statSync(dir);
    if (!stat.isDirectory()) {
        return;
    }
    var subpaths = fs.readdirSync(dir), subpath, md5, filename, uuid;
    for (var i = 0; i < subpaths.length; ++i) {
        if (subpaths[i][0] === '.') {
            continue;
        }
        subpath = path.join(dir, subpaths[i]);
        if (subpath.endsWith(".pvr")) {
            continue;
        }
        stat = fs.statSync(subpath);
        if (stat.isDirectory()) {
            versionDir(subpath, bundleConf, isImport);
        } else if (stat.isFile()) {
            filename = subpaths[i];
            filename = filename.split(".")[0];
            uuid = filename;
            md5 =getFileHash(subpath,uuid);
            md5 = md5.substring(0, 5);
            let index = getPathIndex(uuid, bundleConf);
            index = index == -1 ? uuid : index;
            let path = subpath.replace(filename, filename + "." + md5);
            fs.renameSync(subpath, path);
            if (isImport) {
                bundleConf.versions.import.push(index, md5);
            } else {
                bundleConf.versions.native.push(index, md5);
                //同时拷贝pvr
                let pvrpath = subpath.replace(".png", ".pvr");
                if (fs.existsSync(pvrpath)) {
                    fs.renameSync(pvrpath, path.replace(".png", ".pvr"));
                }
            }
        }
    }
}

function getFileHash(path,uuid){
    //找出同一uuid的所有文件
    let parent = path.slice(0, path.lastIndexOf('/'));
    var subpaths = fs.readdirSync(parent);
    let paths=[];
    for (var i = 0; i < subpaths.length; ++i) {
        let id = subpaths[i].split(".")[0];
        if(id==uuid){
            paths.push(parent+"/"+subpaths[i]);
        }
    }
    //计算hash
    let cryptoHash=crypto.createHash('md5');
    for (let i = 0; i < paths.length; i++) {
        cryptoHash.update(fs.readFileSync(paths[i]));
    }
    let hash=cryptoHash.digest('hex');
    hash=hash.slice(0,5);
    return hash;
}

function getPathIndex(uuid, bundleConf) {
    for (let i = 0; i < bundleConf.uuids.length; i++) {
        let rid = decodeUuid(bundleConf.uuids[i]);
        if (rid == uuid) {
            return i;
        }
    }
    return -1;
}

module.exports = {
    build: function (src,version) {
        Editor.log('md5 remote start');
        assetsRoot = src;
        var subpaths = fs.readdirSync(assetsRoot);
        for (var i = 0; i < subpaths.length; ++i) {
            let bundleRoot = path.join(assetsRoot, subpaths[i]);
            stat = fs.statSync(bundleRoot);
            if (stat.isDirectory()) {
                md5Version(bundleRoot,version);
            }
        }
        Editor.log('md5 remote complete');
    }
}

decode-uuid.js
var BASE64_KEYS = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’;
var BASE64_VALUES = new Array(123); // max char code in base64Keys
for (let i = 0; i < 123; ++i) BASE64_VALUES[i] = 64; // fill with placeholder(’=’) index
for (let i = 0; i < 64; ++i) BASE64_VALUES[BASE64_KEYS.charCodeAt(i)] = i;

    var HexChars = '0123456789abcdef'.split('');

    var _t = ['', '', '', ''];
    var UuidTemplate = _t.concat(_t, '-', _t, '-', _t, '-', _t, '-', _t, _t, _t);
    var Indices = UuidTemplate.map(function (x, i) { return x === '-' ? NaN : i; }).filter(isFinite);

    // fcmR3XADNLgJ1ByKhqcC5Z -> fc991dd7-0033-4b80-9d41-c8a86a702e59
    module.exports = function (base64) {
        if (base64.length !== 22) {
            return base64;
        }
        UuidTemplate[0] = base64[0];
        UuidTemplate[1] = base64[1];
        for (var i = 2, j = 2; i < 22; i += 2) {
            var lhs = BASE64_VALUES[base64.charCodeAt(i)];
            var rhs = BASE64_VALUES[base64.charCodeAt(i + 1)];
            UuidTemplate[Indices[j++]] = HexChars[lhs >> 2];
            UuidTemplate[Indices[j++]] = HexChars[((lhs & 3) << 2) | rhs >> 4];
            UuidTemplate[Indices[j++]] = HexChars[rhs & 0xF];
        }
        return UuidTemplate.join('');
    };