06-03-源码-下载系统(DownloadSystem)

源码-下载系统(DownloadSystem)

篇章 :06-源码深度-文件系统与下载模块 阅读时间 :约 30 分钟 前置知识:了解 IFileSystem


一、引言

本章将深入解析 YooAsset 下载系统(DownloadSystem)的源码实现。DownloadSystem 是 YooAsset 热更新功能的核心,负责从远程服务器下载资源到本地。深入理解 DownloadSystem 是掌握 YooAsset 热更新机制的关键。

DownloadSystem 采用了 DownloadManager、DownloadTask、BundleDownloader 的分层架构,支持并发下载、断点续传、错误重试等高级特性。本章将从源码层面深入解析 DownloadSystem 的实现。


二、下载系统架构

2.1 分层架构

YooAsset 的下载系统采用三层架构:

markdown 复制代码
下载系统架构
├── DownloadManager(下载管理器)
│   ├── 全局下载管理
│   ├── 并发控制
│   └── 任务调度
├── DownloadTask(下载任务)
│   ├── 单个 Package 的下载任务
│   ├── 子任务管理
│   └── 进度汇总
└── BundleDownloader(Bundle 下载器)
    ├── 单个 Bundle 的下载
    ├── HTTP 请求
    └── 文件写入

DownloadManager 详解:

DownloadManager 是下载管理器,负责全局的下载管理。

DownloadTask 详解:

DownloadTask 是下载任务,封装了一个 Package 的所有 Bundle 下载。

BundleDownloader 详解:

BundleDownloader 是 Bundle 下载器,负责单个 Bundle 的实际下载。

2.2 核心组件

csharp 复制代码
public class DownloadManager
{
    public int DownloadMaxConcurrency { get; set; } = 10;
    public int FailedTryAgain { get; set; } = 3;
    public float Timeout { get; set; } = 60f;
    
    private List<DownloadTask> _allTasks = new List<DownloadTask>();
    private List<DownloadTask> _runningTasks = new List<DownloadTask>();
    
    public DownloadTask CreateDownloadTask(ResourcePackage package, List<BundleInfo> bundleInfos);
    public void StartDownload(DownloadTask task);
    public void StopDownload(DownloadTask task);
}

public class DownloadTask : AsyncOperationBase
{
    public List<BundleDownloader> Downloaders { get; }
    public long TotalSize { get; }
    public long DownloadedSize { get; }
    public float Progress { get; }
    public DownloadStatus Status { get; }
}

public class BundleDownloader
{
    public BundleInfo BundleInfo { get; }
    public long DownloadedBytes { get; }
    public float Progress { get; }
    public BundleDownloadStatus Status { get; }
    
    public IEnumerator Download();
}

DownloadManager 配置详解:

  • DownloadMaxConcurrency:最大下载并发数
  • FailedTryAgain:失败重试次数
  • Timeout:下载超时时间

DownloadTask 状态详解:

  • Status:下载状态
  • Progress:下载进度
  • TotalSize:总大小
  • DownloadedSize:已下载大小

BundleDownloader 状态详解:

  • Status:下载状态
  • Progress:下载进度
  • DownloadedBytes:已下载字节数

三、HTTP 下载实现

3.1 UnityWebRequest 封装

csharp 复制代码
public class BundleDownloader
{
    private UnityWebRequest _request;
    private string _remoteURL;
    private string _localPath;
    
    public IEnumerator Download()
    {
        // 1. 创建下载请求
        _request = UnityWebRequest.Get(_remoteURL);
        _request.timeout = 60;
        
        // 2. 设置下载处理器
        var downloadHandler = new DownloadHandlerFile(_localPath);
        downloadHandler.removeFileOnAbort = true;
        _request.downloadHandler = downloadHandler;
        
        // 3. 发送请求
        yield return _request.SendWebRequest();
        
        // 4. 处理结果
        if (_request.result == UnityWebRequest.Result.Success)
        {
            Status = BundleDownloadStatus.Succeed;
        }
        else
        {
            Status = BundleDownloadStatus.Failed;
            Error = _request.error;
        }
    }
}

UnityWebRequest 详解:

UnityWebRequest 是 Unity 提供的网络请求 API。

DownloadHandlerFile 详解:

