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:網絡層錯誤處理