YooAsset源码阅读-Downloader
继续 YooAsset 的 Downloader ,本文将详细介绍如何创建下载器相关代码
CreateResourceDownloaderByAll
关键类
- PlayModeImpl.cs
- ResourceDownloaderOperation.cs
- DownloaderOperation.cs
- BundleInfo.cs
CreateResourceDownloaderByAll 方法用于创建下载所有需要更新资源的下载器。
关键源代码
// 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;
}
流程图
CreateResourceDownloaderByTags
关键类
- PlayModeImpl.cs
- ResourceDownloaderOperation.cs
- PackageBundle.cs
CreateResourceDownloaderByTags 方法用于创建下载指定标签资源的下载器,支持DLC(可下载内容)场景。
关键源代码
// 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;
}
流程图
CreateResourceDownloaderByPaths
关键类
- PlayModeImpl.cs
- ResourceDownloaderOperation.cs
- AssetInfo.cs
- PackageManifest.cs
CreateResourceDownloaderByPaths 方法用于创建下载指定资源路径及其依赖的下载器,支持递归下载选项。
关键源代码
// 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;
}
流程图
下载器核心机制
ResourceDownloaderOperation 继承关系
下载器状态机
DownloaderOperation 使用状态机管理下载流程:
下载器池管理机制
DownloaderOperation 采用动态下载器池来优化下载性能,主要特性:
- 最大并发限制:通过
MAX_LOADER_COUNT = 64
和_downloadingMaxNumber
控制同时下载的文件数量 - 失败重试机制:支持
_failedTryAgain
参数控制失败重试次数 - 动态调度:当有下载器完成时,自动从待下载列表中选择新的文件开始下载
- 暂停/恢复:支持
PauseDownload()
和ResumeDownload()
控制下载状态 - 进度回调:提供详细的下载进度、错误、开始等事件回调
需要注意 DownloaderOperation.InternalUpdate 方法中的关键逻辑:
// 动态创建新的下载器到最大数量限制
// 注意:如果期间有下载失败的文件,暂停动态创建下载器
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的完整调用链是这样的:
用户代码 → downloader.BeginDownload() → OperationSystem.Update() → DownloaderOperation.InternalUpdate()
→ BundleInfo.CreateDownloader() → DefaultCacheFileSystem.DownloadFileAsync() → DownloadPackageBundleOperation
→ DownloadCenter.DownloadFileAsync() → UnityDownloadFileOperation → UnityWebFileRequestOperation → UnityWebRequest
详细调用链分析
1. 用户入口点
// 用户代码
var downloader = package.CreateResourceDownloaderByAll(10, 3);
downloader.BeginDownload(); // 开始下载
2. BeginDownload → InternalUpdate
// DownloaderOperation.cs
public void BeginDownload()
{
// 开始下载会调用 OperationSystem.StartOperation
OperationSystem.StartOperation(PackageName, this);
}
// OperationSystem每帧会调用
internal override void InternalUpdate()
{
// 在Loading状态时会动态创建下载器
if (_bundleInfoList.Count > 0 && _failedList.Count == 0)
{
var bundleInfo = _bundleInfoList[index];
var downloader = bundleInfo.CreateDownloader(_failedTryAgain); // 关键调用
downloader.StartOperation();
this.AddChildOperation(downloader);
}
}
3. BundleInfo.CreateDownloader
// BundleInfo.cs Line 39-44
public FSDownloadFileOperation CreateDownloader(int failedTryAgain)
{
DownloadFileOptions options = new DownloadFileOptions(failedTryAgain);
options.ImportFilePath = _importFilePath;
return _fileSystem.DownloadFileAsync(Bundle, options); // 委托给FileSystem
}
4. DefaultCacheFileSystem.DownloadFileAsync
// DefaultCacheFileSystem.cs Line 172-191
public virtual FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options)
{
// 获取下载地址
string mainURL = RemoteServices.GetRemoteMainURL(bundle.FileName);
string fallbackURL = RemoteServices.GetRemoteFallbackURL(bundle.FileName);
options.SetURL(mainURL, fallbackURL);
// 创建具体的下载操作
var downloader = new DownloadPackageBundleOperation(this, bundle, options);
return downloader;
}
5. DownloadPackageBundleOperation
// DownloadPackageBundleOperation.cs Line 58-72
if (_steps == ESteps.CreateRequest)
{
string url = GetRequestURL();
// 委托给DownloadCenter创建Unity下载器
_unityDownloadFileOp = _fileSystem.DownloadCenter.DownloadFileAsync(Bundle, url);
_steps = ESteps.CheckRequest;
}
6. DownloadCenter → UnityDownloadFileOperation
// DownloadCenterOperation.cs
public UnityDownloadFileOperation DownloadFileAsync(PackageBundle bundle, string url)
{
// 根据断点续传等条件创建不同的下载器
if (bundle.FileSize >= _fileSystem.ResumeDownloadMinimumSize)
{
return new ResumeDownloadOperation(_fileSystem, bundle, url);
}
else
{
return new NormalDownloadOperation(_fileSystem, bundle, url);
}
}
7. UnityDownloadFileOperation → UnityWebRequest
// UnityWebFileRequestOperation.cs Line 72-81
private void CreateWebRequest()
{
DownloadHandlerFile handler = new DownloadHandlerFile(_fileSavePath);
handler.removeFileOnAbort = true;
// 最终创建UnityWebRequest
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
_webRequest.timeout = _timeout;
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
// 发起网络请求
_requestOperation = _webRequest.SendWebRequest();
}
完整调用链流程图
关键调用点解析
1. 责任分离设计
- DownloaderOperation:管理下载器池和并发控制
- BundleInfo:适配器,统一不同FileSystem的接口
- DefaultCacheFileSystem:处理URL获取和参数配置
- DownloadPackageBundleOperation:状态机管理,重试逻辑
- DownloadCenter:并发限制和下载器复用
- UnityDownloadFileOperation:Unity网络请求的具体实现
2. 异步调用链
整个调用链是异步的,通过 OperationSystem 的 Update 循环驱动:
// 主要更新循环
YooAsset.Update() → OperationSystem.Update() → AsyncOperationBase.Update() → InternalUpdate()
3. 错误处理和重试
在调用链的每一层都有相应的错误处理:
- DownloaderOperation:失败隔离策略
- DownloadPackageBundleOperation:重试逻辑和超时处理
- UnityWebRequest:网络层错误处理