DownloadHandlerFile 是文件下载处理器,直接将下载的数据写入文件。

removeFileOnAbort 详解:

removeFileOnAbort 设置下载中断时是否删除已下载的文件。

3.2 下载进度跟踪

csharp 复制代码
public IEnumerator Download()
{
    _request = UnityWebRequest.Get(_remoteURL);
    _request.downloadHandler = new DownloadHandlerFile(_localPath);
    var asyncOp = _request.SendWebRequest();
    
    while (!asyncOp.isDone)
    {
        // 更新进度
        Progress = asyncOp.progress;
        DownloadedBytes = (long)(asyncOp.progress * TotalSize);
        yield return null;
    }
}

进度更新详解:

每帧更新下载进度,asyncOp.progress 范围是 0-1。

DownloadedBytes 计算详解:

已下载字节数 = 进度 × 总大小。

3.3 HTTP 超时处理

csharp 复制代码
public IEnumerator Download()
{
    _request = UnityWebRequest.Get(_remoteURL);
    _request.timeout = 60; // 60 秒超时
    _request.downloadHandler = new DownloadHandlerFile(_localPath);
    
    var startTime = Time.realtimeSinceStartup;
    var asyncOp = _request.SendWebRequest();
    
    while (!asyncOp.isDone)
    {
        if (Time.realtimeSinceStartup - startTime > Timeout)
        {
            _request.Abort();
            Status = BundleDownloadStatus.Timeout;
            yield break;
        }
        yield return null;
    }
}

超时检测详解:

每帧检查是否超时,超时则取消请求。


四、断点续传实现

4.1 断点续传的概念

断点续传可以从上次中断的位置继续下载:

优势:

  • 减少重复下载
  • 节省流量
  • 提高下载成功率

实现原理:

使用 HTTP Range 请求,告诉服务器从哪个字节开始下载。

4.2 HTTP Range 请求实现

csharp 复制代码
public IEnumerator Download()
{
    // 1. 检查本地是否已有部分下载的文件
    long resumePosition = GetResumePosition();
    
    if (resumePosition > 0)
    {
        // 2. 设置 Range 请求头
        _request.SetRequestHeader("Range", $"bytes={resumePosition}-");
    }
    
    // 3. 设置下载处理器(追加模式)
    var downloadHandler = new DownloadHandlerFile(_localPath, resumePosition > 0);
    _request.downloadHandler = downloadHandler;
    
    // 4. 发送请求
    yield return _request.SendWebRequest();
}

Range 请求头详解:

Range 请求头的格式为 bytes=start-end,表示从 start 字节开始下载。

追加模式详解:

DownloadHandlerFile 的第二个参数为 append,true 表示追加模式。

4.3 文件位置记录

csharp 复制代码
private long GetResumePosition()
{
    if (File.Exists(_localPath))
    {
        return new FileInfo(_localPath).Length;
    }
    return 0;
}

文件大小获取详解:

通过 FileInfo 获取已下载的文件大小。

断点位置详解:

断点位置就是已下载的文件大小。

4.4 完整性验证

csharp 复制代码
public IEnumerator Download()
{
    // ... 下载逻辑 ...
    
    // 下载完成后验证文件完整性
    if (Status == BundleDownloadStatus.Succeed)
    {
        if (!VerifyFileIntegrity())
        {
            Status = BundleDownloadStatus.Failed;
            Error = "File integrity check failed";
            // 删除损坏的文件
            if (File.Exists(_localPath))
                File.Delete(_localPath);
        }
    }
}

private bool VerifyFileIntegrity()
{
    string fileHash = CalculateFileHash(_localPath);
    return fileHash == BundleInfo.FileHash;
}

Hash 校验详解:

计算下载文件的 Hash,与 BundleInfo 中的 FileHash 对比。

损坏文件处理详解:

校验失败时删除损坏的文件,下次重新下载。


五、错误重试

5.1 重试策略

csharp 复制代码
public IEnumerator Download()
{
    int retryCount = 0;
    int maxRetries = 3;
    
    while (retryCount < maxRetries)
    {
        bool success = false;
        yield return DoDownload(result => success = result);
        
        if (success) yield break;
        
        retryCount++;
        
        // 等待一段时间后重试
        float waitTime = Mathf.Pow(2, retryCount);
        yield return new WaitForSeconds(waitTime);
    }
    
    Status = BundleDownloadStatus.Failed;
    Error = "Max retries exceeded";
}

