Addressables的运行时初始化都做了什么?


当你第一次调用Addressables的运行时API,或者显示调用[color=blue]Addressables.InitializeAsync[/color]时,Addressables都会进行初始化操作。在整个初始化的过程中,主要做了以下几件事情:
1、设置ResourceManager和各种ResourceLocator。
2、加载ResourceManagerRuntimeData对象。
3、执行IInitializableObject操作。
4、如果有需要,检查是否有content catalog更新。
5、加载content catalog。

来看一下[color=blue]Addressables.InitializeAsync[/color]的代码,依然是比较简单:

/// <param name="autoReleaseHandle">如果为true,则在Complete的时候自动释放返回的句柄</param>
/// <returns>异步操作句柄</returns>
public static AsyncOperationHandle<IResourceLocator> InitializeAsync(bool autoReleaseHandle)
{
    // 直接调用AddressablesImpl.InitializeAsync
    return m_Addressables.InitializeAsync(autoReleaseHandle);
}

/// AddressablesImpl.InitializeAsync
public AsyncOperationHandle<IResourceLocator> InitializeAsync(bool autoReleaseHandle)
{
    // 读取配置文件路径
    var settingsPath =
#if UNITY_EDITOR
        // 如果是编辑器,则从PlayerPrefs读取,这个键值是在各个BuildScript中设置的
        PlayerPrefs.GetString(Addressables.kAddressablesRuntimeDataPath, RuntimePath + "/settings.json");
#else
        // 否则从AddressablesImpl.RuntimePath读取
        RuntimePath + "/settings.json";
#endif
    // 读出来的配置很有可能是{UnityEngine.AddressableAssets.Addressables.RuntimePath}/settings.json这样的字符串
    // 需要调用ResolveInternalId来进行解析,返回实际的路径
    // 然后调用最终的初始化接口
    return InitializeAsync(ResolveInternalId(settingsPath), null, autoReleaseHandle);
}

/// <param name="runtimeDataPath">运行时初始化配置的路径</param>
/// <param name="providerSuffix">如果为true,则在Complete的时候自动释放返回的句柄</param>
/// <param name="autoReleaseHandle">如果为true,则在Complete的时候自动释放返回的句柄</param>
/// <returns>异步操作句柄</returns>
public AsyncOperationHandle<IResourceLocator> InitializeAsync(string runtimeDataPath, string providerSuffix = null, bool autoReleaseHandle = true)
{
    if (hasStartedInitialization)
    {
        // 当前正在初始化中或已初始化完毕
        if (m_InitializationOperation.IsValid())
            // 正在初始化中,直接返回当前的Operation
            return m_InitializationOperation;
        // 已初始化完毕,返回完成操作(CompletedOperation)
        var completedOperation = ResourceManager.CreateCompletedOperation(m_ResourceLocators[0].Locator, errorMsg: null);
        if (autoReleaseHandle)
            AutoReleaseHandleOnCompletion(completedOperation);
        return completedOperation;
    }

    if (ResourceManager.ExceptionHandler == null)
    {
        ResourceManager.ExceptionHandler = LogException;
    }

    // 设置初始化标记
    hasStartedInitialization = true;
    // 这里的判断感觉没有必要?
    if (m_InitializationOperation.IsValid())
        return m_InitializationOperation;
    //these need to be referenced in order to prevent stripping on IL2CPP platforms.
    if (string.IsNullOrEmpty(Application.streamingAssetsPath))
        Addressables.LogWarning("Application.streamingAssetsPath has been stripped!");
#if !UNITY_SWITCH
    if (string.IsNullOrEmpty(Application.persistentDataPath))
        Addressables.LogWarning("Application.persistentDataPath has been stripped!");
#endif
    if (string.IsNullOrEmpty(runtimeDataPath))
        return ResourceManager.CreateCompletedOperation<IResourceLocator>(null, string.Format("Invalid Key: {0}", runtimeDataPath));

    // 设置三个事件回调
    m_OnHandleCompleteAction = OnHandleCompleted;
    m_OnSceneHandleCompleteAction = OnSceneHandleCompleted;
    m_OnHandleDestroyedAction = OnHandleDestroyed;

#if UNITY_EDITOR
    //this indicates that a specific addressables settings asset is being used for the runtime locations
    if (runtimeDataPath.StartsWith("GUID:"))
    {
        // 当前设置的是某个文件的GUID作为配置,则需先找到该GUID对应的配置文件,然后根据该文件进行初始化
        // 当在Groups设置里使用的是"Use Asset Database(fastest)"时(即BuildScriptFastMode)用的是GUID
        var assembly = Assembly.Load("Unity.Addressables.Editor");
        var settingsType = assembly.GetType("UnityEditor.AddressableAssets.Settings.AddressableAssetSettings");
        var settingsGUID = runtimeDataPath.Substring(runtimeDataPath.IndexOf(':') + 1);
        var settingsPath = UnityEditor.AssetDatabase.GUIDToAssetPath(settingsGUID);
        var settingsObj = UnityEditor.AssetDatabase.LoadAssetAtPath(settingsPath, settingsType);
        var settingsSetupMethod = settingsType.GetMethod("CreatePlayModeInitializationOperation", BindingFlags.Instance | BindingFlags.NonPublic);
        // 调用AddressableAssetSettings.CreatePlayModeInitializationOperation
        // 而AddressableAssetSettings.CreatePlayModeInitializationOperation是创建了FastModeInitializationOperation并启动
        m_InitializationOperation = (AsyncOperationHandle<IResourceLocator>)settingsSetupMethod.Invoke(settingsObj, new object[] { this });
    }
    else
#endif
        // 调用InitializationOperation.CreateInitializationOperation创建初始化操作
        m_InitializationOperation = Initialization.InitializationOperation.CreateInitializationOperation(this, runtimeDataPath, providerSuffix);
    if (autoReleaseHandle)
        AutoReleaseHandleOnCompletion(m_InitializationOperation);

    return m_InitializationOperation;
}

