YooAsset源码阅读-Download

继续 YooAsset 的下载系统代码研究,本文将详细介绍如何创建下载器相关代码

CreateResourceDownloaderByAll

关键类

  • PlayModeImpl.cs
  • ResourceDownloaderOperation.cs
  • DownloaderOperation.cs
  • BundleInfo.cs

CreateResourceDownloaderByAll 方法用于创建下载所有需要更新资源的下载器。

关键源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// PlayModeImpl.cs Line 110-115
ResourceDownloaderOperation IPlayMode.CreateResourceDownloaderByAll(int downloadingMaxNumber, int failedTryAgain)
{
List<BundleInfo> downloadList = GetDownloadListByAll(ActiveManifest);
var operation = new ResourceDownloaderOperation(PackageName, downloadList, downloadingMaxNumber, failedTryAgain);
return operation;
}

// PlayModeImpl.cs Line 245-264
public List<BundleInfo> GetDownloadListByAll(PackageManifest manifest)
{
if (manifest == null)
return new List<BundleInfo>();

List<BundleInfo> result = new List<BundleInfo>(1000);
foreach (var packageBundle in manifest.BundleList)
{
var fileSystem = GetBelongFileSystem(packageBundle);
if (fileSystem == null)
continue;

if (fileSystem.NeedDownload(packageBundle))
{
var bundleInfo = new BundleInfo(fileSystem, packageBundle);
result.Add(bundleInfo);
}
}
return result;
}

流程图

flowchart TD
    A["CreateResourceDownloaderByAll"] -->|调用| B["GetDownloadListByAll"]
    B -->|遍历| C["manifest.BundleList"]
    C -->|每个Bundle| D["GetBelongFileSystem"]
    D -->|检查| E["fileSystem.NeedDownload"]
    E -->|是| F["创建BundleInfo"]
    E -->|否| G["跳过该Bundle"]
    F --> H["添加到下载列表"]
    G --> I["继续下一个Bundle"]
    H --> I
    I -->|所有Bundle处理完| J["返回下载列表"]
    J --> K["创建ResourceDownloaderOperation"]
    K --> L["返回下载器"]

    %% 样式定义
    style A fill:#ff6b6b,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style B fill:#4ecdc4,stroke:#ffffff,stroke-width:2px,color:#000000
    style C fill:#45b7d1,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style D fill:#f9ca24,stroke:#ffffff,stroke-width:2px,color:#000000
    style E fill:#6c5ce7,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style F fill:#a29bfe,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style G fill:#fd79a8,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style H fill:#00d2d3,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style I fill:#ff9ff3,stroke:#ffffff,stroke-width:2px,color:#000000
    style J fill:#00aaff,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style K fill:#ff3838,stroke:#ffffff,stroke-width:2px,color:#ffffff
    style L fill:#2ed573,stroke:#ffffff,stroke-width:2px,color:#000000

CreateResourceDownloaderByTags

关键类

  • PlayModeImpl.cs
  • ResourceDownloaderOperation.cs
  • PackageBundle.cs

CreateResourceDownloaderByTags 方法用于创建下载指定标签资源的下载器,支持DLC(可下载内容)场景。

关键源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// PlayModeImpl.cs Line 116-121
ResourceDownloaderOperation IPlayMode.CreateResourceDownloaderByTags(string[] tags, int downloadingMaxNumber, int failedTryAgain)
{
List<BundleInfo> downloadList = GetDownloadListByTags(ActiveManifest, tags);
var operation = new ResourceDownloaderOperation(PackageName, downloadList, downloadingMaxNumber, failedTryAgain);
return operation;
}

// PlayModeImpl.cs Line 265-297
public List<BundleInfo> GetDownloadListByTags(PackageManifest manifest, string[] tags)
{
if (manifest == null)
return new List<BundleInfo>();

List<BundleInfo> result = new List<BundleInfo>(1000);
foreach (var packageBundle in manifest.BundleList)
{
var fileSystem = GetBelongFileSystem(packageBundle);
if (fileSystem == null)
continue;

if (fileSystem.NeedDownload(packageBundle))
{
// 如果未带任何标记,则统一下载
if (packageBundle.HasAnyTags() == false)
{
var bundleInfo = new BundleInfo(fileSystem, packageBundle);
result.Add(bundleInfo);
}
else
{
// 查询DLC资源
if (packageBundle.HasTag(tags))
{
var bundleInfo = new BundleInfo(fileSystem, packageBundle);
result.Add(bundleInfo);
}
}
}
}
return result;
}

