[√]cocos creator 热更新源码剖析(1)

简介: [√]cocos creator 热更新源码剖析

基于cocos creator 2.4.11版本,其他版本应该差异不大,可以作为参考,

热更新的核心实现都在jsb.AssetsManager,对应的C++实现就是frameworks\cocos2d-x\extensions\assets-manager\AssetsManagerEx.cpp

基于热更新的流程阅读分析代码,更加直观。

热更新的一切的源头

this._assetsMgr.update();
void AssetsManagerEx::update()
{
    if (_updateEntry != UpdateEntry::NONE)
    {
        CCLOGERROR("AssetsManagerEx::update, updateEntry isn't NONE");
        return;
    }
    // 检查是否进行了初始化
    if (!_inited){
        CCLOG("AssetsManagerEx : Manifests uninited.\n");
        dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
        return;
    }
    if (!_localManifest->isLoaded())
    {
        CCLOG("AssetsManagerEx : No local manifest file found error.\n");
        dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
        return;
    }
    _updateEntry = UpdateEntry::DO_UPDATE;
    switch (_updateState) {
        case State::UNCHECKED:
        {
            _updateState = State::PREDOWNLOAD_VERSION;
        }
        case State::PREDOWNLOAD_VERSION:
        {
            downloadVersion();
        }
            break;
        case State::VERSION_LOADED:
        {
            parseVersion();
        }
            break;
        case State::PREDOWNLOAD_MANIFEST:
        {
            downloadManifest();
        }
            break;
        case State::MANIFEST_LOADED:
        {
            parseManifest();
        }
            break;
        case State::FAIL_TO_UPDATE:
        case State::READY_TO_UPDATE:
        case State::NEED_UPDATE:
        {
            // Manifest not loaded yet
            if (!_remoteManifest->isLoaded())
            {
                _updateState = State::PREDOWNLOAD_MANIFEST;
                downloadManifest();
            }
            else if (_updateEntry == UpdateEntry::DO_UPDATE)
            {
                startUpdate();
            }
        }
            break;
        case State::UP_TO_DATE:
        case State::UPDATING:
        case State::UNZIPPING:
            _updateEntry = UpdateEntry::NONE;
            break;
        default:
            break;
    }
}
  • 设置初始化的逻辑
void AssetsManagerEx::initManifests()
{
    _inited = true;
    _canceled = false;
    // Init and load temporary manifest
    _tempManifest = new (std::nothrow) Manifest();
    if (_tempManifest)
    {
        // 尝试去解析manifest文件,而这个文件
        _tempManifest->parseFile(_tempManifestPath);
        // Previous update is interrupted
        if (_fileUtils->isFileExist(_tempManifestPath))
        {
            // Manifest parse failed, remove all temp files
            if (!_tempManifest->isLoaded())
            {
                _fileUtils->removeDirectory(_tempStoragePath);
                CC_SAFE_RELEASE(_tempManifest);
                _tempManifest = nullptr;
            }
        }
    }
    else
    {
        _inited = false;
    }
    // Init remote manifest for future usage
    _remoteManifest = new (std::nothrow) Manifest();
    if (!_remoteManifest)
    {
        _inited = false;
    }
    if (!_inited)
    {
        CC_SAFE_RELEASE(_localManifest);
        CC_SAFE_RELEASE(_tempManifest);
        CC_SAFE_RELEASE(_remoteManifest);
        _localManifest = nullptr;
        _tempManifest = nullptr;
        _remoteManifest = nullptr;
    }
}

manifest数据结构

  • project.manfest的数据结构
{
    "version": "1.0",
    "packageUrl": "http://192.168.1.134:5520",
    "remoteManifestUrl": "http://192.168.1.134:5520/project.manifest",
    "remoteVersionUrl": "http://192.168.1.134:5520/version.manifest",
   "searchPaths": []
    "assets": {
        "src/cocos2d-jsb.js": {
            "size": 3335419,
            "md5": "eb37f3077bcb635d9b68bea6893ed217"
        },
    }
}
  • version.manifest
{
    "version": "1.0",
    "packageUrl": "http://192.168.1.134:5520",
    "remoteManifestUrl": "http://192.168.1.134:5520/project.manifest",
    "remoteVersionUrl": "http://192.168.1.134:5520/version.manifest"
}