由此可见,这里根据不同情况分别调用了两个初始化接口。当在编辑器下,且配置是GUID时(选择了Use Asset Database模式),会使用[color=blue]AddressableAssetSettings.CreatePlayModeInitializationOperation [/color],而它里头则是创建了[color=blue]FastModeInitializationOperation[/color]。其他情况下则是调用[color=blue]InitializationOperation.CreateInitializationOperation[/color]。那么这两个接口又有什么不同呢?我们先来看一下大多数情况下所调用的[color=blue]InitializationOperation.CreateInitializationOperation[/color]接口:

internal static AsyncOperationHandle<IResourceLocator> CreateInitializationOperation(AddressablesImpl aa, string playerSettingsLocation, string providerSuffix)
{
    // 添加各种Provider
    var jp = new JsonAssetProvider();
    aa.ResourceManager.ResourceProviders.Add(jp);
    var tdp = new TextDataProvider();
    aa.ResourceManager.ResourceProviders.Add(tdp);
    aa.ResourceManager.ResourceProviders.Add(new ContentCatalogProvider(aa.ResourceManager));

    // 用传入的playerSettingsLocation创建ResourceLocation
    var runtimeDataLocation = new ResourceLocationBase("RuntimeData", playerSettingsLocation, typeof(JsonAssetProvider).FullName, typeof(ResourceManagerRuntimeData));

    // 创建InitializationOperation和InitalizationObjectsOperation,并加载ResourceManagerRuntimeData配置
    var initOp = new InitializationOperation(aa);
    initOp.m_rtdOp = aa.ResourceManager.ProvideResource<ResourceManagerRuntimeData>(runtimeDataLocation);
    initOp.m_ProviderSuffix = providerSuffix;
    initOp.m_InitGroupOps = new InitalizationObjectsOperation();
    initOp.m_InitGroupOps.Init(initOp.m_rtdOp, aa);

    // 启动InitalizationObjectsOperation
    var groupOpHandle = aa.ResourceManager.StartOperation(initOp.m_InitGroupOps, initOp.m_rtdOp);

    return aa.ResourceManager.StartOperation<IResourceLocator>(initOp, groupOpHandle);
}