重试次数详解:

默认重试 3 次,可通过配置调整。

指数退避详解:

重试前等待时间 = 2^retryCount,避免立即重试。

5.2 错误分类

csharp 复制代码
private bool ShouldRetry(UnityWebRequest request)
{
    switch (request.result)
    {
        case UnityWebRequest.Result.ConnectionError:
            // 网络错误,重试
            return true;
        case UnityWebRequest.Result.ProtocolError:
            // 协议错误,根据状态码决定
            if (request.responseCode >= 500) return true; // 服务器错误
            return false; // 客户端错误,不重试
        case UnityWebRequest.Result.DataProcessingError:
            // 数据处理错误,重试
            return true;
        default:
            return false;
    }
}

网络错误详解:

网络连接失败,应该重试。

服务器错误详解:

5xx 状态码是服务器错误,可以重试。

客户端错误详解:

4xx 状态码是客户端错误,不应该重试。

5.3 重试实现

csharp 复制代码
public IEnumerator Download()
{
    int retryCount = 0;
    int maxRetries = 3;
    
    while (retryCount <= maxRetries)
    {
        yield return DoSingleDownload();
        
        if (Status == BundleDownloadStatus.Succeed)
            yield break;
        
        if (!ShouldRetry(_request))
        {
            yield break;
        }
        
        retryCount++;
        yield return new WaitForSeconds(Mathf.Pow(2, retryCount));
    }
}

重试条件详解:

只有可重试的错误才重试。

重试间隔详解:

使用指数退避策略,避免频繁重试。


六、下载进度计算

6.1 整体进度计算

csharp 复制代码
public class DownloadTask
{
    public float Progress
    {
        get
        {
            if (TotalSize == 0) return 0;
            return (float)DownloadedSize / TotalSize;
        }
    }
    
    public long DownloadedSize
    {
        get
        {
            long total = 0;
            foreach (var downloader in Downloaders)
            {
                total += downloader.DownloadedBytes;
            }
            return total;
        }
    }
}

整体进度详解:

整体进度 = 所有 Bundle 已下载字节数 / 总字节数。

DownloadedSize 详解:

DownloadedSize 是所有 BundleDownloader 的已下载字节数之和。

6.2 单个下载进度

csharp 复制代码
public class BundleDownloader
{
    public float Progress => _totalSize > 0 ? (float)_downloadedBytes / _totalSize : 0;
}

单个下载进度详解:

单个下载进度 = 已下载字节数 / 该 Bundle 总大小。

6.3 下载速度计算

csharp 复制代码
public class DownloadSpeedCalculator
{
    private long _lastBytes;
    private float _lastTime;
    public float Speed { get; private set; }
    
    public void Update(long currentBytes)
    {
        var currentTime = Time.realtimeSinceStartup;
        var timeDelta = currentTime - _lastTime;
        
        if (timeDelta >= 1.0f) // 每秒更新一次
        {
            Speed = (currentBytes - _lastBytes) / timeDelta;
            _lastBytes = currentBytes;
            _lastTime = currentTime;
        }
    }
}

下载速度详解:

下载速度 = (当前字节数 - 上次字节数) / 时间差。

更新频率详解:

每秒更新一次速度,避免抖动。


七、并发控制

7.1 并发控制的重要性

并发控制是 DownloadManager 的重要特性:

  • 避免过多的网络请求
  • 控制带宽使用
  • 防止服务器过载

7.2 并发控制实现

csharp 复制代码
public class DownloadManager
{
    private int _maxConcurrency = 10;
    private Queue<DownloadTask> _waitingTasks = new Queue<DownloadTask>();
    private List<DownloadTask> _runningTasks = new List<DownloadTask>();
    
    public void StartDownload(DownloadTask task)
    {
        if (_runningTasks.Count >= _maxConcurrency)
        {
            _waitingTasks.Enqueue(task);
            return;
        }
        
        _runningTasks.Add(task);
        task.BeginDownload();
    }
    
