原因
在网络上找了一圈也没有找到一个像样的说明。如果不是我们技术组的大大说这个东西可以用我都快放弃了。 稍微阅读了一下这个组件的源代码。发现该有的功能都有(如下所列)。 其实最初吸引我们用这个东西的功能是按文件更新。这种更新方式很好的解决了跨版本更新时。需要下载大量的重复文件的问题。 当然这种实现方式也有自己的问题,下面会有详细的解释
- 按文件更新
- 更新失败的时候,只更新失败的文件
- 更新失败的情况下,下次重新启动,只更新上次更新的错误文件
原理
本地会存在一个配置文件,网络中(你的服务器中也会存在一个配置文件)。通过本地配置文件与网络的配置文件进行比对。发现差异化数据然后,将网络数据拉取到本地。
使用
创建AssetsManagerEx
_assets_manager_ex = AssetsManagerEx::create("Config/project.manifest", FileUtils::getInstance()->getWritablePath() + "DownLoad"); _assets_manager_ex->retain();
注解
很明显这是创建了今天的主角AssetsManagerEx然后拿了他的指针。参数上第一个是本地的配置文件地地址,第二个参数是你从网络上拉取的数据的本地保存地址。
或许你现在想问,这个配置文件是什么格式的,我需要怎么来生成这个配置文件,配置文件格式如下
本地配置文件
{ "packageUrl" : "http://tools.itharbors.com/assets_manager/AMTestScene1/", "remoteManifestUrl" : "http://7xs6k4.com1.z0.glb.clouddn.com/project.manifest", "remoteVersionUrl" : "http://tools.itharbors.com/assets_manager/AMTestScene1/version_dev.manifest", "version" : "1.0.0", "engineVersion" : "3.0 beta", "assets" : { "Images/background1.jpg" : { "md5" : "..." } }, "searchPaths" : [ ] }
注解
这是一个Json的数据格式
packageUrl是你要下载具体内容的地址,程序允许的时候会将你的资源名称比如Image/xxx.png添加到packageUrl的后边组成完整的连接,相对于我们刚才举得例子的位置就是http://tools.itharbors.com/assets_manager/AMTestScene1/Image/xxx.png。然后从这个连接中拉取数据到本地并且保存为Image/xxx.png
remoteManifestUrl是远程的配置文件地址,与你本地的配置文件做为对应。就是最前边原理里边提到的拉取远程数据跟本地数据做对比的远程数据
remoteVersionUrl 因为配置文件中可能存在很多需要更新的配置文件的信息,所以频发的拉取这个数据是非常要命的。所以AssetsManagerEx提供了一个让你只拉取版本信息的连接,具体内容跟远程配置文件格式相似,只是没有了文件的相关配置,下面我会给出相应的范例
远程版本文件
{ "packageUrl": "http://tools.itharbors.com/assets_manager/AMTestScene1/", "remoteManifestUrl": "http://tools.itharbors.com/assets_manager/AMTestScene1/project_dev.manifest", "remoteVersionUrl": "http://tools.itharbors.com/assets_manager/AMTestScene1/version_dev.manifest", "version": "1.2.0", "engineVersion": "3.0 dev" }
远程配置文件
{ "packageUrl" : "http://tools.itharbors.com/assets_manager/AMTestScene1/", "remoteManifestUrl" : "http://7xs6k4.com1.z0.glb.clouddn.com/project_dev.manifest", "remoteVersionUrl" : "http://tools.itharbors.com/assets_manager/AMTestScene1/version_dev.manifest", "version" : "1.2.0", "engineVersion" : "3.x dev", "assets" : { "Images/assetMgrBackground1.jpg" : { "md5" : "....." }, "Images/ball.png" : { "md5" : "..." }, "Images/blocks.png" : { "md5" : "..." }, "compressed.zip" : { "md5" : "...", "compressed" : true }, "Images/Bird.jpg" : { "md5" : "..." }, "Images/Daisy_Flower.jpg" : { "md5" : "..." }, "Images/Mountain_Reflections.jpg" : { "md5" : "..." }, "Images/Plitvice_National_Park.jpg" : { "md5" : "..." }, "Images/sakountala.jpg" : { "md5" : "..." }, "Images/Snake_River.jpg" : { "md5" : "..." }, "Images/Thunder.jpg" : { "md5" : "..." }, "Images/Tranquil_Lagoon.jpg" : { "md5" : "..." }, "Images/Tyrol.jpg" : { "md5" : "..." }, "Images/univ-lille1.jpg" : { "md5" : "..." }, "Images/Yellow_Garden_Flowers.jpg" : { "md5" : "..." }, "Images/Yellow_Lilly.jpg" : { "md5" : "..." }, "Images/Yellow_Tulips.jpg" : { "md5" : "..." } }, "searchPaths" : [ ] }
业务相关的代码
if (!_assets_manager_ex->getLocalManifest()->isLoaded()) { onLoadSuccess(); } else { _assets_manager_listener = cocos2d::extension::EventListenerAssetsManagerEx::create(_assets_manager_ex, [this](EventAssetsManagerEx * event){ switch (event->getEventCode()) { case cocos2d::extension::EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST: case cocos2d::extension::EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST: case cocos2d::extension::EventAssetsManagerEx::EventCode::ERROR_PARSE_MANIFEST: case cocos2d::extension::EventAssetsManagerEx::EventCode::ERROR_DECOMPRESS: case cocos2d::extension::EventAssetsManagerEx::EventCode::UPDATE_FAILED: { this->onLoadError((int)event->getEventCode()); break; } case cocos2d::extension::EventAssetsManagerEx::EventCode::ERROR_UPDATING: { tryDownloadFaildAssets(); break; } case cocos2d::extension::EventAssetsManagerEx::EventCode::ASSET_UPDATED: { tryDownloadFaildAssets(); break; } case cocos2d::extension::EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE: { CCLOG("已经是最新版本,直接进入主界面"); this->onAllFileIsNew(); break; } case cocos2d::extension::EventAssetsManagerEx::EventCode::UPDATE_FINISHED: { CCLOG("更新完成重新加载"); this->onLoadSuccess(); break; } case cocos2d::extension::EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION: { // this->onLoadPercent(event->getPercent()); this->onLoadPercent(event->getPercentByFile()); break; } case cocos2d::extension::EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND: { CCLOG("发现新本版开始升级"); break; } default: break; } }); Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_assets_manager_listener, 1); _assets_manager_ex->update(); }
代码注解
- 其实意思已经很明显了,就是查看一下当前是不是已经加载完成了。如果已经加载完成了,那么直接跳过,进入游戏就好了
- 如果没有加载完成就需要添加上监听器,然后启动下载,等待回调就好了
- 他的枚举的类型我就不一一进行解释了,自己看着字面上,应该可以猜到具体是什么。
- 其实说明到现在已经基本上没有啥好说的了。
更深入一些的东西(AssetsManagerEx已经实现的功能和具体的实现过程)
说明
其实之前的说明已经能够让你用起来了,不过他有些设计上的思路,我觉得还不错,所以专门来讲解一下原理。如果你觉得原理这种东西没有什么好了解的能用就行了。好的,请你跳过这一节,进入下一节,并且把它读完,因为不读完,(3.9版本肯定3.10应该)不能用。
进行比对
- 拉取配置文件到本地,然后添加.temp后缀作为临时文件
- 然后将临时文件跟本地文件进行比对,得出差异结果集,然后操作本地文件
- 这个文件比对是拿md5数据进行确定这个文件是否正常的。不过这个md5是指配置文件里边的md5而不是这个文件实际的md5
- 请你不要认为这套md5是文件的md5校验,把它认为是一个文件的版本号可能更容易理解一些
- 之所以文件用md5进行比对,应该是方便后台在构建这个文件时有一个可靠的依据
怎样进行不修改代码的情况下替换资源
- 通过添加SearchPath进行文件替换
- 其实组件并没有直接替换掉你包里边的文件,而只是添加了优先搜索目录来进行文件的优先查找的权限
下载文件的确立过程
- 这个组件会修改他自己下载的.temp组件,其实就是添加上下载的状态,然后重新保存一边
- 重新保存的时机在所有的文件尝试下载过一边之后
- 下载一边的意思是说成功和失败都算
- 但是下载过程中,软件意外退出或者主动退出,是不会保存状态的,如果需要,则需要业务进行手动的保存调用
如何做到重启之后,依然沿着上次的下载过程继续下载的
- 如果程序检测到存在.temp文件,并且.temp文件与远程文件的版本是一样的话,那么直接认为.temp文件是最新版本,尝试从.temp文件中尝试重新加载上一次的数据
- 也就是说,只有所有的文件下载过一遍之后,系统主动保存到.temp文件之后。再重新启动能够重新复盘
- 或者下载到一半,你觉得用户可能要退出的情况下保存了这个文件也能够复盘成功
修订
说明
- AssetsManagerEx在3.9的Demo上根本跑不起来
- 3.10没有测试过,我看了下源码的地方貌似也没有修改
BUG的表现
在任何一个资源下载失败的情况下,你会发现更新已经卡住再也不动了。
BUG的原因
这个组件的内部会有一个计数,未下载完的计数。组件在成功下载的时候回将这个计数减一。所有都成功的时候能正常的跑通。但是存在未下载完成的时候。这个计数没有减一,所以系统一直在等下载失败的数据下载完成。而下载失败的数据已经不再下载了,所以根本不会存在减一的情况。所以系统就会掉到这个BUG中,而造成业务链的断裂
修复
将AssetsManagerEx::onError方法修改成下边这个样子
void AssetsManagerEx::onError(const network::DownloadTask& task, int errorCode, int errorCodeInternal, const std::string& errorStr) { // Skip version error occured if (task.identifier == VERSION_ID) { CCLOG("AssetsManagerEx : Fail to download version file, step skipped "); _updateState = State::PREDOWNLOAD_MANIFEST; downloadManifest(); } else if (task.identifier == MANIFEST_ID) { dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST, task.identifier, errorStr, errorCode, errorCodeInternal); } else { auto unitIt = _downloadUnits.find(task.identifier); // Found unit and add it to failed units if (unitIt != _downloadUnits.end()) { --_totalWaitToDownload; DownloadUnit unit = unitIt->second; _failedUnits.emplace(unit.customId, unit); } dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_UPDATING, task.identifier, errorStr, errorCode, errorCodeInternal); } }
Demo
别以为下载了就好用,去看看修订这一章节然后你才能成功(仅限3.9)
下课,解散