流程图

flowchart TD
    A["CreateResourceDownloaderByTags"] -->|传入tags数组| B["GetDownloadListByTags"]
    B -->|遍历| C["manifest.BundleList"]
    C -->|每个Bundle| D["GetBelongFileSystem"]
    D -->|检查| E["fileSystem.NeedDownload"]
    E -->|否| F["跳过该Bundle"]
    E -->|是| G["检查Bundle标签"]
    G -->|HasAnyTags = false| H["无标签Bundle
统一下载"] G -->|HasAnyTags = true| I["检查HasTag匹配"] I -->|匹配| J["DLC资源
添加到下载列表"] I -->|不匹配| K["跳过该Bundle"] H --> L["创建BundleInfo"] J --> L L --> M["添加到结果列表"] F --> N["继续下一个Bundle"] K --> N M --> N N -->|处理完成| O["返回下载列表"] O --> P["创建ResourceDownloaderOperation"] %% 样式定义 style A fill:#ff6b6b,stroke:#ffffff,stroke-width:2px,color:#ffffff style B fill:#4ecdc4,stroke:#ffffff,stroke-width:2px,color:#000000 style C fill:#45b7d1,stroke:#ffffff,stroke-width:2px,color:#ffffff style D fill:#f9ca24,stroke:#ffffff,stroke-width:2px,color:#000000 style E fill:#6c5ce7,stroke:#ffffff,stroke-width:2px,color:#ffffff style F fill:#fd79a8,stroke:#ffffff,stroke-width:2px,color:#ffffff style G fill:#00d2d3,stroke:#ffffff,stroke-width:2px,color:#ffffff style H fill:#ff9ff3,stroke:#ffffff,stroke-width:2px,color:#000000 style I fill:#00aaff,stroke:#ffffff,stroke-width:2px,color:#ffffff style J fill:#ff3838,stroke:#ffffff,stroke-width:2px,color:#ffffff style K fill:#2ed573,stroke:#ffffff,stroke-width:2px,color:#000000 style L fill:#ffa502,stroke:#ffffff,stroke-width:2px,color:#000000 style M fill:#a29bfe,stroke:#ffffff,stroke-width:2px,color:#ffffff style N fill:#74b9ff,stroke:#ffffff,stroke-width:2px,color:#ffffff style O fill:#e17055,stroke:#ffffff,stroke-width:2px,color:#ffffff style P fill:#81ecec,stroke:#ffffff,stroke-width:2px,color:#000000

CreateResourceDownloaderByPaths

关键类

  • PlayModeImpl.cs
  • ResourceDownloaderOperation.cs
  • AssetInfo.cs
  • PackageManifest.cs

CreateResourceDownloaderByPaths 方法用于创建下载指定资源路径及其依赖的下载器,支持递归下载选项。

关键源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// PlayModeImpl.cs Line 122-127
ResourceDownloaderOperation IPlayMode.CreateResourceDownloaderByPaths(AssetInfo[] assetInfos, bool recursiveDownload, int downloadingMaxNumber, int failedTryAgain)
{
List<BundleInfo> downloadList = GetDownloadListByPaths(ActiveManifest, assetInfos, recursiveDownload);
var operation = new ResourceDownloaderOperation(PackageName, downloadList, downloadingMaxNumber, failedTryAgain);
return operation;
}

// PlayModeImpl.cs Line 298-359 (核心逻辑)
public List<BundleInfo> GetDownloadListByPaths(PackageManifest manifest, AssetInfo[] assetInfos, bool recursiveDownload)
{
if (manifest == null)
return new List<BundleInfo>();

// 获取资源对象的资源包和所有依赖资源包
List<PackageBundle> checkList = new List<PackageBundle>();
foreach (var assetInfo in assetInfos)
{
if (assetInfo.IsInvalid)
{
YooLogger.Warning(assetInfo.Error);
continue;
}

// 获取主资源包
PackageBundle mainBundle = manifest.GetMainPackageBundle(assetInfo.Asset);
if (checkList.Contains(mainBundle) == false)
checkList.Add(mainBundle);

// 获取依赖资源包
List<PackageBundle> mainDependBundles = manifest.GetAssetAllDependencies(assetInfo.Asset);
foreach (var dependBundle in mainDependBundles)
{
if (checkList.Contains(dependBundle) == false)
checkList.Add(dependBundle);
}

// 递归下载主资源包内所有资源对象依赖的资源包
if (recursiveDownload)
{
foreach (var otherMainAsset in mainBundle.IncludeMainAssets)
{
var otherMainBundle = manifest.GetMainPackageBundle(otherMainAsset.BundleID);
if (checkList.Contains(otherMainBundle) == false)
checkList.Add(otherMainBundle);

List<PackageBundle> otherDependBundles = manifest.GetAssetAllDependencies(otherMainAsset);
foreach (var dependBundle in otherDependBundles)
{
if (checkList.Contains(dependBundle) == false)
checkList.Add(dependBundle);
}
}
}
}

// 筛选需要下载的资源包
List<BundleInfo> result = new List<BundleInfo>(1000);
foreach (var packageBundle in checkList)
{
var fileSystem = GetBelongFileSystem(packageBundle);
if (fileSystem == null)
continue;

if (fileSystem.NeedDownload(packageBundle))
{
var bundleInfo = new BundleInfo(fileSystem, packageBundle);
result.Add(bundleInfo);
}
}
return result;
}

流程图

flowchart TD
    A["CreateResourceDownloaderByPaths"] -->|AssetInfo数组| B["GetDownloadListByPaths"]
    B -->|遍历| C["AssetInfo数组"]
    C -->|每个AssetInfo| D{AssetInfo.IsInvalid?}
    D -->|是| E["记录警告
跳过该资源"] D -->|否| F["获取主资源包"] F --> G["添加到checkList"] G --> H["获取依赖资源包"] H --> I["添加依赖到checkList"] I --> J{recursiveDownload?} J -->|否| K["处理下一个AssetInfo"] J -->|是| L["递归处理主资源包内
所有资源的依赖"] L --> M["获取其他主资源包"] M --> N["获取其他依赖资源包"] N --> O["添加到checkList"] O --> K E --> K K -->|所有AssetInfo处理完| P["遍历checkList"] P -->|每个PackageBundle| Q["GetBelongFileSystem"] Q --> R{fileSystem.NeedDownload?} R -->|是| S["创建BundleInfo
添加到结果"] R -->|否| T["跳过该Bundle"] S --> U["继续下一个Bundle"] T --> U U -->|处理完成| V["返回下载列表"] V --> W["创建ResourceDownloaderOperation"] %% 样式定义 style A fill:#ff6b6b,stroke:#ffffff,stroke-width:2px,color:#ffffff style B fill:#4ecdc4,stroke:#ffffff,stroke-width:2px,color:#000000 style C fill:#45b7d1,stroke:#ffffff,stroke-width:2px,color:#ffffff style D fill:#f9ca24,stroke:#ffffff,stroke-width:2px,color:#000000 style E fill:#fd79a8,stroke:#ffffff,stroke-width:2px,color:#ffffff style F fill:#6c5ce7,stroke:#ffffff,stroke-width:2px,color:#ffffff style G fill:#a29bfe,stroke:#ffffff,stroke-width:2px,color:#ffffff style H fill:#00d2d3,stroke:#ffffff,stroke-width:2px,color:#ffffff style I fill:#ff9ff3,stroke:#ffffff,stroke-width:2px,color:#000000 style J fill:#00aaff,stroke:#ffffff,stroke-width:2px,color:#ffffff style K fill:#ff3838,stroke:#ffffff,stroke-width:2px,color:#ffffff style L fill:#2ed573,stroke:#ffffff,stroke-width:2px,color:#000000 style M fill:#ffa502,stroke:#ffffff,stroke-width:2px,color:#000000 style N fill:#74b9ff,stroke:#ffffff,stroke-width:2px,color:#ffffff style O fill:#e17055,stroke:#ffffff,stroke-width:2px,color:#ffffff style P fill:#81ecec,stroke:#ffffff,stroke-width:2px,color:#000000 style Q fill:#55a3ff,stroke:#ffffff,stroke-width:2px,color:#ffffff style R fill:#fd79a8,stroke:#ffffff,stroke-width:2px,color:#ffffff style S fill:#00d2d3,stroke:#ffffff,stroke-width:2px,color:#ffffff style T fill:#dda0dd,stroke:#ffffff,stroke-width:2px,color:#000000 style U fill:#98fb98,stroke:#ffffff,stroke-width:2px,color:#000000 style V fill:#ffd700,stroke:#ffffff,stroke-width:2px,color:#000000 style W fill:#ff69b4,stroke:#ffffff,stroke-width:2px,color:#ffffff

下载器核心机制

ResourceDownloaderOperation 继承关系

flowchart LR
    AsyncOperationBase["AsyncOperationBase
异步操作基类"] DownloaderOperation["DownloaderOperation
下载器操作基类"] ResourceDownloaderOperation["ResourceDownloaderOperation
资源下载器"] ResourceUnpackerOperation["ResourceUnpackerOperation
资源解压器"] ResourceImporterOperation["ResourceImporterOperation
资源导入器"] AsyncOperationBase -.->|继承| DownloaderOperation DownloaderOperation -.->|继承| ResourceDownloaderOperation DownloaderOperation -.->|继承| ResourceUnpackerOperation DownloaderOperation -.->|继承| ResourceImporterOperation %% 样式定义 style AsyncOperationBase fill:#00aaff,stroke:#ffffff,stroke-width:2px,color:#ffffff style DownloaderOperation fill:#ff6600,stroke:#ffffff,stroke-width:2px,color:#ffffff style ResourceDownloaderOperation fill:#00ff88,stroke:#ffffff,stroke-width:2px,color:#000000 style ResourceUnpackerOperation fill:#ff4444,stroke:#ffffff,stroke-width:2px,color:#ffffff style ResourceImporterOperation fill:#44ff44,stroke:#ffffff,stroke-width:2px,color:#000000

下载器状态机

flowchart TD
    A["DownloaderOperation.BeginDownload
外部使用"] -->|InternalStart| B["Check"] B -->|验证下载列表| C{下载列表有效?} C -->|无效| D["Done
Status=Failed"] C -->|有效| E["Loading"] E -->|创建下载器| F["动态管理下载器池"] F -->|检查下载结果| G{有失败的下载?} G -->|有| H["Done
Status=Failed"] G -->|无| I{所有下载完成?} I -->|否| J["继续下载"] I -->|是| K["Done
Status=Succeed"] J --> F %% 样式定义 style A fill:#ff6b6b,stroke:#ffffff,stroke-width:2px,color:#ffffff style B fill:#4ecdc4,stroke:#ffffff,stroke-width:2px,color:#000000 style C fill:#f9ca24,stroke:#ffffff,stroke-width:2px,color:#000000 style D fill:#fd79a8,stroke:#ffffff,stroke-width:2px,color:#ffffff style E fill:#6c5ce7,stroke:#ffffff,stroke-width:2px,color:#ffffff style F fill:#a29bfe,stroke:#ffffff,stroke-width:2px,color:#ffffff style G fill:#00d2d3,stroke:#ffffff,stroke-width:2px,color:#ffffff style H fill:#ff9ff3,stroke:#ffffff,stroke-width:2px,color:#000000 style I fill:#00aaff,stroke:#ffffff,stroke-width:2px,color:#ffffff style J fill:#ff3838,stroke:#ffffff,stroke-width:2px,color:#ffffff style K fill:#2ed573,stroke:#ffffff,stroke-width:2px,color:#000000

下载器池管理机制

DownloaderOperation 采用动态下载器池来优化下载性能,主要特性:

  1. 最大并发限制:通过 MAX_LOADER_COUNT = 64_downloadingMaxNumber 控制同时下载的文件数量
  2. 失败重试机制:支持 _failedTryAgain 参数控制失败重试次数
  3. 动态调度:当有下载器完成时,自动从待下载列表中选择新的文件开始下载
  4. 暂停/恢复:支持 PauseDownload()ResumeDownload() 控制下载状态
  5. 进度回调:提供详细的下载进度、错误、开始等事件回调

需要注意 DownloaderOperation.InternalUpdate 方法中的关键逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 动态创建新的下载器到最大数量限制
// 注意:如果期间有下载失败的文件,暂停动态创建下载器
if (_bundleInfoList.Count > 0 && _failedList.Count == 0)
{
if (_isPause)
return;

if (_downloaders.Count < _downloadingMaxNumber)
{
int index = _bundleInfoList.Count - 1;
var bundleInfo = _bundleInfoList[index];
var downloader = bundleInfo.CreateDownloader(_failedTryAgain);
downloader.StartOperation();
this.AddChildOperation(downloader);

_downloaders.Add(downloader);
_bundleInfoList.RemoveAt(index);
}
}

从用户调用到UnityWebRequest的完整调用链

看完上面的分析,我觉得还需要把整个调用链梳理清楚,这样更容易理解整个下载流程。

调用链概述

用户调用下载器到最终发起UnityWebRequest的完整调用链是这样的:

graph TD
    A[用户代码] -->|downloader.BeginDownload| B[DownloaderOperation]
    B -->|OperationSystem.Update| C[InternalUpdate循环]
    C -->|bundleInfo.CreateDownloader| D[BundleInfo]
    D -->|_fileSystem.DownloadFileAsync| E[DefaultCacheFileSystem]
    E -->|new DownloadPackageBundleOperation| F[DownloadPackageBundleOperation]
    F -->|DownloadCenter.DownloadFileAsync| G[DownloadCenterOperation]
    G -->|创建具体下载器| H[UnityDownloadFileOperation]
    H -->|CreateWebRequest| I[UnityWebFileRequestOperation]
    I -->|_webRequest.SendWebRequest| J[UnityWebRequest]
    
    %% 详细步骤说明
    K[关键步骤说明]
    K --> L[用户调用BeginDownload启动下载]
    K --> M[OperationSystem每帧Update驱动]
    K --> N[BundleInfo适配不同FileSystem]
    K --> O[FileSystem创建具体下载操作]
    K --> P[DownloadCenter管理并发控制]
    K --> Q[UnityWebRequest执行实际网络请求]

    %% 暗色主题样式
    style A fill:#ff6b6b,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style B fill:#4ecdc4,stroke:#ffffff,stroke-width:3px,color:#000000
    style C fill:#45b7d1,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style D fill:#f9ca24,stroke:#ffffff,stroke-width:3px,color:#000000
    style E fill:#6c5ce7,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style F fill:#a29bfe,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style G fill:#fd79a8,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style H fill:#00d2d3,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style I fill:#ff9ff3,stroke:#ffffff,stroke-width:3px,color:#000000
    style J fill:#00aaff,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style K fill:#2ed573,stroke:#ffffff,stroke-width:3px,color:#000000
    style L fill:#ffa502,stroke:#ffffff,stroke-width:3px,color:#000000
    style M fill:#74b9ff,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style N fill:#e17055,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style O fill:#81ecec,stroke:#ffffff,stroke-width:3px,color:#000000
    style P fill:#55a3ff,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style Q fill:#dda0dd,stroke:#ffffff,stroke-width:3px,color:#000000

关键调用点解析

1. 责任分离设计

  • DownloaderOperation:管理下载器池和并发控制
  • BundleInfo:适配器,统一不同FileSystem的接口
  • DefaultCacheFileSystem:处理URL获取和参数配置
  • DownloadPackageBundleOperation:状态机管理,重试逻辑
  • DownloadCenter:并发限制和下载器复用
  • UnityDownloadFileOperation:Unity网络请求的具体实现

2. 异步调用链

整个调用链是异步的,通过 OperationSystem 的 Update 循环驱动:

1
2
// 主要更新循环
YooAsset.Update() → OperationSystem.Update() → AsyncOperationBase.Update() → InternalUpdate()

3. 错误处理和重试

在调用链的每一层都有相应的错误处理:

  • DownloaderOperation:失败隔离策略
  • DownloadPackageBundleOperation:重试逻辑和超时处理
  • UnityWebRequest:网络层错误处理

YooAsset源码阅读-Download
https://lshgame.com/2025/08/25/YooAsset_Code_Reading_Download/
作者
SuHang
发布于
2025年8月25日
许可协议