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.StartOperationOperationSystem.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:网络层错误处理