Files
YooAsset/Assets/YooAsset/Runtime/DownloadSystem

DownloadSystem 下载模块

模块概述

DownloadSystem 是 YooAsset 资源管理系统的底层网络下载层,负责处理所有 HTTP 网络请求。该模块提供了统一的下载接口抽象,支持文件下载、断点续传、并发请求(由上层调度)、看门狗监控等功能。

核心职责

  • HTTP/HTTPS 文件下载
  • 断点续传支持
  • 看门狗超时保护
  • 多种下载类型(文件/字节/文本/AssetBundle
  • 可插拔的网络库后端

边界与上层协作

DownloadSystem 的职责是提供“可替换后端 + 统一请求接口 + 轮询式生命周期”的基础能力:

  • 本模块不负责并发队列/限流调度:并发通常由上层同时创建多个 request 并自行控制并发数。
  • 本模块不负责重试/回退策略:失败后的重试、切换 CDN、降级等策略通常由上层系统实现。
  • 本模块不负责持久化下载任务:断点续传依赖本地已有文件与 Range 请求头,并由上层管理断点信息。

设计目标

目标 说明
可扩展性 支持可插拔的网络库后端UnityWebRequest/BestHTTP/自研)
鲁棒性 看门狗超时保护、自动清理失败文件、完整错误信息
高性能 轮询模式无阻塞、支持并发请求(并发数由上层调度)
易用性 流畅的参数构建 API、清晰的状态转换

架构概念

分层架构

┌─────────────────────────────────────────────────────────┐
│                    上层调用者                             │
│              (FileSystem / ResourceManager)             │
└─────────────────────────┬───────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────┐
│                  IDownloadBackend                       │
│                    (后端接口)                            │
│         定义网络库合约,工厂模式创建请求                     │
└─────────────────────────┬───────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────┐
│                  IDownloadRequest                       │
│                    (请求接口)                            │
│           轮询式生命周期管理,状态机驱动                     │
└─────────────────────────┬───────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────┐
│              UnityWebRequest / 其他网络库                 │
│                    (底层实现)                            │
└─────────────────────────────────────────────────────────┘

核心组件

  • 后端层 (IDownloadBackend): 定义网络库实现合约,通过工厂方法创建各类请求
  • 请求层 (IDownloadRequest): 统一的请求生命周期管理,支持轮询驱动
  • 参数层 (Args 结构体): 配置下载行为(超时、断点续传、看门狗等)

文件结构

DownloadSystem/
├── Interface/                                # 接口定义
│   ├── IDownloadBackend.cs                   # 后端接口(工厂模式)
│   └── IDownloadRequest.cs                   # 请求接口层次结构
│
├── DefaultDownloadBackend/                   # 默认后端实现
│   ├── UnityWebRequestBackend.cs             # UnityWebRequest 后端
│   └── UnityWebRequestCreator.cs             # UnityWebRequest 创建委托
│
├── DefaultDownloadRequest/                   # 默认请求实现
│   ├── UnityWebRequestDownloaderBase.cs      # 基础下载器(抽象类)
│   ├── UnityWebRequestFileDownloader.cs      # 文件下载器
│   ├── UnityWebRequestHeadDownloader.cs      # HEAD 请求器
│   ├── UnityWebRequestBytesDownloader.cs     # 字节下载器
│   ├── UnityWebRequestTextDownloader.cs      # 文本下载器
│   ├── UnityWebRequestAssetBundleDownloader.cs # AssetBundle 下载器
│   └── VirtualFileDownloader.cs              # 模拟下载器(编辑器用)
│
├── DownloadSystemDefine.cs                   # 枚举、结构体定义
├── DownloadSystemHelper.cs                   # 工具函数
└── WebRequestCounter.cs                      # 请求失败计数器

接口说明

IDownloadBackend后端接口

定义网络库实现的合约,通过工厂方法创建各类下载请求。

public interface IDownloadBackend
{
    /// <summary>
    /// 后端标识名称(用于日志/调试)
    /// </summary>
    string Name { get; }

    /// <summary>
    /// 定期驱动更新(部分第三方库需要)
    /// </summary>
    void Update();

