源码-下载系统(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 的热更新功能,并优化下载性能。
下一篇 :加密解密机制