基于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
,但是你必须实例化类之后才能调用,在实例化类的过程中就会触发这个异常。