03-06-架构篇-资源依赖与分组架构

资源下载器架构

篇章 :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 快速入门

相关推荐
鑫鑫哥adam6 小时前
一局游戏的时长,凭什么信客户端说的?
unity3d·游戏开发
Behavior14 小时前
国内下载使用 Unity 6000 踩坑点总结
c#·unity3d·游戏开发
哈欠兽1 天前
Laya 1.x AS3 游戏开发 以玩家死亡为例子 了解如何通过事件调用函数
游戏开发
SmalBox2 天前
03-05-架构篇-操作异步体系(OperationSystem)
unity3d·游戏开发
SmalBox2 天前
03-04-架构篇-文件系统架构(IFileSystem)
unity3d·游戏开发
甲维斯10 天前
Opus5.5大考!做一个赛车游戏"秋名山车神"
人工智能·游戏开发
鑫鑫哥adam13 天前
一个 1x1 贴图,是怎么炸掉整个热更包的?
unity3d·游戏开发
Behavior13 天前
Unity 手游动态更换 App 图标 — Android 与 iOS 双端技术方案
c#·unity3d·游戏开发
SmalBox14 天前
03-03-架构篇-Runtime加载系统架构
unity3d·游戏开发