    // 工厂方法 - 创建各类请求
    IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args);
    IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args);
    IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args);
    IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args);
    IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args);
    IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args);
}

IDownloadRequest基础请求接口

所有下载请求的通用接口,定义生命周期和状态管理。

public interface IDownloadRequest : IDisposable
{
    // 元信息
    string URL { get; }

    // 生命周期
    bool IsDone { get; }                      // 每次访问自动轮询
    EDownloadRequestStatus Status { get; }

    // 进度跟踪
    float DownloadProgress { get; }           // 0f - 1f
    long DownloadedBytes { get; }             // 本次请求新增字节数

    // 诊断信息
    long HttpCode { get; }
    string Error { get; }

    // 生命周期方法
    void SendRequest();                       // 发起请求
    void PollingRequest();                    // 轮询状态
    void AbortRequest();                      // 中止请求
}

专化请求接口

接口 用途 特有属性
IDownloadHeadRequest HEAD 请求,获取响应头 ETag, LastModified, ContentLength, ContentType
IDownloadFileRequest 文件下载到本地 SavePath
IDownloadBytesRequest 下载到内存(字节数组) byte[] Result
IDownloadTextRequest 下载文本内容 string Result
IDownloadAssetBundleRequest 下载并加载 AssetBundle AssetBundle Result

结构体定义

请求状态枚举

public enum EDownloadRequestStatus
{
    None,       // 未开始
    Running,    // 进行中
    Succeed,    // 已成功
    Failed,     // 已失败
    Aborted     // 已中止(用户中止或看门狗超时)
}

请求参数结构体

DownloadFileRequestArgs文件下载参数

public struct DownloadFileRequestArgs
{
    public string URL;                    // 请求地址
    public int Timeout;                   // 响应超时0=无限制
    public int WatchdogTime;              // 看门狗超时(秒)

    public string SavePath;               // 文件保存路径
    public bool AppendToFile;             // 追加写入(断点续传)
    public bool RemoveFileOnAbort;        // 中止时删除文件
    public long ResumeFromBytes;          // 断点续传起始位置

    public Dictionary<string, string> Headers;  // 自定义请求头
}

DownloadDataRequestArgs数据下载参数

public struct DownloadDataRequestArgs
{
    public string URL;                    // 请求地址
    public int Timeout;                   // 响应超时(秒)
    public int WatchdogTime;              // 看门狗超时(秒)
    public Dictionary<string, string> Headers;  // 自定义请求头
}

DownloadAssetBundleRequestArgsAssetBundle 下载参数)

public struct DownloadAssetBundleRequestArgs
{
    public string URL;                    // 请求地址
    public int Timeout;                   // 响应超时
    public int WatchdogTime;              // 看门狗超时

    public bool DisableUnityWebCache;     // 禁用 Unity 缓存(推荐 true
    public string FileHash;               // 文件哈希(缓存启用时需要)
    public uint UnityCRC;                 // Unity CRC 校验值

    public Dictionary<string, string> Headers;
}

DownloadSimulateRequestArgs模拟下载参数

public struct DownloadSimulateRequestArgs
{
    public string URL;            // 标识符
    public long FileSize;         // 模拟文件大小
    public long DownloadSpeed;    // 模拟速度(字节/秒),默认 1MB/s
}

核心类说明

UnityWebRequestBackend

默认的后端实现,基于 Unity 的 UnityWebRequest API。

特性:

  • 支持自定义 UnityWebRequest 创建方式(证书验证、代理等)
  • 无需手动调用 Update()UnityWebRequest 自动驱动
// 自定义 UnityWebRequest 创建(建议通过 backend 构造函数传入)
UnityWebRequestCreator creator = (url, method) =>
{
    var request = new UnityWebRequest(url, method);
    // 自定义配置...
    return request;
};

IDownloadBackend backend = new UnityWebRequestBackend(creator);

UnityWebRequestDownloaderBase

抽象基类,封装所有下载器的通用逻辑。

