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);
        }
    }
}

随机推荐

  1. c# datetime 格式化

    //c datetime 格式化 DateTime dt = DateTime.Now; Label1.Text = dt.ToString();//2005-11-5 13:21:25 Label2 ...

  2. 学习simple.data之高级篇

    一.调用存储过程 1.不带参数 CREATE PROCEDURE ProcedureWithoutParams AS SELECT * FROM ORDER; 调用db.ProcedureWithou ...

  3. Html5时钟的实现

    最近准备把自己的博客装修一下,首先,先为自己设计一个时钟吧,希望博客园能够尽快发放给我使用js的权限! 自从看见了苹果设计的那款因为侵权而赔钱了时钟,我就决定我的时钟一定是要参考这个来设计了! 不得不 ...

  4. Java数据库连接关闭后无法启动

    错误如下: java.sql.SQLException: No operations allowed after connection closed. at com.mysql.jdbc.Connec ...

  5. RMAN-06023: no backup or copy of datafile 6 found to restore

    一:问题描述 我用指定备份集恢复时,报错: RMAN> run { 2> shutdown immediate; 3> startup mount; 4> allocate c ...

  6. [Java 并发] Java并发编程实践 思维导图 - 第二章 线程安全性

    依据<Java并发编程实践>一书整理的思维导图.

  7. .Net程序员学用Oracle系列(16):访问数据库(ODP.NET)

    1..Net for Oracle 常见数据库驱动 1.1.微软提供的驱动 1.2.甲骨文提供的驱动 1.3.其它厂商提供的驱动 2.ODP.NET 常见问题分析 2.1.参数化问题 2.2.方法调用 ...

  8. Python format 格式化函数

    str.format() 格式化字符串的函数 str.format(),它增强了字符串格式化的功能. 基本语法是通过 {} 和 : 来代替以前的 % format 函数可以接受不限个参数,位置可以不按 ...

  9. Session session = connection.createSession(paramA,paramB);参数解析

    Session session = connection.createSession(paramA,paramB); paramA是设置事务,paramB是设置acknowledgment mode ...

  10. 开始PYTHON之路

    曾经的功献给了球场酒精 曾经的激情也献给了爱情 曾经的智商用来副本求生 曾经的VB6老迈的只剩点0 曾经的SQL2000都不兼容 曾经........ 还有一些理想没有实现 还得继续在这个世界谋生 岁 ...