这个接口做的事情很简单,先是添加了3个Provider,接着就是创建初始化相关的Operation,最后分别启动它们并设置它们的依赖关系(InitalizationObjectsOperation依赖于ResourceManager.ProvideResource<ResourceManagerRuntimeData>(),而InitializationOperation依赖于InitalizationObjectsOperation)。
先来看一下配置文件[color=red]ResourceManagerRuntimeData[/color]的定义:

public class ResourceManagerRuntimeData
{
    // ContentCatalogData的资源地址(Address)
    public const string kCatalogAddress = "AddressablesMainContentCatalog";

    [SerializeField]
    string m_buildTarget;
    // 当前这份数据对应的BuildTarget
    public string BuildTarget { get { return m_buildTarget; } set { m_buildTarget = value; } }

    [FormerlySerializedAs("m_settingsHash")]
    [SerializeField]
    string m_SettingsHash;
    // 按字面意思是生成该Data的配置文件的哈希值,但是该变量并没有被复制,为空字符串
    public string SettingsHash { get { return m_SettingsHash; } set { m_SettingsHash = value; } }
    [FormerlySerializedAs("m_catalogLocations")]
    [SerializeField]
    List<ResourceLocationData> m_CatalogLocations = new List<ResourceLocationData>();
    // ContentCatalogData的资源位置列表,先尝试远程地址,再尝试本地
    public List<ResourceLocationData> CatalogLocations { get { return m_CatalogLocations; } }
    [FormerlySerializedAs("m_profileEvents")]
    [SerializeField]
    bool m_ProfileEvents;
    // 代表ResourceManager是否向Profiler发送事件的标志位
    public bool ProfileEvents { get { return m_ProfileEvents; } set { m_ProfileEvents = value; } }

    [FormerlySerializedAs("m_logResourceManagerExceptions")]
    [SerializeField]
    bool m_LogResourceManagerExceptions = true;
    // 若为true,则ResourceManager.ExceptionHandler会被设置为(op, ex) => Debug.LogException(ex);
    public bool LogResourceManagerExceptions { get { return m_LogResourceManagerExceptions; } set { m_LogResourceManagerExceptions = value; } }

    [FormerlySerializedAs("m_extraInitializationData")]
    [SerializeField]
    List<ObjectInitializationData> m_ExtraInitializationData = new List<ObjectInitializationData>();
    /// <summary>
    /// The list of initialization data.  These objects will get deserialized and initialized during the Addressables initialization process.  This happens after resource providers have been set up but before any catalogs are loaded.
    /// </summary>
    // 初始化数据列表,它们在Resource Provider被设置之后进行初始化
    public List<ObjectInitializationData> InitializationObjects { get { return m_ExtraInitializationData; } }

    [SerializeField] private bool m_DisableCatalogUpdateOnStart = false;

    // 这个属性决定了我们是否要在启动时更新Catalog
    public bool DisableCatalogUpdateOnStartup
    {
        get { return m_DisableCatalogUpdateOnStart; }
        set { m_DisableCatalogUpdateOnStart = value; }
    }

    [SerializeField] private bool m_IsLocalCatalogInBundle = false;

    // 若为true,表示本地Catalog被打在了assetbundle中,为false则表示是一个独立的json文件
    public bool IsLocalCatalogInBundle
    {
        get { return m_IsLocalCatalogInBundle; }
        set { m_IsLocalCatalogInBundle = value; }
    }

    [SerializeField]
    SerializedType m_CertificateHandlerType;

    // 该Type被用于创建ResourceManager.CertificateHandlerInstance
    public Type CertificateHandlerType
    {
        get
        {
            return m_CertificateHandlerType.Value;
        }
        set
        {
            m_CertificateHandlerType.Value = value;
        }
    }

    [SerializeField]
    string m_AddressablesVersion;