职责:

  • 管理请求生命周期和状态转换
  • 实现看门狗监控机制
  • 追踪下载进度和字节数
  • 处理超时和错误

生命周期:

None ──► SendRequest() ──► Running ──► PollingRequest() ──┬──► Succeed
                                                          ├──► Failed
                                                          └──► Aborted

具体下载器

下载器 实现接口 使用场景
UnityWebRequestFileDownloader IDownloadFileRequest 大文件下载到本地
UnityWebRequestHeadDownloader IDownloadHeadRequest 检查资源信息
UnityWebRequestBytesDownloader IDownloadBytesRequest 小文件内存加载
UnityWebRequestTextDownloader IDownloadTextRequest 文本文件下载
UnityWebRequestAssetBundleDownloader IDownloadAssetBundleRequest AB 包下载加载
VirtualFileDownloader IDownloadFileRequest 编辑器模拟下载

使用示例

基础文件下载

// 1. 创建后端和请求
IDownloadBackend backend = new UnityWebRequestBackend();
var args = new DownloadFileRequestArgs(
    url: "https://example.com/file.zip",
    savePath: "/path/to/save/file.zip",
    timeout: 30,
    watchdogTime: 0);
IDownloadFileRequest request = backend.CreateFileRequest(args);

// 2. 发起并轮询
request.SendRequest();
while (!request.IsDone)
{
    await Task.Yield();
    // 可选:显示进度
    float progress = request.DownloadProgress;
}

// 3. 检查结果
if (request.Status == EDownloadRequestStatus.Succeed)
{
    Debug.Log("下载成功");
}
else
{
    Debug.LogError($"下载失败: {request.Error}");
}

// 4. 清理资源
request.Dispose();

断点续传

// 获取已下载的文件大小
long existingFileSize = new FileInfo(savePath).Length;

var args = new DownloadFileRequestArgs(
    url: url,
    savePath: savePath,
    timeout: 30,
    watchdogTime: 0,
    appendToFile: true,                 // 追加写入
    removeFileOnAbort: false,           // 中止时保留文件
    resumeFromBytes: existingFileSize); // 断点位置

IDownloadFileRequest request = backend.CreateFileRequest(args);
request.SendRequest();
// ... 轮询等待完成

看门狗保护

var args = new DownloadFileRequestArgs(
    url: url,
    savePath: path,
    timeout: 30,
    watchdogTime: 30); // 30秒无数据自动中止

IDownloadFileRequest request = backend.CreateFileRequest(args);
request.SendRequest();

while (!request.IsDone)
{
    await Task.Yield();
}

// 检查是否因看门狗超时而中止
if (request.Status == EDownloadRequestStatus.Aborted)
{
    Debug.LogWarning("下载超时,已自动中止");
}

HEAD 请求获取文件信息

var args = new DownloadDataRequestArgs(
    url: "https://example.com/file.zip",
    timeout: 30,
    watchdogTime: 0);

IDownloadHeadRequest request = backend.CreateHeadRequest(args);
request.SendRequest();

while (!request.IsDone)
{
    await Task.Yield();
}

if (request.Status == EDownloadRequestStatus.Succeed)
{
    long fileSize = request.ContentLength;
    string etag = request.ETag;
    string lastModified = request.LastModified;

    Debug.Log($"文件大小: {fileSize}, ETag: {etag}");
}

下载字节数据

var args = new DownloadDataRequestArgs(
    url: "https://example.com/data.json",
    timeout: 30,
    watchdogTime: 0);

IDownloadBytesRequest request = backend.CreateBytesRequest(args);
request.SendRequest();

while (!request.IsDone)
{
    await Task.Yield();
}

if (request.Status == EDownloadRequestStatus.Succeed)
{
    byte[] data = request.Result;
    // 处理数据...
}

设计模式

工厂模式

IDownloadBackend 作为工厂接口,创建各类下载请求对象:

IDownloadBackend
    ├── CreateHeadRequest()    ──► IDownloadHeadRequest
    ├── CreateFileRequest()    ──► IDownloadFileRequest
    ├── CreateBytesRequest()   ──► IDownloadBytesRequest
    ├── CreateTextRequest()    ──► IDownloadTextRequest
    ├── CreateAssetBundleRequest() ──► IDownloadAssetBundleRequest
    └── CreateSimulateRequest() ──► IDownloadFileRequest

