资源下载器架构
篇章 :03-架构篇 阅读时间 :约 35 分钟 前置知识:了解文件系统架构和异步体系
一、引言
本章将深入解析 YooAsset 的资源下载器架构。资源下载器是 YooAsset 热更新功能的核心组件,负责从远程服务器下载资源到本地。
YooAsset 的资源下载器采用了 DownloadManager → DownloadTask → BundleDownloader 的分层架构设计,支持多任务、并发控制、错误重试、进度跟踪等高级特性。本章将详细解析资源下载器的设计和实现。
二、资源下载器架构总览
2.1 分层架构
YooAsset 的资源下载器采用三层架构:
markdown
资源下载器三层架构
├── DownloadManager(下载管理器)
│ ├── 全局下载管理
│ ├── 并发控制
│ └── 任务调度
├── DownloadTask(下载任务)
│ ├── 单个 Package 的下载任务
│ ├── 子任务管理
│ └── 进度汇总
└── BundleDownloader(Bundle 下载器)
├── 单个 Bundle 的下载
├── HTTP 请求
└── 文件写入
DownloadManager 详解:
DownloadManager 是下载管理器,负责全局的下载管理。它管理所有 Package 的下载任务,控制并发数,提供统一的下载入口。
DownloadTask 详解:
DownloadTask 是下载任务,封装了一个 Package 的所有 Bundle 下载。它管理子任务的创建、执行和进度汇总。
BundleDownloader 详解:
BundleDownloader 是 Bundle 下载器,负责单个 Bundle 的实际下载。它处理 HTTP 请求、文件写入、错误重试等。
2.2 核心组件
YooAsset 资源下载器的核心组件包括:
DownloadManager:
csharp
public class DownloadManager
{
public int DownloadMaxConcurrency { get; set; } = 10;
public int FailedTryAgain { get; set; } = 3;
public float Timeout { get; set; } = 60f;
public DownloadTask CreateDownloadTask(ResourcePackage package, List<BundleInfo> bundleInfos);
public void StartDownload(DownloadTask task);
public void StopDownload(DownloadTask task);
}
DownloadTask:
csharp
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 void BeginDownload();
public void StopDownload();
}
BundleDownloader:
csharp
public class BundleDownloader
{
public BundleInfo BundleInfo { get; }
public long DownloadedBytes { get; }
public float Progress { get; }
public BundleDownloadStatus Status { get; }
public IEnumerator Download();
}
三、DownloadManager 详解
3.1 DownloadManager 的职责
DownloadManager 负责以下职责:
全局下载管理:
DownloadManager 是所有下载任务的统一入口。开发者通过 DownloadManager 创建下载任务,启动下载,停止下载。
并发控制:
DownloadManager 控制同时进行的下载任务数量。默认最大并发数为 10,可以通过 DownloadMaxConcurrency 修改。
任务调度:
DownloadManager 负责任务的调度,决定哪些任务可以开始执行,哪些任务需要等待。
全局进度跟踪:
DownloadManager 跟踪所有下载任务的全局进度,提供统一的进度查询接口。
3.2 DownloadManager 的实现
DownloadManager 的核心实现:
csharp
public class DownloadManager
{
private List<DownloadTask> _allTasks = new List<DownloadTask>();
private List<DownloadTask> _runningTasks = new List<DownloadTask>();
private int _maxConcurrency = 10;
public DownloadTask CreateDownloadTask(ResourcePackage package, List<BundleInfo> bundleInfos)
{
DownloadTask task = new DownloadTask(package, bundleInfos);
_allTasks.Add(task);
return task;
}
public void StartDownload(DownloadTask task)
{
if (_runningTasks.Count >= _maxConcurrency)
{
// 并发数已满,等待
return;
}
_runningTasks.Add(task);
task.BeginDownload();
}
public void Update()
{
// 检查已完成的任务
for (int i = _runningTasks.Count - 1; i >= 0; i--)
{
if (_runningTasks[i].IsDone)
{
_runningTasks.RemoveAt(i);
// 启动等待中的任务
StartNextWaitingTask();
}
}
}
}
3.3 DownloadManager 的配置
DownloadManager 提供了多种配置项:
| 配置项 | 描述 | 默认值 |
|---|---|---|
| DownloadMaxConcurrency | 最大下载并发数 | 10 |
| FailedTryAgain | 失败重试次数 | 3 |
| Timeout | 下载超时时间(秒) | 60 |
并发控制详解:
并发控制是 DownloadManager 的重要特性。通过限制同时下载的任务数,可以避免过多的网络请求影响游戏性能。
超时配置详解:
超时配置用于控制单个 Bundle 的下载超时时间。如果一个 Bundle 的下载超过超时时间,会被认为是下载失败。
重试配置详解:
重试配置用于控制下载失败时的重试次数。如果一个 Bundle 的下载失败,会自动重试指定次数。
四、DownloadTask 详解
4.1 DownloadTask 的职责
DownloadTask 负责单个 Package 的资源下载,包括:
子任务管理:
DownloadTask 管理所有 BundleDownloader 子任务。它创建、调度、监控子任务。
进度汇总:
DownloadTask 汇总所有子任务的进度,计算整体的下载进度。
错误处理:
DownloadTask 处理子任务的错误,决定是否重试,是否继续。
状态管理:
DownloadTask 管理自身的状态,包括下载中、暂停、完成、失败等。
4.2 DownloadTask 的工作流程
DownloadTask 的工作流程如下:
markdown
DownloadTask 工作流程
1. 创建 DownloadTask
├── 设置 Package 信息
├── 设置 BundleInfo 列表
└── 创建 BundleDownloader 列表
2. 开始下载
├── 设置状态为 Running
└── 启动并发下载
3. 下载过程中
├── 监控每个 BundleDownloader 的进度
├── 处理下载错误(重试)
└── 更新整体进度
4. 下载完成
├── 验证所有 Bundle 是否下载完成
├── 设置状态为 Succeed 或 Failed
└── 触发完成回调
4.3 DownloadTask 的并发控制
DownloadTask 内部实现了并发控制:
csharp
public class DownloadTask
{
private int _currentConcurrency = 0;
private int _maxConcurrency = 5;
private Queue<BundleDownloader> _waitingDownloaders = new Queue<BundleDownloader>();
public void Update()
{
// 启动等待中的下载
while (_currentConcurrency < _maxConcurrency && _waitingDownloaders.Count > 0)
{
BundleDownloader downloader = _waitingDownloaders.Dequeue();
StartDownloader(downloader);
}
// 更新正在下载的下载器
// 检查已完成的下载器
}
}
4.4 DownloadTask 的进度计算
DownloadTask 的进度计算:
csharp
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;
}
}
五、BundleDownloader 详解
5.1 BundleDownloader 的职责
BundleDownloader 负责单个 Bundle 的实际下载,包括:
HTTP 请求:
BundleDownloader 使用 UnityWebRequest 发送 HTTP 请求,从远程服务器下载 Bundle 文件。
文件写入:
BundleDownloader 将下载的数据写入到本地缓存文件系统。
进度跟踪:
BundleDownloader 跟踪下载进度,提供准确的进度信息。
错误重试:
BundleDownloader 处理下载错误,支持自动重试。
断点续传:
BundleDownloader 支持断点续传,可以从上次中断的位置继续下载。
5.2 BundleDownloader 的实现
BundleDownloader 的核心实现:
csharp
public class BundleDownloader
{
private UnityWebRequest _request;
private string _localPath;
private FileStream _fileStream;
public IEnumerator Download()
{
// 创建 HTTP 请求
_request = UnityWebRequest.Get(_remoteURL);
_request.downloadHandler = new DownloadHandlerFile(_localPath);
_request.SendWebRequest();
// 等待下载完成
while (!_request.isDone)
{
UpdateProgress();
yield return null;
}
// 检查下载结果
if (_request.result != UnityWebRequest.Result.Success)
{
// 下载失败
yield break;
}
}
}
5.3 断点续传
断点续传是 BundleDownloader 的重要特性:
断点续传的原理:
断点续传通过 HTTP Range 请求实现。客户端告诉服务器从哪个字节开始下载,服务器返回指定范围的字节。
断点续传的实现:
csharp
public IEnumerator Download()
{
long resumePosition = GetResumePosition();
if (resumePosition > 0)
{
// 断点续传
_request.SetRequestHeader("Range", $"bytes={resumePosition}-");
}
// 打开本地文件,从断点位置追加
_fileStream = new FileStream(_localPath, FileMode.Append, FileAccess.Write);
// 处理下载数据
// ...
}
断点续传的优势:
- 减少重复下载,节省流量
- 提高下载成功率
- 改善用户体验
5.4 错误重试
BundleDownloader 支持自动重试:
重试策略:
- 网络错误:自动重试
- 服务器错误(5xx):自动重试
- 客户端错误(4xx):不重试
- 超时错误:自动重试
重试实现:
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++;
// 等待一段时间后重试
yield return new WaitForSeconds(Mathf.Pow(2, retryCount));
}
}
六、断点续传详解
6.1 断点续传的工作原理
断点续传的工作原理如下:
markdown
断点续传工作原理
1. 检查本地是否已有部分下载的文件
2. 获取已下载的文件大小
3. 发送 HTTP Range 请求,从已下载的位置继续下载
4. 服务器返回指定范围的数据
5. 客户端将数据追加到本地文件
6. 完成后验证文件完整性
6.2 HTTP Range 请求
HTTP Range 请求是断点续传的基础:
Range 请求格式:
sql
Range: bytes=start-end
Range 请求示例:
csharp
// 从 1000 字节开始下载
_request.SetRequestHeader("Range", "bytes=1000-");
// 下载 1000-1999 字节
_request.SetRequestHeader("Range", "bytes=1000-1999");
// 从 1000 字节下载到文件末尾
_request.SetRequestHeader("Range", "bytes=1000-");
Range 请求的响应:
服务器返回 206 Partial Content 状态码,表示部分内容响应。
6.3 断点续传的实现要点
文件位置记录:
需要记录已下载的文件大小,通常通过文件系统的文件大小获取。
文件追加写入:
下载的数据需要追加到本地文件,而不是覆盖。
完整性验证:
下载完成后需要验证文件完整性(通过 CRC、MD5、SHA1 等)。
异常处理:
断点续传过程中可能出现各种异常,需要妥善处理。
七、下载速度优化
7.1 并发下载
通过并发下载可以显著提高下载速度:
并发下载配置:
csharp
// 设置下载并发数
DownloadManager.Instance.DownloadMaxConcurrency = 20;
并发下载的注意事项:
- 并发数过高可能导致网络拥塞
- 并发数过低可能导致下载速度慢
- 需要根据网络环境和服务器性能调整
7.2 CDN 加速
使用 CDN(Content Delivery Network)可以显著提高下载速度:
CDN 的优势:
- 就近访问:用户从最近的 CDN 节点下载
- 负载均衡:CDN 自动分配请求到不同节点
- 高可用性:CDN 提供高可用性保障
CDN 的实现:
csharp
// 配置 CDN 地址
RemoteServices.RemoteURL = "https://cdn.example.com/game";
7.3 HTTP/2 和 HTTP/3
使用 HTTP/2 或 HTTP/3 可以提高下载效率:
HTTP/2 的优势:
- 多路复用:单个连接可以处理多个请求
- 头部压缩:减少请求头大小
- 服务器推送:服务器可以主动推送资源
HTTP/3 的优势:
- 基于 QUIC 协议:减少连接建立时间
- 更快的重连:网络切换时快速重连
- 更好的拥塞控制
7.4 预下载
通过预下载可以在游戏空闲时下载资源:
预下载的实现:
csharp
// 在游戏启动时预下载常用资源
var preloadBundles = GetPreloadBundles();
var task = DownloadManager.Instance.CreateDownloadTask(package, preloadBundles);
DownloadManager.Instance.StartDownload(task);
预下载的最佳实践:
- 在游戏启动画面时预下载
- 在游戏关卡切换时预下载下一关资源
- 在网络空闲时预下载未来资源
八、下载错误处理
8.1 常见下载错误
下载过程中可能遇到各种错误:
| 错误类型 | 描述 | 处理策略 |
|---|---|---|
| 网络错误 | 网络连接失败 | 自动重试 |
| 超时错误 | 下载超时 | 自动重试 |
| 服务器错误 | 5xx 状态码 | 自动重试 |
| 客户端错误 | 4xx 状态码 | 不重试 |
| 文件写入错误 | 磁盘空间不足 | 提示用户 |
8.2 错误处理实现
csharp
public class BundleDownloader
{
public string Error { get; private set; }
public int RetryCount { get; private set; }
private void HandleError(UnityWebRequest request)
{
if (request.result == UnityWebRequest.Result.ConnectionError)
{
// 网络错误
Error = $"网络错误: {request.error}";
}
else if (request.result == UnityWebRequest.Result.ProtocolError)
{
// 协议错误
Error = $"协议错误: {request.responseCode}";
}
else if (request.result == UnityWebRequest.Result.DataProcessingError)
{
// 数据处理错误
Error = $"数据处理错误: {request.error}";
}
}
}
8.3 用户友好的错误处理
csharp
public void OnDownloadFailed(string error)
{
switch (error)
{
case "网络连接失败":
ShowMessage("网络连接失败,请检查网络设置");
break;
case "磁盘空间不足":
ShowMessage("磁盘空间不足,请清理后重试");
break;
case "服务器错误":
ShowMessage("服务器异常,请稍后重试");
break;
}
}
九、下载进度展示
9.1 进度展示的实现
下载进度的展示通常需要以下信息:
- 总下载大小
- 已下载大小
- 下载进度百分比
- 下载速度
- 剩余时间
csharp
public class DownloadProgress
{
public long TotalSize { get; }
public long DownloadedSize { get; }
public float Progress => (float)DownloadedSize / TotalSize;
public float DownloadSpeed { get; }
public TimeSpan RemainingTime { get; }
}
9.2 进度展示的 UI 实现
csharp
public class DownloadUI : MonoBehaviour
{
public Slider ProgressSlider;
public Text ProgressText;
public Text SpeedText;
public Text TimeText;
public void UpdateUI(DownloadProgress progress)
{
ProgressSlider.value = progress.Progress;
ProgressText.text = $"{progress.Progress * 100:F1}%";
SpeedText.text = $"{progress.DownloadSpeed / 1024:F1} KB/s";
TimeText.text = $"剩余时间: {progress.RemainingTime:mm\:ss}";
}
}
十、总结
本章深入解析了 YooAsset 资源下载器架构,包括:
- 分层架构:DownloadManager → DownloadTask → BundleDownloader
- DownloadManager:全局下载管理
- DownloadTask:单个 Package 的下载任务
- BundleDownloader:单个 Bundle 的下载
- 断点续传:HTTP Range 请求实现
- 下载速度优化:并发下载、CDN 加速、预下载
- 错误处理:常见错误和重试策略
- 进度展示:下载进度的 UI 展示
资源下载器是 YooAsset 热更新功能的核心组件,灵活运用这些特性可以实现高效、可靠的热更新。
上一篇 :通用异步体系(OperationSystem) 下一篇 :YooAsset 快速入门