    // 生成这个配置文件时使用的Addressables包的版本号
    // 读的是UnityEditor PackageManager的信息
    // 若是直接拷贝的源代码则会读不到信息
    // 但是从代码上来看目前这个字段也没有任何代码访问到
    public string AddressablesVersion
    {
        get
        {
            return m_AddressablesVersion;
        }
        set
        {
            m_AddressablesVersion = value;
        }
    }

    [SerializeField]
    int m_maxConcurrentWebRequests = 500;
    // 同时进行WebRequest的请求数量,只能从1到1024
    public int MaxConcurrentWebRequests { get { return m_maxConcurrentWebRequests; } set { m_maxConcurrentWebRequests = Mathf.Clamp(value, 1, 1024); } }

    [SerializeField]
    int m_CatalogRequestsTimeout = 0;
    // 请求Catalog的超时时间,0则为默认超时时间
    public int CatalogRequestsTimeout { get { return m_CatalogRequestsTimeout; } set { m_CatalogRequestsTimeout = value < 0 ? 0 : value; } }
}

[color=red]ResourceManagerRuntimeData[/color]顾名思义是运行时用来配置ResourceManager用的,它的具体使用就是在刚才说到的[color=red]InitalizationObjectsOperation[/color]和[color=red]InitializationOperation[/color]这两个类里头。那让我们来看一下第一个类的执行函数[color=blue]InitalizationObjectsOperation.Execute[/color]:

protected override void Execute()
{
    var rtd = m_RtdOp.Result;
    if (rtd == null)
    {
        Addressables.LogError("RuntimeData is null.  Please ensure you have built the correct Player Content.");
        Complete(true, true, "");
        return;
    }

    // 读取RuntimeBuildLog的日志路径,并调用LogRuntimeWarnings把它们调用UnityEngine.Debug.Log输出出来
    // 这个文件虽然叫做BuildLog,但是它其实是在运行期初始化时生成的
    // 详见BuildScriptPackedPlayMode
    string buildLogsPath = m_Addressables.ResolveInternalId(PlayerPrefs.GetString(Addressables.kAddressablesRuntimeBuildLogPath));
    if (LogRuntimeWarnings(buildLogsPath))
        File.Delete(buildLogsPath);

    // 根据ResourceManagerRuntimeData.InitializationObjects来创建初始化操作
    // InitalizationObjectsOperation这个类名也是因为这个过程而得名
    List<AsyncOperationHandle> initOperations = new List<AsyncOperationHandle>();
    foreach (var i in rtd.InitializationObjects)
    {
        if (i.ObjectType.Value == null)
        {
            Addressables.LogFormat("Invalid initialization object type {0}.", i.ObjectType);
            continue;
        }

        try
        {
            var o = i.GetAsyncInitHandle(m_Addressables.ResourceManager);
            initOperations.Add(o);
            Addressables.LogFormat("Initialization object {0} created instance {1}.", i, o);
        }
        catch (Exception ex)
        {
            Addressables.LogErrorFormat("Exception thrown during initialization of object {0}: {1}", i,
                                        ex.ToString());
        }
    }

    // 创建一个组操作,它会依赖于initOperations中的所有Operation
    m_DepOp = m_Addressables.ResourceManager.CreateGenericGroupOperation(initOperations, true);
    // 对上面那个组操作设置完成回调
    m_DepOp.Completed += (obj) =>
    {
        bool success = obj.Status == AsyncOperationStatus.Succeeded;
        Complete(true, success, success ? "" : $"{obj.DebugName}, status={obj.Status}, result={obj.Result} failed initialization.");
        m_Addressables.Release(m_DepOp);
    };
}

[color=blue]InitalizationObjectsOperation.Execute[/color]所做的事情很简单—-读取[color=blue]ResourceManagerRuntimeData.InitializationObjects[/color]并根据它创建相应的初始化操作。目前只看到一个[color=red]CacheInitialization[/color]。
接着是[color=blue]InitializationOperation.Execute[/color]:

protected override void Execute()
{
    Addressables.LogFormat("Addressables - runtime data operation completed with status = {0}, result = {1}.", m_rtdOp.Status, m_rtdOp.Result);
    // 一样的,InitializationOperation需要访问ResourceManagerRuntimeData数据
    if (m_rtdOp.Result == null)
    {
        Addressables.LogWarningFormat("Addressables - Unable to load runtime data at location {0}.", m_rtdOp);
        Complete(Result, false, string.Format("Addressables - Unable to load runtime data at location {0}.", m_rtdOp));
        return;
    }
    Addressables.LogFormat("Initializing Addressables version {0}.", m_rtdOp.Result.AddressablesVersion);
    var rtd = m_rtdOp.Result;

    // 设置Addressables的一些基本参数(是否发送Profiler事件、设置WebRequest同时请求数、Catalog获取超时时间)
    m_Addressables.ResourceManager.postProfilerEvents = rtd.ProfileEvents;
    WebRequestQueue.SetMaxConcurrentRequests(rtd.MaxConcurrentWebRequests);
    m_Addressables.CatalogRequestsTimeout = rtd.CatalogRequestsTimeout;
    // 设置WebRequestTimeout
    foreach (var catalogLocation in rtd.CatalogLocations)
    {
        if (catalogLocation.Data != null && catalogLocation.Data is ProviderLoadRequestOptions loadData)
        {
            loadData.WebRequestTimeout = rtd.CatalogRequestsTimeout;
        }
    }

    // 释放m_rtdOp
    m_Addressables.Release(m_rtdOp);
    // 如果有设置CertificateHandlerType,则创建ResourceManager.CertificateHandlerInstance
    // ResourceManager.CertificateHandlerInstance是用来WebRequest请求时进行证书验证的
    // 若为null则使用默认的验证规则。可从CertificateHandler派生,从而自定义验证规则
    // 例子见https://docs.unity3d.com/ScriptReference/Networking.CertificateHandler.ValidateCertificate.html
    if (rtd.CertificateHandlerType != null)
        m_Addressables.ResourceManager.CertificateHandlerInstance = Activator.CreateInstance(rtd.CertificateHandlerType) as CertificateHandler;

#if UNITY_EDITOR
    if (UnityEditor.EditorUserBuildSettings.activeBuildTarget.ToString() != rtd.BuildTarget)
        Addressables.LogErrorFormat("Addressables - runtime data was built with a different build target.  Expected {0}, but data was built with {1}.  Certain assets may not load correctly including shaders.  You can rebuild player content via the Addressables window.", UnityEditor.EditorUserBuildSettings.activeBuildTarget, rtd.BuildTarget);
#endif
    // 设置是否异常处理器
    if (!rtd.LogResourceManagerExceptions)
        ResourceManager.ExceptionHandler = null;

    // 设置是否有分析器(m_Diagnostics)
    if (!rtd.ProfileEvents)
    {
        m_Diagnostics.Dispose();
        m_Diagnostics = null;
        m_Addressables.ResourceManager.ClearDiagnosticCallbacks();
    }

    Addressables.Log("Addressables - loading initialization objects.");

    // 设置ContentCatalogProvider中的参数
    ContentCatalogProvider ccp = m_Addressables.ResourceManager.ResourceProviders
                                 .FirstOrDefault(rp => rp.GetType() == typeof(ContentCatalogProvider)) as ContentCatalogProvider;
    if (ccp != null)
    {
        ccp.DisableCatalogUpdateOnStart = rtd.DisableCatalogUpdateOnStartup;
        ccp.IsLocalCatalogInBundle = rtd.IsLocalCatalogInBundle;
    }

    // 将ResourceManagerRuntimeData.CatalogLocations作为参数创建ResourceLocationMap
    // 并将其添加到ResourceLocator中
    var locMap = new ResourceLocationMap("CatalogLocator", rtd.CatalogLocations);
    m_Addressables.AddResourceLocator(locMap);
    IList<IResourceLocation> catalogs;
    // 查询ContentCatalogData文件的资源地址
    if (!locMap.Locate(ResourceManagerRuntimeData.kCatalogAddress, typeof(ContentCatalogData), out catalogs))
    {
        Addressables.LogWarningFormat(
            "Addressables - Unable to find any catalog locations in the runtime data.");
        m_Addressables.RemoveResourceLocator(locMap);
        Complete(Result, false, "Addressables - Unable to find any catalog locations in the runtime data.");
    }
    else
    {
        // 待补充
        Addressables.LogFormat("Addressables - loading content catalogs, {0} found.", catalogs.Count);
        IResourceLocation remoteHashLocation = null;
        if (catalogs[0].Dependencies.Count == 2 && rtd.DisableCatalogUpdateOnStartup)
        {
            remoteHashLocation = catalogs[0].Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Remote];
            catalogs[0].Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Remote] = catalogs[0].Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Cache];
        }
        // 加载ContentCatalogData
        m_loadCatalogOp = LoadContentCatalogInternal(catalogs, 0, locMap, remoteHashLocation);
    }
}

