YooAsset源码阅读-Downloader篇

发布于:2025-08-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

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

流程图

调用
遍历
每个Bundle
检查
所有Bundle处理完
CreateResourceDownloaderByAll
GetDownloadListByAll
manifest.BundleList
GetBelongFileSystem
fileSystem.NeedDownload
创建BundleInfo
跳过该Bundle
添加到下载列表
继续下一个Bundle
返回下载列表
创建ResourceDownloaderOperation
返回下载器

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

流程图

传入tags数组
遍历
每个Bundle
检查
HasAnyTags = false
HasAnyTags = true
匹配
不匹配
处理完成
CreateResourceDownloaderByTags
GetDownloadListByTags
manifest.BundleList
GetBelongFileSystem
fileSystem.NeedDownload
跳过该Bundle
检查Bundle标签
无标签Bundle
统一下载
检查HasTag匹配
DLC资源
添加到下载列表
跳过该Bundle
创建BundleInfo
添加到结果列表
继续下一个Bundle
返回下载列表
创建ResourceDownloaderOperation

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

流程图

AssetInfo数组
遍历
每个AssetInfo
所有AssetInfo处理完
每个PackageBundle
处理完成
CreateResourceDownloaderByPaths
GetDownloadListByPaths
AssetInfo数组
AssetInfo.IsInvalid?
记录警告
跳过该资源
获取主资源包
添加到checkList
获取依赖资源包
添加依赖到checkList
recursiveDownload?
处理下一个AssetInfo
递归处理主资源包内
所有资源的依赖
获取其他主资源包
获取其他依赖资源包
添加到checkList
遍历checkList
GetBelongFileSystem
fileSystem.NeedDownload?
创建BundleInfo
添加到结果
跳过该Bundle
继续下一个Bundle
返回下载列表
创建ResourceDownloaderOperation

下载器核心机制

ResourceDownloaderOperation 继承关系

继承
继承
继承
继承
AsyncOperationBase
异步操作基类
DownloaderOperation
下载器操作基类
ResourceDownloaderOperation
资源下载器
ResourceUnpackerOperation
资源解压器
ResourceImporterOperation
资源导入器

下载器状态机

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

InternalStart
验证下载列表
无效
有效
创建下载器
检查下载结果
DownloaderOperation.BeginDownload
外部使用
Check
下载列表有效?
Done
Status=Failed
Loading
动态管理下载器池
有失败的下载?
Done
Status=Failed
所有下载完成?
继续下载
Done
Status=Succeed

下载器池管理机制

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

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

需要注意 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();
}

完整调用链流程图

downloader.BeginDownload
OperationSystem.Update
bundleInfo.CreateDownloader
_fileSystem.DownloadFileAsync
new DownloadPackageBundleOperation
DownloadCenter.DownloadFileAsync
创建具体下载器
CreateWebRequest
_webRequest.SendWebRequest
用户代码
DownloaderOperation
InternalUpdate循环
BundleInfo
DefaultCacheFileSystem
DownloadPackageBundleOperation
DownloadCenterOperation
UnityDownloadFileOperation
UnityWebFileRequestOperation
UnityWebRequest
关键步骤说明
用户调用BeginDownload启动下载
OperationSystem每帧Update驱动
BundleInfo适配不同FileSystem
FileSystem创建具体下载操作
DownloadCenter管理并发控制
UnityWebRequest执行实际网络请求

关键调用点解析

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

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

// 主要更新循环
YooAsset.Update() → OperationSystem.Update() → AsyncOperationBase.Update()InternalUpdate()
3. 错误处理和重试

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

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

网站公告

今日签到

点亮在社区的每一天
去签到