version是project的精简版,可能是考虑到传输速率,尽快得知是否需要热更。

AssetsManagerEx初始化

热更新的入口逻辑,一般我们会在js层初始化

let url = manifest.nativeUrl;// manifest: cc.Asset
let storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remote-asset');
this._assetsMgr = new jsb.AssetsManager(url, storagePath,
    (versionLocal, versionRemote) => {
    }
);

在初始化AssetsManager的时候,有2个参数、3个参数:

#define MANIFEST_FILENAME       "project.manifest"
AssetsManagerEx::AssetsManagerEx(
    // 项目对应的本机manifest url
    // 比如assets/main/native/a1/a1d2694c-d3e7-4fd9-b7fc-58b171be56b1.manifest
    const std::string& manifestUrl,
    // 热更文件的存储路径,必须是可写目录下的一个目录
    const std::string& storagePath,
    // 自定义的版本对比函数,js层可以不设置,会执行对应2个参数的构造函数
    const VersionCompareHandle& handle
    )
{
    setStoragePath(storagePath);
    _cacheManifestPath = _storagePath + MANIFEST_FILENAME;
    loadLocalManifest(manifestUrl);
}
void AssetsManagerEx::setStoragePath(const std::string& storagePath)
{
    // 设置storagePath
    _storagePath = storagePath;
    _fileUtils->createDirectory(_storagePath);
    // 同步创建一个临时目录 remote-asset_temp
    _tempStoragePath = _storagePath;
    _tempStoragePath.insert(_storagePath.size() - 1, TEMP_PACKAGE_SUFFIX);
    _fileUtils->createDirectory(_tempStoragePath);
}
// loadLocalManifest的逻辑有点多,需要耐心看,难度不大。
bool AssetsManagerEx::loadLocalManifest(const std::string& manifestUrl)
{
    _manifestUrl = manifestUrl;
    // Init and load local manifest
    _localManifest = new (std::nothrow) Manifest();
    Manifest *cachedManifest = nullptr;
    // 查找上次下载的project.manifest.cache
    if (_fileUtils->isFileExist(_cacheManifestPath))
    {
        cachedManifest = new (std::nothrow) Manifest();
        cachedManifest->parseFile(_cacheManifestPath);// 读取解析文件
        if (!cachedManifest->isLoaded())
        {
            // 如果文件无效,会删除掉
            _fileUtils->removeFile(_cacheManifestPath);
            CC_SAFE_RELEASE(cachedManifest);
            cachedManifest = nullptr;
        }
    }
    std::vector<std::string> searchPaths = _fileUtils->getSearchPaths();
    if (cachedManifest)
    {
        // 将project.manifest.cache配置的searchPaths从游戏搜索路径中删除
        std::vector<std::string> cacheSearchPaths = cachedManifest->getSearchPaths();
        std::vector<std::string> trimmedPaths = searchPaths;
        for (auto path : cacheSearchPaths)
        {
            const auto pos = std::find(trimmedPaths.begin(), trimmedPaths.end(), path);
            if (pos != trimmedPaths.end())
            {
                trimmedPaths.erase(pos);
            }
        }
        _fileUtils->setSearchPaths(trimmedPaths);
    }
    // Load local manifest in app package
    _localManifest->parseFile(_manifestUrl);
    if (cachedManifest)
    {
        // Restore search paths
        _fileUtils->setSearchPaths(searchPaths);
    }
    if (_localManifest->isLoaded())
    {
        if (cachedManifest)
        {
            // 比较project.manifest.local和project.manifest.cache的版本号
            // 这里用到了handle参数,如果没有则使用默认的比较函数
            bool localNewer = _localManifest->versionGreater(cachedManifest, _versionCompareHandle);
            if (localNewer)
            {
                // 如果local高于cache,清空下载目录
                // 一般不会出现这种情况,不过用户很容易配置错误,导致这种情况
                _fileUtils->removeDirectory(_storagePath);
                _fileUtils->createDirectory(_storagePath);
                CC_SAFE_RELEASE(cachedManifest);
            }
            else
            {
                CC_SAFE_RELEASE(_localManifest);
                // 将cache的数据作为后续的基准
                _localManifest = cachedManifest;
            }
        }
        // 将manifest文件所在的目录(assets/main/native/a1/),包括配置里面searchPaths的路径,都加入到搜索路径
        prepareLocalManifest();
    }
    // Fail to load local manifest
    if (!_localManifest->isLoaded())
    {
        // 初始化阶段会遇到第一个异常,没有manifest文件或者解析json失败发生问题时,会抛出这个异常
        CCLOG("AssetsManagerEx : No local manifest file found error.\n");
        dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);
        return false;
    }
    // 这里就没啥逻辑了,都是一些准备工作
    initManifests();
    _updateState = State::UNCHECKED;
    return true;
}