    public void OnTaskComplete(DownloadTask task)
    {
        _runningTasks.Remove(task);
        
        if (_waitingTasks.Count > 0)
        {
            var nextTask = _waitingTasks.Dequeue();
            StartDownload(nextTask);
        }
    }
}

最大并发数详解:

默认最大并发数为 10,可通过 DownloadMaxConcurrency 调整。

任务队列详解:

超过并发数的任务进入等待队列。

启动等待任务详解:

有任务完成时,启动等待中的任务。

7.3 单任务内部并发

csharp 复制代码
public class DownloadTask
{
    private int _maxConcurrency = 5;
    private Queue<BundleDownloader> _waitingDownloaders = new Queue<BundleDownloader>();
    
    public void Update()
    {
        // 启动等待中的下载
        while (_currentConcurrency < _maxConcurrency && _waitingDownloaders.Count > 0)
        {
            var downloader = _waitingDownloaders.Dequeue();
            StartDownloader(downloader);
        }
    }
}

任务内部并发详解:

单个 DownloadTask 也可以控制内部并发。

Bundle 并发下载详解:

单个任务内的多个 Bundle 可以并发下载。


八、下载操作 API

8.1 下载操作

csharp 复制代码
public class DownloadManager
{
    public DownloadTask BeginDownload(ResourcePackage package, List<BundleInfo> bundleInfos, int failedTryAgain = 0, int timeout = 60);
    public void CancelDownload(DownloadTask task);
    public void PauseDownload(DownloadTask task);
    public void ResumeDownload(DownloadTask task);
}

BeginDownload 详解:

开始下载,返回 DownloadTask。

CancelDownload 详解:

取消下载,停止所有下载。

PauseDownload 详解:

暂停下载,保存下载进度。

ResumeDownload 详解:

恢复下载,从断点继续。

8.2 进度查询

csharp 复制代码
public class DownloadTask
{
    public float Progress { get; }
    public long DownloadedSize { get; }
    public long TotalSize { get; }
    public DownloadStatus Status { get; }
    public string Error { get; }
}

Progress 详解:

Progress 是下载进度,范围 0-1。

Status 详解:

Status 是下载状态,包括 Running、Paused、Succeed、Failed、Canceled。

8.3 事件回调

csharp 复制代码
public class DownloadTask
{
    public event Action<DownloadTask> OnDownloadStart;
    public event Action<DownloadTask> OnDownloadProgress;
    public event Action<DownloadTask> OnDownloadComplete;
    public event Action<DownloadTask> OnDownloadError;
}

事件类型详解:

  • OnDownloadStart:下载开始
  • OnDownloadProgress:下载进度更新
  • OnDownloadComplete:下载完成
  • OnDownloadError:下载错误

九、总结

本章深入解析了 YooAsset 下载系统(DownloadSystem)的源码实现,包括:

  • 下载系统架构:分层架构、核心组件
  • HTTP 下载实现:UnityWebRequest 封装、进度跟踪、超时处理
  • 断点续传实现:HTTP Range 请求、文件位置记录、完整性验证
  • 错误重试:重试策略、错误分类、重试实现
  • 下载进度计算:整体进度、单个进度、下载速度
  • 并发控制:并发控制的重要性、实现、单任务内部并发
  • 下载操作 API:下载操作、进度查询、事件回调

通过深入理解 DownloadSystem 的实现,开发者可以更好地使用 YooAsset 的热更新功能,并优化下载性能。


下一篇 :加密解密机制

相关推荐
SmalBox1 天前
06-02-源码-内置文件系统实现
unity3d·游戏开发
SmalBox2 天前
06-01-源码-文件系统体系(IFileSystem)
unity3d·游戏开发
狗凯之家源码网2 天前
H5 棋牌对战系统源码深度评测与实战分析
php·游戏开发
SmalBox3 天前
05-08-源码-缓存系统与内存管理
unity3d·游戏开发
雪庭4 天前
OCCT OCAF 实战
unity3d
SmalBox4 天前
05-07-源码-引用计数与资源生命周期
unity3d·游戏开发
雪庭4 天前
用 OCCT 造一只啤酒瓶:完整实录
unity3d
SmalBox5 天前
05-06-源码-异步操作体系(OperationSystem)
unity3d·游戏开发
侑虎科技5 天前
深度分析屏幕空间反射算法以及在URP中的实现(一)
unity3d