Cocos 更新时反复杀进程,导致差异更新失效的Bug
Cocos 更新时反复杀进程时,差异更新失效的问题:
问题复现步骤:
1、在project.manifest.temp 文件下载成功后,下载Assets资源的时候杀掉进程
2、重启游戏,继续更新时会使用上次下载成功的project.manifest.temp文件,这个时候因为没有将文件下载状态保存,而更新的时候又判断没有下再成功就去下载,就导致将所有文件都下载了。
下载流程分析
1、 project.manifest.temp 文件下载成功之前如果kill进程,下次进入就会删除这个文件,重新下载;PS:调用update
接口开始更新时kill进程,再次启动都会引起project.manifest.temp的重新下载,这也是导致bug的原因之一。
// 这个方法会在更新之前调用
void AssetsManagerEx::initManifests(const std::string& manifestUrl)
{
_inited = true;
// Init and load local manifest
_localManifest = new (std::nothrow) Manifest();
if (_localManifest)
{
loadLocalManifest(manifestUrl);
// Init and load temporary manifest
_tempManifest = new (std::nothrow) Manifest();
if (_tempManifest)
{
_tempManifest->parse(_tempManifestPath);
// 这里判断如果文件不是完整的,并且存在就删除它;
if (!_tempManifest->isLoaded() && _fileUtils->isFileExist(_tempManifestPath))
_fileUtils->removeFile(_tempManifestPath);
}
else
{
_inited = false;
}
// Init remote manifest for future usage
_remoteManifest = new (std::nothrow) Manifest();
if (!_remoteManifest)
{
_inited = false;
}
}
else
{
_inited = false;
}
if (!_inited)
{
CC_SAFE_DELETE(_localManifest);
CC_SAFE_DELETE(_tempManifest);
CC_SAFE_DELETE(_remoteManifest);
}
}
2、开始下载根据project.manifest.temp 临时文件是否完整存在来决定是恢复之前的下载状态,还是根据重新下载回来的manifest文件与本地文件对比差异度,决定下载那些。
void AssetsManagerEx::startUpdate()
{
if (_updateState != State::NEED_UPDATE)
return;
_updateState = State::UPDATING;
// Clean up before update
_failedUnits.clear();
_downloadUnits.clear();
_compressedFiles.clear();
_totalWaitToDownload = _totalToDownload = 0;
_percent = _percentByFile = _sizeCollected = _totalSize = 0;
_downloadedSize.clear();
_totalEnabled = false;
// Temporary manifest exists, resuming previous download
if (_tempManifest->isLoaded() && _tempManifest->versionEquals(_remoteManifest))
{
// 文件是完整的,直接从文件恢复下载,并且从文件中读取下载状态,来判断是否需要下载
// 更新完成之前kill进程和每次启动程序下载project.manifest.temp文件都会导致下载状态被清空
// 恢复下载是根据manifest临时文件中保存的下载状态来决定,详细见3
_tempManifest->genResumeAssetsList(&_downloadUnits);
_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();
this->batchDownload();
std::string msg = StringUtils::format("Resuming from previous unfinished update, %d files remains to be finished.", _totalToDownload);
CCLOG(msg);
// this time , the remoteManifest(has no DownloadState) file overwrite tempManifest file. when we kill process, and restart, it will uses error file.
// 为了避免被重新下载的manifest文件覆盖掉下载状态,我们这里需要将当前状态再次写入文件备份
_tempManifest->saveToFile(_tempManifestPath);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, "", msg);
}
// Check difference
else
{
// Temporary manifest not exists or out of date,
// it will be used to register the download states of each asset,
// in this case, it equals remote manifest.
_tempManifest->release();
_tempManifest = _remoteManifest;
std::unordered_map<std::string, Manifest::AssetDiff> diff_map = _localManifest->genDiff(_remoteManifest);
std::string log = StringUtils::format("AssetsManagerEx : Diff file size %d", diff_map.size());
CCLOG(log);
if (diff_map.size() == 0)
{
CCLOG("AssetsManagerEx updateSucceed");
updateSucceed();
}
else
{
// Generate download units for all assets that need to be updated or added
std::string packageUrl = _remoteManifest->getPackageUrl();
for (auto it = diff_map.begin(); it != diff_map.end(); ++it)
{
Manifest::AssetDiff diff = it->second;
if (diff.type == Manifest::DiffType::DELETED)
{
CCLOG("AssetsManagerEx DELETED " + diff.asset.path);
_fileUtils->removeFile(_storagePath + diff.asset.path);
}
else
{
std::string path = diff.asset.path;
CCLOG("AssetsManagerEx " + diff.asset.path + " type "+ diff.type);
// Create path
_fileUtils->createDirectory(basename(_storagePath + path));
DownloadUnit unit;
unit.customId = it->first;
unit.srcUrl = packageUrl + path;
unit.storagePath = _storagePath + path;
_downloadUnits.emplace(unit.customId, unit);
}
}
// Set other assets' downloadState to SUCCESSED
auto &assets = _remoteManifest->getAssets();
for (auto it = assets.cbegin(); it != assets.cend(); ++it)
{
const std::string &key = it->first;
auto diffIt = diff_map.find(key);
if (diffIt == diff_map.end())
{
// 根据文件对比,将不需要下载的文件的状态设置为下载成功
_tempManifest->setAssetDownloadState(key, Manifest::DownloadState::SUCCESSED);
}
}
_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();
this->batchDownload();
std::string msg = StringUtils::format("Start to update %d files from remote package.", _totalToDownload);
// 为了避免进程在更新完成之前被kill导致下载状态丢失,这里先保存文件,备份一次
_tempManifest->saveToFile(_tempManifestPath);
dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, "", msg);
}
}
}
3、恢复下载是,需要根据之前保存在临时文件中的下载状态来决定下载那些文件
void Manifest::genResumeAssetsList(DownloadUnits *units) const
{
for (auto it = _assets.begin(); it != _assets.end(); ++it)
{
Asset asset = it->second;
if (asset.downloadState != DownloadState::SUCCESSED)
{
DownloadUnit unit;
unit.customId = it->first;
unit.srcUrl = _packageUrl + asset.path;
unit.storagePath = _manifestRoot + asset.path;
units->emplace(unit.customId, unit);
}
}
}
Cocos 更新时反复杀进程,导致差异更新失效的Bug的更多相关文章
- SQL SERVER 2008 R2 SP1更新时,遇上共享功能更新失败解决方案
SQL SERVER 2008 R2 SP1更新时,遇上共享功能更新失败的问题,可作如下尝试: 更新失败后,在windows的[事件查看器→应用程序]中找到来源为MsiInstaller,事件ID为1 ...
- Android SDK 更新时修改hosts文件仍然无法更新,可试试这个方法……
Android SDK 更新时修改hosts文件仍然无法更新,此时必定万分蛋疼.在hosts文件中更换了各种ip,仍然解决不了!!!!!!!!!!!!!!? 第一步: 打开此软件,等待服务器连接 第二 ...
- 关于SVN更新时文件加锁的小结
今天使用SVN更新应用,出现了下面的问题: update D:/workspace/acode/resource/springconf -r 6622 --force Attempted to ...
- [Erlang10]为什么热更新时,Shell执行2次l(Module)后会把原来用到Module的进程 kill?
0. 问题引入: -module(hot_code_server). -compile(export_all). start() –> erlang:register(?MODULE, erla ...
- Tomcat启动时项目反复载入,导致资源初始化两次的问题
近期在项目开发測试的时候,发现Tomcat启动时项目反复载入,导致资源初始化两次的问题 导致该问题的解决办法: 例如以下图:在Eclipse中将Server Locations设置为"Us ...
- Windows远程时无法复制文件--杀进程rdpclip.exe,然后再启动
1.远程登陆到主机上 2.任务管理器杀进程rdpclip.exe 3.[开始],搜索rdpclip.exe,点击运行 此时重新复制文件,可以跨主机复制啦 原以为是公司网络限制,现在看来还是没那么先进嘛
- mysql查询更新时的锁表机制分析
为了给高并发情况下的mysql进行更好的优化,有必要了解一下mysql查询更新时的锁表机制. 一.概述 MySQL有三种锁的级别:页级.表级.行级.MyISAM和MEMORY存储引擎采用的是表级锁(t ...
- Postsharp 破解工具(通杀版,持续更新)
2019.04.18 重要说明 VS2019 正式版已经发布了,Postsharp v6.2.2-Preview(预览版)也开始支持VS2019.不过截至目前,该预览版还不是特别稳定,因此提醒下大家在 ...
- mysql查询更新时的锁表机制分析(只介绍了MYISAM)
为了给高并发情况下的mysql进行更好的优化,有必要了解一下mysql查询更新时的锁表机制. 一.概述 MySQL有三种锁的级别:页级.表级.行级.MyISAM和MEMORY存储引擎采用的是表级锁(t ...
随机推荐
- Git 日常工作中使用的命令记录
前言 这篇文章主要是介绍我在使用Git中的有一些忘记了,但是很重要的命令. 20190424 Git 历史信息 username 和 email 更改 git config alias.chang ...
- fdisk
fdisk管理分区 参数 作用 m ...
- XamarinAndroid组件教程RecylerView自定义适配器动画
XamarinAndroid组件教程RecylerView自定义适配器动画 如果RecyclerViewAnimators.Adapters命名空间中没有所需要的适配器动画,开发者可以自定义动画.此时 ...
- ENQUIRE the predecessor to the World Wide Web.
看Building Responsive Data Visualization for the Web时介绍到了Enquire,表示wiki类系统实现了它的核心思想. 有点好奇是如何实现的,所以大概看 ...
- Navicat操作数据库时一直显示加载中
用Navicat for mysql操作mysql数据库,其中一个表怎么也打不开,一直加载,还不能关闭.从网上搜索原因,主要是以下几个原因:原因一:表死锁,会出现这样的情况,锁不释放,无论多久都读取不 ...
- LeetCode之旅
14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower",&quo ...
- PHP 中的魔法常数
PHP中的魔法常数 PHP中有很多描述当前状态的魔法函数,可以很方便地获取运行时的局部环境 测试代码及结果如下 <?php namespace NS { function writeln($v= ...
- js中函数对象创建的总结
在JavaScript的函数对象创建方法中,可以分为三种情况: 1:第一种是使用function语句定义函数 <script type="text/javascript"&g ...
- 【网站管理6】_一个网站SEO优化方案
首先,前端/页编人员主要负责站内优化,主要从四个方面入手: 第一个,站内结构优化 合理规划站点结构(1.扁平化结构 2.辅助导航.面包屑导航.次导航) 内容页结构设置(最新文章.推荐文章.热门文章.增 ...
- 小甲鱼Python第十讲课后题---
0. 下边的列表分片操作会打印什么内容? >>> list1 = [1, 3, 2, 9, 7, 8]>>> list1[2:5] [2,9,7] 1.请问 lis ...