这里有一个问题,如果发生了异常,这个异常无论是c++还是js层都无法捕获到,因为这是发生在new阶段,压根就没有机会去注册_eventCallback

// 内部的实现
void AssetsManagerEx::dispatchUpdateEvent(){
    if (_eventCallback != nullptr) {
        EventAssetsManagerEx* event = new (std::nothrow) EventAssetsManagerEx(_eventName, this, code, assetId, message, curle_code, curlm_code);
        _eventCallback(event);// 回调
        event->release();
    }
}
// 构造函数初始化
AssetsManagerEx::AssetsManagerEx(): _eventCallback(nullptr){} 
// 设置回调的api
void setEventCallback(const EventCallback& callback) {_eventCallback = callback;};

虽然也有提供api setEventCallback,但是你必须实例化类之后才能调用,在实例化类的过程中就会触发这个异常。

目录
相关文章
|
2月前
|
XML 存储 JSON
CocosCreator 面试题(十五)Cocos Creator如何内置protobuf JS版本?
CocosCreator 面试题(十五)Cocos Creator如何内置protobuf JS版本?
113 0
|
2月前
|
图形学
cocos creator DragonBones 源码阅读
cocos creator DragonBones 源码阅读
43 0
|
8月前
|
JavaScript C++
[√]cocos creator 热更新源码剖析(2)
[√]cocos creator 热更新源码剖析(2)
38 1
|
8月前
|
JavaScript
[√]cocos creator 热更新源码剖析(3)
[√]cocos creator 热更新源码剖析
120 1
|
2月前
|
编解码 前端开发 UED
CocosCreator 面试题(十一)Cocos Creator 屏幕适配
CocosCreator 面试题(十一)Cocos Creator 屏幕适配
165 0
|
2月前
|
JavaScript 前端开发 Java
CocosCreator 面试题(十)Cocos Creator 内存管理
CocosCreator 面试题(十)Cocos Creator 内存管理
263 0
|
9月前
Cocos Creator3.8 项目实战(二)cocos creator编辑器中绑定事件引发的bug解决
Cocos Creator3.8 项目实战(二)cocos creator编辑器中绑定事件引发的bug解决
|
9月前
Cocos Creator3.8 项目实战(一)cocos creator prefab 无法显示内容解决
Cocos Creator3.8 项目实战(一)cocos creator prefab 无法显示内容解决
123 0
|
9月前
Cocos Creator 解决热更新资源md5比较引发卡顿问题(四)
Cocos Creator 解决热更新资源md5比较引发卡顿问题
102 0
|
9月前
|
JavaScript Android开发 C++
Cocos Creator 解决热更新资源md5比较引发卡顿问题(一)
Cocos Creator 解决热更新资源md5比较引发卡顿问题
260 0