策略模式

通过实现 IDownloadBackend 接口,可以替换底层网络库:

IDownloadBackend (接口)
    ├── UnityWebRequestBackend  (默认实现)
    ├── BestHTTPBackend         (可扩展)
    └── CustomBackend           (自定义)

状态机模式

请求生命周期通过状态机管理:

┌──────┐    SendRequest()    ┌─────────┐
│ None │ ──────────────────► │ Running │
└──────┘                     └────┬────┘
                                  │ PollingRequest()
                    ┌─────────────┼─────────────┐
                    ▼             ▼             ▼
              ┌─────────┐  ┌──────────┐  ┌─────────┐
              │ Succeed │  │  Failed  │  │ Aborted │
              └─────────┘  └──────────┘  └─────────┘

看门狗模式

监控数据接收,防止网络卡顿导致请求无限等待:

每帧轮询 PollingRequest()
    │
    ├── 收到新数据 ──► 重置计时器
    │
    └── 未收到数据 ──► 计时器累加
                           │
                           └── 超过 WatchdogTime ──► AbortRequest()

类继承关系

IDownloadRequest (基础接口)
    │
    ├── IDownloadHeadRequest        (HEAD 请求)
    ├── IDownloadFileRequest        (文件下载)
    ├── IDownloadBytesRequest       (字节下载)
    ├── IDownloadTextRequest        (文本下载)
    └── IDownloadAssetBundleRequest (AssetBundle 下载)

UnityWebRequestDownloaderBase (抽象基类)
    │
    ├── UnityWebRequestFileDownloader      ──► IDownloadFileRequest
    ├── UnityWebRequestHeadDownloader      ──► IDownloadHeadRequest
    ├── UnityWebRequestBytesDownloader     ──► IDownloadBytesRequest
    ├── UnityWebRequestTextDownloader      ──► IDownloadTextRequest
    └── UnityWebRequestAssetBundleDownloader ──► IDownloadAssetBundleRequest

VirtualFileDownloader (独立实现) ──► IDownloadFileRequest

工具类

DownloadSystemHelper

提供跨平台的工具函数:

方法 说明
ConvertToWWWPath() 转换本地路径为 WWW 协议 URL
IsRequestLocalFile() 判断是否本地文件请求

WebRequestCounter

请求失败计数器,用于诊断统计:

  • 线程安全:内部使用 Dictionary 且未加锁,约定只在主线程调用;如需多线程统计请在外层加锁或改造实现
  • Key 规则:$"{packageName}_{eventName}"
  • 统计口径:仅统计网络请求失败IDownloadRequest.Status != Succeed 时记录),不统计内容为空、校验失败、解析失败等业务层失败
// 记录失败
WebRequestCounter.RecordRequestFailed(packageName, eventName);

// 查询失败次数
int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);

注意事项

  1. 资源释放:使用完毕后务必调用 Dispose() 释放资源
    • AbortRequest() 仅用于中止请求与切换状态,不等同于释放资源;无论成功/失败/中止都需要 Dispose()
    • 推荐使用 try/finally 确保释放(尤其是上层可能提前中止的场景)
  2. 断点续传:需要服务器支持 Range 请求头和 206 Partial Content 响应
    • 若服务端不支持 Range 仍返回 200全量内容可能会被追加写入导致文件损坏
  3. 看门狗超时:设置为 0 表示禁用,建议根据网络环境设置合理值
  4. 内存下载IDownloadBytesRequest 会将整个响应体加载到内存,不适合大文件
  5. 驱动更新:部分第三方网络库实现的 backend 可能需要每帧调用 IDownloadBackend.Update() 进行驱动
  6. 中止语义Aborted 可能来自用户主动 AbortRequest() 或看门狗超时;中止场景下 HttpCode/Error 可能为默认值(例如 0/空)
  7. 线程安全:所有下载请求的创建和轮询应在主线程进行