[游戏开发][Unity]Assetbundle下载篇(9)启动边玩边下流程

目录

打包与资源加载框架目录

正文

下载篇(7)是获取边下边玩的AB包列表,这一步只是获取列表,并没有下载。

本篇文章,也就是下载篇(9)启动边下边玩流程,可以在热更结束后的任意时刻开始下载。毕竟是边玩边下流程,和启动热更流程混到一起就不合适了。

//开启下载
private IEnumerator Download()
{
    // 计算下载文件的总大小
    totalDownloadCount = _patcher.InGameDownloadList.Count;
    if (totalDownloadCount == 0)
    {
        PatchEventDispatcher.SendInGameWebFileDownloadOverMsg();
        _patcher.Switch(EPatchStates.InGameDownloadOver.ToString());
        yield break;
    }
    //先检查一遍temp目录有没有下载完的, 计算这次实际需要下载的个数和size
    IEnumerator innerCoroutineEnumerator = CheckDownloaded(_patcher.InGameDownloadList, false);
    while(innerCoroutineEnumerator.MoveNext())
        yield return innerCoroutineEnumerator.Current;

    // 开始下载列表里的所有资源
    PatchHelper.Log(ELogLevel.Log, $"Begine download web files : {totalDownloadCount-currentDownloadCount}");
    PatchEventDispatcher.SendPatchStatesChangeMsg(EPatchStates.InGameDownloadWebFiles);
    var startTime = Time.realtimeSinceStartup;
    var newDownloaded = new List<PatchElement>(_patcher.InGameDownloadList.Count);
    foreach (var element in _patcher.InGameDownloadList)
    {
        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>");
    //全部下载完成后把这次新下载的文件再校验一遍,如果有文件失败就退出
    IEnumerator coroutineEnumerator = CheckDownloaded(newDownloaded, true);
    while(coroutineEnumerator.MoveNext())
        yield return coroutineEnumerator.Current;
    if(failedOnCheckDownload)
    {
        yield break;
    }
    // 最后清空下载列表
    _patcher.InGameDownloadList.Clear();
    _patcher.SwitchNext();
}

//检查是否有已完成或下载中断的文件。
private IEnumerator CheckDownloaded(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 > 20)
                    {
                        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;
                }
            }
        }
    }
}

单任务下载

IEnumerator DownloadWithSingleTask(List<PatchElement> newDownloaded)
{
    foreach (var element in newDownloaded)
    {
        string url = _patcher.GetWebDownloadURL(element.Name);
        string savePath = AssetPathHelper.MakeDownloadTempPath(element.Name);
        PatchHelper.CreateFileDirectory(savePath);
        // 创建下载器
        using (var download = new WebFileRequest(url, savePath, OnDownloadProgress))
        {
            yield return download.DownLoad();
            //PatchHelper.Log(ELogLevel.Log, $"Web file is download : {savePath}");
            // 检测是否下载失败
            if (download.States != EWebRequestStates.Success)
            {
                PatchEventDispatcher.SendWebFileDownloadFailedMsg(url, element.Name);
                failedOnDownload = true;
                yield break;
            }
        }
        currentDownloadSizeKB += element.SizeKB;
        currentDownloadCount++;
        element.SavePath = savePath;
    }
}

多任务下载

private class DownloadJob
{
    public UnityWebRequest req;
    public PatchElement info;
}

IEnumerator DownloadWithMultiTask(List<PatchElement> downloadList)
{
    var maxJobs = 2;
    var runningJobs = new List<DownloadJob>(maxJobs);
    var lastRecordTime = Time.realtimeSinceStartup;
    int assignedIdx = 0;
    while (assignedIdx < downloadList.Count)
    {
        while (runningJobs.Count < maxJobs && assignedIdx < downloadList.Count)
        {
            var element = downloadList[assignedIdx];
            var url = _patcher.GetWebDownloadURL(element.Name);
            string savePath = AssetPathHelper.MakeDownloadTempPath(element.Name);
            PatchHelper.CreateFileDirectory(savePath);

            var job = UnityWebRequest.Get(url);
            job.downloadHandler = new DownloadHandlerFile(savePath) { removeFileOnAbort = true };
            job.SendWebRequest();
            runningJobs.Add(new DownloadJob()
            {
                req = job,
                info = element
            });
            assignedIdx++;
        }                
        yield return new WaitUntil(() => CheckAnyDownloadFinished(runningJobs, ref lastRecordTime));
        if (failedOnDownload) yield break;
    }
    yield return new WaitUntil(() => CheckAllDownloadFinished(runningJobs, ref lastRecordTime));
    
    //clear up all request jobs            
    foreach(var job in runningJobs)
    {
        LogIfFailed(job);
        job.req.Dispose();
    }
    runningJobs.Clear();
}

//检查是否有任务完成
bool CheckAnyDownloadFinished(List<DownloadJob> jobs, ref float lastRecordTime)
{    
    long downloadedBytes = 0;
    int finished = 0;
    for(var i=jobs.Count-1; i>=0; i--)
    {
        var job = jobs[i];
        if(job.req.isDone)
        {
            finished++;
            LogIfFailed(job);
            jobs.RemoveAt(i);
            downloadedBytes += job.info.SizeKB;
            job.req.Dispose();
        }                
    }
    if (finished>0)
    {
        var deltaTime = Time.realtimeSinceStartup - lastRecordTime;
        currentDownloadCount += finished;
        currentDownloadSizeKB += downloadedBytes;
        OnDownloadProgress(downloadedBytes, downloadedBytes /  deltaTime);
        lastRecordTime += deltaTime;
    }
    return finished > 0;
}
//检查所有任务是否完成
bool CheckAllDownloadFinished(List<DownloadJob> jobs, ref float lastRecordTime)
{
    long downloadedBytes = 0;
    foreach(var job in jobs)
    {
        if (!job.req.isDone) return false;
        downloadedBytes += job.info.SizeKB;
    }
    var deltaTime = Time.realtimeSinceStartup - lastRecordTime;
    if(deltaTime > 0)
    {
        currentDownloadCount += jobs.Count;
        currentDownloadSizeKB += downloadedBytes;
        OnDownloadProgress(downloadedBytes, downloadedBytes / deltaTime);
        lastRecordTime += deltaTime;
    }
    return true;
}

请注意,InGame模式的AB包下载和热更AB包没有区别,甚至这两步的代码几乎一模一样,虽然代码分开写的,封装在一起没有任何问题,下载完成后,记得把临时文件夹的AB包剪切到persistentDataPath文件夹内就好啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Little丶Seven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值