原因
在網絡上找了一圈也沒有找到一個像樣的說明。如果不是我們技術組的大大說這個東西可以用我都快放棄了。 稍微閱讀了一下這個組件的源代碼。發現該有的功能都有(如下所列)。 其實最初吸引我們用這個東西的功能是按文件更新。這種更新方式很好的解決了跨版本更新時。需要下載大量的重復文件的問題。 當然這種實現方式也有自己的問題,下面會有詳細的解釋
- 按文件更新
- 更新失敗的時候,只更新失敗的文件
- 更新失敗的情況下,下次重新啟動,只更新上次更新的錯誤文件
原理
本地會存在一個配置文件,網絡中(你的服務器中也會存在一個配置文件)。通過本地配置文件與網絡的配置文件進行比對。發現差異化數據然后,將網絡數據拉取到本地。
使用
創建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\n"); _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)
下課,解散