[color=blue]InitializationOperation.Execute[/color]除了设置一些参数之外,它的最终目的就是为了通过[color=blue]InitializationOperation.LoadContentCatalogInternal[/color]调用[color=blue]InitializationOperation.LoadContentCatalog[/color]来加载ContentCatalogData。我们直接来看[color=blue]InitializationOperation.LoadContentCatalog[/color]的代码:

public static AsyncOperationHandle<IResourceLocator> LoadContentCatalog(AddressablesImpl addressables, IResourceLocation loc, string providerSuffix, IResourceLocation remoteHashLocation = null)
{
    // 构建一个ProviderOperation类型
    Type provType = typeof(ProviderOperation<ContentCatalogData>);
    // 创建一个加载ContentCatalogData的Operation
    var catalogOp = addressables.ResourceManager.CreateOperation<ProviderOperation<ContentCatalogData>>(provType, provType.GetHashCode(), null, null);

    // 从ResourceManager.ResourceProviders中找到ContentCatalogProvider
    IResourceProvider catalogProvider = null;
    foreach (IResourceProvider provider in addressables.ResourceManager.ResourceProviders)
    {
        if (provider is ContentCatalogProvider)
        {
            catalogProvider = provider;
            break;
        }
    }

    // 创建一个加载依赖项的Operation
    var dependencies = addressables.ResourceManager.CreateGroupOperation<string>(loc.Dependencies, true);
    // 初始化加载ContentCatalogData的Operation,随之启动它
    catalogOp.Init(addressables.ResourceManager, catalogProvider, loc, dependencies, true);
    var catalogHandle = addressables.ResourceManager.StartOperation(catalogOp, dependencies);
    dependencies.Release();

    // 创建一个链式操作,在ContentCatalogData加载完毕之后调用InitializationOperation.OnCatalogDataLoaded进行解析
    // 实际返回的是这个链式操作
    var chainOp = addressables.ResourceManager.CreateChainOperation(catalogHandle, res => OnCatalogDataLoaded(addressables, res, providerSuffix, remoteHashLocation));
    return chainOp;
}

看到这里有人可能会有疑问,为什么不像加载一样调用[color=blue]ResourceManager.ProvideResource[/color]来加载[color=red]ContentCatalogData[/color]呢?原因很简单,[color=blue]ResourceManager.ProvideResource[/color]需要依赖于[color=red]ContentCatalogData[/color]里的数据来查找ResourceLocation,而这时候[color=red]ContentCatalogData[/color]还没加载呢。
上面代码的最后一步,就是调用[color=blue]InitializationOperation.OnCatalogDataLoaded[/color]来处理加载完毕的[color=red]ContentCatalogData[/color]:

static AsyncOperationHandle<IResourceLocator> OnCatalogDataLoaded(AddressablesImpl addressables, AsyncOperationHandle<ContentCatalogData> op, string providerSuffix, IResourceLocation remoteHashLocation)
{
    // 获取结果并且释放异步句柄
    var data = op.Result;
    addressables.Release(op);
    if (data == null)
    {
        // 加载失败
        var opException = op.OperationException != null ? new Exception("Failed to load content catalog.", op.OperationException) : new Exception("Failed to load content catalog.");
        return addressables.ResourceManager.CreateCompletedOperationWithException<IResourceLocator>(null, opException);
    }
    else
    {
        // ContentCatalogData.ResourceProviderData里保存了所需要的ResourceProvider的信息
        // InitializationOperation.LoadProvider函数负责创建它们并添加到ResourceManager.ResourceProviders
        // LoadProvider代码见后
        if (data.ResourceProviderData != null)
            foreach (var providerData in data.ResourceProviderData)
                LoadProvider(addressables, providerData, providerSuffix);

        // 创建InstanceProvider和SceneProvider
        if (addressables.InstanceProvider == null)
        {
            var prov = data.InstanceProviderData.CreateInstance<IInstanceProvider>();
            if (prov != null)
                addressables.InstanceProvider = prov;
        }
        if (addressables.SceneProvider == null)
        {
            var prov = data.SceneProviderData.CreateInstance<ISceneProvider>();
            if (prov != null)
                addressables.SceneProvider = prov;
        }

        // 作用未知(猜测是用于从remote更新?)
        if (remoteHashLocation != null)
            data.location.Dependencies[(int)ContentCatalogProvider.DependencyHashIndex.Remote] = remoteHashLocation;

        // 用ContentCatalogData中记录的资源地址创建ResourceLocator
        ResourceLocationMap locMap = data.CreateCustomLocator(data.location.PrimaryKey, providerSuffix);
        addressables.AddResourceLocator(locMap, data.localHash, data.location);
        addressables.AddResourceLocator(new DynamicResourceLocator(addressables));
        // ContentCatalogData处理完毕
        return addressables.ResourceManager.CreateCompletedOperation<IResourceLocator>(locMap, string.Empty);
    }
}

static void LoadProvider(AddressablesImpl addressables, ObjectInitializationData providerData, string providerSuffix)
{
    // providerData上有id和type两个字段,不同type的类有可能被标记为同一个id
    // 同一个id的Provider只能有一个实例,但是传入了providerSuffix的话,那么就算id相同也会被添加
    //don't add providers that have the same id...
    var indexOfExistingProvider = -1;
    var newProviderId = string.IsNullOrEmpty(providerSuffix) ? providerData.Id : (providerData.Id + providerSuffix);
    for (int i = 0; i < addressables.ResourceManager.ResourceProviders.Count; i++)
    {
        var rp = addressables.ResourceManager.ResourceProviders[i];
        if (rp.ProviderId == newProviderId)
        {
            indexOfExistingProvider = i;
            break;
        }
    }

    // 存在同id的Provider,且providerSuffix为空,则不添加
    //if not re-initializing, just use the old provider
    if (indexOfExistingProvider >= 0 && string.IsNullOrEmpty(providerSuffix))
        return;

    var provider = providerData.CreateInstance<IResourceProvider>(newProviderId);
    if (provider != null)
    {
        // 这里的判断条件感觉跟上面的冲突了,也就是永远走不到else分支
        // 因为else的进入条件就是 indexOfExistingProvider >= 0 && string.IsNullOrEmpty(providerSuffix)
        if (indexOfExistingProvider < 0 || !string.IsNullOrEmpty(providerSuffix))
        {
            Addressables.LogFormat("Addressables - added provider {0} with id {1}.", provider, provider.ProviderId);
            addressables.ResourceManager.ResourceProviders.Add(provider);
        }
        else
        {
            Addressables.LogFormat("Addressables - replacing provider {0} at index {1}.", provider, indexOfExistingProvider);
            addressables.ResourceManager.ResourceProviders[indexOfExistingProvider] = provider;
        }
    }
    else
    {
        Addressables.LogWarningFormat("Addressables - Unable to load resource provider from {0}.", providerData);
    }
}

到这里,Addressables的初始化操作就全部结束了。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注