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;
}

流程图

graph 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: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:#ff3838,stroke:#ffffff,stroke-width:3px,color:#ffffff
    style L fill:#2ed573,stroke:#ffffff,stroke-width:3px,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;
}

流程图

graph 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: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:#fd79a8,stroke:#ffffff,stroke-width:3px,color:#ffffff style G fill:#00d2d3,stroke:#ffffff,stroke-width:3px,color:#ffffff style H fill:#ff9ff3,stroke:#ffffff,stroke-width:3px,color:#000000 style I fill:#00aaff,stroke:#ffffff,stroke-width:3px,color:#ffffff style J fill:#ff3838,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:#a29bfe,stroke:#ffffff,stroke-width:3px,color:#ffffff style N fill:#74b9ff,stroke:#ffffff,stroke-width:3px,color:#ffffff style O fill:#e17055,stroke:#ffffff,stroke-width:3px,color:#ffffff style P fill:#81ecec,stroke:#ffffff,stroke-width:3px,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;
}

流程图

graph 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: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:#fd79a8,stroke:#ffffff,stroke-width:3px,color:#ffffff style F fill:#6c5ce7,stroke:#ffffff,stroke-width:3px,color:#ffffff style G fill:#a29bfe,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:#ff3838,stroke:#ffffff,stroke-width:3px,color:#ffffff style L fill:#2ed573,stroke:#ffffff,stroke-width:3px,color:#000000 style M fill:#ffa502,stroke:#ffffff,stroke-width:3px,color:#000000 style N fill:#74b9ff,stroke:#ffffff,stroke-width:3px,color:#ffffff style O fill:#e17055,stroke:#ffffff,stroke-width:3px,color:#ffffff style P fill:#81ecec,stroke:#ffffff,stroke-width:3px,color:#000000 style Q fill:#55a3ff,stroke:#ffffff,stroke-width:3px,color:#ffffff style R fill:#fd79a8,stroke:#ffffff,stroke-width:3px,color:#ffffff style S fill:#00d2d3,stroke:#ffffff,stroke-width:3px,color:#ffffff style T fill:#dda0dd,stroke:#ffffff,stroke-width:3px,color:#000000 style U fill:#98fb98,stroke:#ffffff,stroke-width:3px,color:#000000 style V fill:#ffd700,stroke:#ffffff,stroke-width:3px,color:#000000 style W fill:#ff69b4,stroke:#ffffff,stroke-width:3px,color:#ffffff

下载器核心机制

ResourceDownloaderOperation 继承关系

graph 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:3px,color:#ffffff style DownloaderOperation fill:#ff6600,stroke:#ffffff,stroke-width:3px,color:#ffffff style ResourceDownloaderOperation fill:#00ff88,stroke:#ffffff,stroke-width:3px,color:#000000 style ResourceUnpackerOperation fill:#ff4444,stroke:#ffffff,stroke-width:3px,color:#ffffff style ResourceImporterOperation fill:#44ff44,stroke:#ffffff,stroke-width:3px,color:#000000

下载器状态机

DownloaderOperation 使用状态机管理下载流程:

graph 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:3px,color:#ffffff style B fill:#4ecdc4,stroke:#ffffff,stroke-width:3px,color:#000000 style C fill:#f9ca24,stroke:#ffffff,stroke-width:3px,color:#000000 style D fill:#fd79a8,stroke:#ffffff,stroke-width:3px,color:#ffffff 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:#00d2d3,stroke:#ffffff,stroke-width:3px,color:#ffffff style H fill:#ff9ff3,stroke:#ffffff,stroke-width:3px,color:#000000 style I fill:#00aaff,stroke:#ffffff,stroke-width:3px,color:#ffffff style J fill:#ff3838,stroke:#ffffff,stroke-width:3px,color:#ffffff style K fill:#2ed573,stroke:#ffffff,stroke-width:3px,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日
许可协议