您现在的位置是:首页 >其他 >[游戏开发][Unity]Assetbundle加载篇(4)检查断点续传以及开始下载AB包网站首页其他

[游戏开发][Unity]Assetbundle加载篇(4)检查断点续传以及开始下载AB包

Little丶Seven 2024-09-14 12:01:05
简介[游戏开发][Unity]Assetbundle加载篇(4)检查断点续传以及开始下载AB包

下载AB包之前,要检查该AB包是否下载中断过,例如用户杀程序,卡死等情况。

前文有讲解过,下载AB包会先下载到临时文件夹,全部下载成功后,全部剪切到persistentDataPath沙盒目录中。

回顾一下之前的筛选机制,哪些AB包可以加入下载列表,其中并没有临时下载目录的判断。

该AB包数据是否加入列表要经过下面几个筛选

  1. 判断persistentDataPath沙盒目录是否存在该AB包,如果存在证明之前下载过,无需加入下载列表。

  1. 判断StreamingAsset目录中是否存在该AB包,如果存在证明打包时该AB包已经在包体里,无需加入下载列表。

  1. 如果该AB包的下载类型是游戏内下载,或者叫边玩边下,那么不需要在游戏启动热更时下载,无需加入下载列表

检查是否在临时下载目录中是否存在的代码如下

private IEnumerator CheckTempFileComplete(List<PatchElement> list, bool isPostCheck)
{
    PatchEventDispatcher.SendCheckDownloadedFileMd5();
    var sw = new Stopwatch();
    sw.Start();
    var buf = new byte[10240];
    this.totalDownloadSizeKB = 0;
    this.currentDownloadSizeKB = 0;
    this.currentDownloadCount = 0;
    using (var md5 = System.Security.Cryptography.MD5.Create())
    {
        foreach (var ele in list)
        {
            this.totalDownloadSizeKB += ele.SizeKB;
            string savePath = AssetPathHelper.MakeDownloadTempPath(ele.Name);
            if (!File.Exists(savePath))
            {
                //下载后的检查需要抛出异常,下载前的检查跳过不存在文件
                if (isPostCheck)
                {
                    PatchHelper.Log(ELogLevel.Error, $"[checking md5] file is not existed: {ele.Name}");
                    failedOnCheckDownload = true;
                    yield break;
                }
                else
                {
                    continue;
                }
            }
            
            using (var fs = new FileStream(savePath, FileMode.Open, FileAccess.Read))
            {
                int byteRead;
                md5.Initialize();
                while ((byteRead = fs.Read(buf, 0, buf.Length)) > 0)
                {
                    md5.TransformBlock(buf, 0, byteRead, null, 0);
                    if (sw.ElapsedMilliseconds > 250)
                    {
                        yield return null;
                        sw.Restart();
                    }
                }
                md5.TransformFinalBlock(buf, 0, 0);
                fs.Close();
                string localMd5 = BitConverter.ToString(md5.Hash).Replace("-", "");
                if (string.Equals(ele.MD5, localMd5, StringComparison.OrdinalIgnoreCase))
                {
                    //MotionLog.Log(ELogLevel.Log, StringFormat.Format("skip download existed file: {0}", savePath));
                    ele.SkipDownload = true;
                    this.currentDownloadSizeKB += ele.SizeKB;
                    this.currentDownloadCount++;
                }
                else if (isPostCheck)
                {
                    PatchHelper.Log(ELogLevel.Error, $"Web file md5 verification error : {ele.Name}");
                    PatchHelper.Log(ELogLevel.Error, $"local md5 is : {localMd5}");
                    PatchHelper.Log(ELogLevel.Error, $"md5 in manifest is : {ele.MD5}");
                    PatchEventDispatcher.SendWebFileMD5VerifyFailedMsg(ele.Name);
                    failedOnCheckDownload = true;
                    File.Delete(savePath);
                    yield break;
                }
            }
        }
    }
}

布尔值isPostCheck的作用是是否是二次检测,先不管它

按流程来走

  1. 检查该AB包是否在临时文件夹中存在,如果不存在则跳过

  1. 如果已存在,创建该临时下载文件的MD5,与下载清单中的MD5做对比

  1. 如果MD5对比一致,则标记该AB包数据为SkipDownload,同时标记下载数据长度,供UI显示

我们项目里目前的代码被人改了,我也有有点看不懂为何要用 md5.TransformBlock的方式

下面是一套生成MD5码的代码,加载文件,MD5CryptoServiceProvider会根据加载的stream生成hash,

readonly MD5CryptoServiceProvider _provider = new MD5CryptoServiceProvider();
public static string StreamMD5()
{
    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        byte[] hashBytes = _provider.ComputeHash(stream);
        return ToString(hashBytes);
    }
}
private static string ToString(byte[] hashBytes)
{
    var sb = new StringBuilder();
    foreach (var t in hashBytes)
    sb.Append(t.ToString("x2"));
    return sb.ToString();
}

首先要判断临时下载目录中是否存在该文件,如果没有,我们项目

private IEnumerator Download()
{
    // 计算下载文件的总大小
    totalDownloadCount = _patcher.DownloadList.Count;
    if (totalDownloadCount == 0)
    {
        _patcher.SwitchNext();
        yield break;
    }
    //先检查一遍temp目录有没有下载完的, 计算这次实际需要下载的个数和size
    yield return CheckTempFileComplete(_patcher.DownloadList, false);

    // 开始下载列表里的所有资源
    PatchHelper.Log(ELogLevel.Log, $"Begine download web files : {totalDownloadCount-currentDownloadCount}");
    PatchEventDispatcher.SendPatchStatesChangeMsg(EPatchStates.DownloadWebFiles);
    var startTime = Time.realtimeSinceStartup;
    var newDownloaded = new List<PatchElement>(_patcher.DownloadList.Count);
    foreach (var element in _patcher.DownloadList)
    {
        if (element.SkipDownload) continue;
        newDownloaded.Add(element);
    }

    if (useMultiRequest)
    {
        yield return DownloadWithMultiTask(newDownloaded);
    }
    else
    {
        yield return DownloadWithSingleTask(newDownloaded);
    }
    if (failedOnDownload)
    {
        yield break;
    }
    MotionLog.Log(ELogLevel.Log, $"<color=#ff0000>Downloading {newDownloaded.Count} files cost {Time.realtimeSinceStartup-startTime} sec.</color>");
    //全部下载完成后把这次新下载的文件再校验一遍,如果有文件失败就退出
    yield return CheckTempFileComplete(newDownloaded, true);
    if (failedOnCheckDownload)
    {
        yield break;
    }
    
    if (_patcher.MiniAndroid)
    {
        var fileCount = _patcher.DownloadList[0].Version; // tricky storing filecount in field [version]
        var zipFileName = _patcher.DownloadList[0].Name;
        yield return DecompressInitPack(zipFileName, fileCount);
    }
    else
    {
        yield return DeployDownloadFiles();
    }

    // 最后清空下载列表
    _patcher.DownloadList.Clear();
    _patcher.SwitchNext();
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。