第11章 Addressables架构解析 Addressables系统架构图 Unity Addressables系统是一个复杂的资源管理系统,其架构设计遵循了模块化、可扩展和高性能的原则。整个系统由多个核心组件协同工作,形成了一个完整的资源加载和管理生态系统。
核心架构组件 Addressables系统的主要架构可以分为以下几个核心组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ┌─────────────────────────────────────────────────────────────┐ │ Addressables System │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │ │ Addressables │ │ ResourceManager │ │ Resource │ │ │ │ API │ │ │ │ Locator │ │ │ └─────────────────┘ └─────────────────┘ └─────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │ │ Address │ │ AsyncOperation │ │ Location │ │ │ │ Resolution │ │ Handles │ │ Data │ │ │ └─────────────────┘ └─────────────────┘ └─────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │ │ Resource │ │ Provider │ │ Catalog │ │ │ │ Providers │ │ System │ │ Management │ │ │ └─────────────────┘ └─────────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘
架构层次详解 1. 应用层(Application Layer)
Addressables API : 提供给开发者的公共接口,包括LoadAssetAsync、Release等方法
用户代码 : 游戏逻辑代码,调用Addressables API进行资源管理
2. 服务层(Service Layer)
ResourceManager : 核心资源管理器,负责协调资源加载、缓存和释放
ResourceLocator : 资源定位器,根据地址查找资源位置信息
AsyncOperationHandles : 异步操作句柄系统,管理加载操作的生命周期
3. 执行层(Execution Layer)
Resource Providers : 资源提供者系统,实际执行资源加载操作
Location System : 位置管理系统,管理资源的物理位置信息
Catalog System : 目录管理系统,维护资源地址到位置的映射
系统架构特点 Addressables系统架构具有以下重要特点:
模块化设计 : 各组件职责明确,便于维护和扩展
可扩展性 : 支持自定义Provider和Location类型
异步优先 : 所有资源操作都支持异步执行
缓存机制 : 内置多级缓存系统
生命周期管理 : 完整的资源引用计数和生命周期管理
核心模块关系(ResourceManager、ResourceLocator等) ResourceManager详解 ResourceManager是Addressables系统的核心引擎,负责协调所有资源操作。它是一个单例模式的管理器,提供了统一的资源操作接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.ResourceManagement;using UnityEngine.ResourceManagement.AsyncOperations;using UnityEngine.ResourceManagement.ResourceLocations;using UnityEngine.ResourceManagement.ResourceProviders;using UnityEngine.ResourceManagement.Util;public class ResourceManagerArchitecture : MonoBehaviour { [Header("ResourceManager配置" ) ] public int operationCacheSize = 1000 ; public float timeoutDelay = 60f ; public bool enableDiagnostics = true ; private ResourceManager resourceManager; private Dictionary<string , IResourceLocation> locationCache = new Dictionary<string , IResourceLocation>(); private List<AsyncOperationHandle> activeOperations = new List<AsyncOperationHandle>(); void Start () { InitializeResourceManager(); } private void InitializeResourceManager () { resourceManager = new ResourceManager(); resourceManager.TimeoutDeltaTime = timeoutDelay; resourceManager.DiagnosticLogging = enableDiagnostics; Debug.Log("ResourceManager初始化完成" ); } public AsyncOperationHandle <TObject > SimulateLoad <TObject >(string locationName ) where TObject : class { IResourceLocation location = ResolveLocation(locationName); if (location == null ) { Debug.LogError($"无法解析位置: {locationName} " ); return default (AsyncOperationHandle<TObject>); } IResourceProvider provider = GetResourceProvider(location); if (provider == null ) { Debug.LogError($"无法找到资源提供者: {locationName} " ); return default (AsyncOperationHandle<TObject>); } var operation = resourceManager.CreateOperation<TObject>(provider, location, typeof (TObject)); activeOperations.Add(operation); resourceManager.StartOperation(operation, location); Debug.Log($"开始加载资源: {locationName} " ); return operation; } private IResourceLocation ResolveLocation (string locationName ) { if (locationCache.ContainsKey(locationName)) { return locationCache[locationName]; } var mockLocation = new MockResourceLocation(locationName); locationCache[locationName] = mockLocation; return mockLocation; } private IResourceProvider GetResourceProvider (IResourceLocation location ) { string providerId = location.Provider; return new MockResourceProvider(providerId); } public string GetResourceManagerStats () { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== ResourceManager统计 ===" ); stats.AppendLine($"活跃操作数: {activeOperations.Count} " ); stats.AppendLine($"位置缓存数: {locationCache.Count} " ); stats.AppendLine($"操作缓存大小: {operationCacheSize} " ); stats.AppendLine($"超时延迟: {timeoutDelay} s" ); stats.AppendLine($"诊断日志: {enableDiagnostics} " ); return stats.ToString(); } public void CleanupResourceManager () { foreach (var handle in activeOperations) { resourceManager.Release(handle); } activeOperations.Clear(); locationCache.Clear(); Debug.Log("ResourceManager清理完成" ); } void OnDestroy () { CleanupResourceManager(); } } [System.Serializable ] public class MockResourceLocation : IResourceLocation { public string InternalId { get ; set ; } public string ProviderId { get ; set ; } public IList<IResourceLocation> Dependencies { get ; set ; } public object Data { get ; set ; } public string PrimaryKey { get ; set ; } public MockResourceLocation (string key ) { PrimaryKey = key; InternalId = key; ProviderId = "MockProvider" ; Dependencies = new List<IResourceLocation>(); } public bool HasKey (string key ) { return PrimaryKey == key; } public bool Matches (string key ) { return PrimaryKey == key; } public T GetData <T >() { return (T)Data; } } public class MockResourceProvider : IResourceProvider { public string ProviderId { get ; private set ; } public MockResourceProvider (string providerId ) { ProviderId = providerId; } public void Provide (ProvideHandle provideHandle ) { string locationName = provideHandle.Location.PrimaryKey; System.Threading.Tasks.Task.Delay(100 ).ContinueWith(_ => { provideHandle.Complete(locationName, true , null ); }); } public void Release (IResourceLocation location, object asset ) { Debug.Log($"释放资源: {location.PrimaryKey} " ); } }
ResourceLocator详解 ResourceLocator是Addressables系统中负责资源定位的核心组件,它维护了资源地址到位置信息的映射关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.ResourceManagement.ResourceLocations;public class ResourceLocatorArchitecture : MonoBehaviour { [Header("ResourceLocator配置" ) ] public string locatorId = "DefaultLocator" ; public int maxCacheSize = 1000 ; private Dictionary<string , IResourceLocation> locationMap = new Dictionary<string , IResourceLocation>(); private Dictionary<string , IList<IResourceLocation>> dependencyMap = new Dictionary<string , IList<IResourceLocation>>(); private Dictionary<string , string > addressToKeyMap = new Dictionary<string , string >(); private List<string > cachedKeys = new List<string >(); void Start () { InitializeResourceLocator(); } private void InitializeResourceLocator () { Debug.Log($"初始化ResourceLocator: {locatorId} " ); LoadMockCatalogData(); } private void LoadMockCatalogData () { var mockLocations = new [] { new MockResourceLocation("Assets/Prefabs/Player.prefab" ) { ProviderId = "PrefabProvider" }, new MockResourceLocation("Assets/Textures/UI/Background.png" ) { ProviderId = "TextureProvider" }, new MockResourceLocation("Assets/Audio/Music/Battle.mp3" ) { ProviderId = "AudioProvider" } }; foreach (var location in mockLocations) { AddLocation(location); } Debug.Log($"加载了 {mockLocations.Length} 个模拟位置" ); } public void AddLocation (IResourceLocation location ) { if (locationMap.ContainsKey(location.PrimaryKey)) { Debug.LogWarning($"位置已存在: {location.PrimaryKey} " ); return ; } locationMap[location.PrimaryKey] = location; addressToKeyMap[location.PrimaryKey] = location.PrimaryKey; if (cachedKeys.Count >= maxCacheSize) { string oldestKey = cachedKeys[0 ]; cachedKeys.RemoveAt(0 ); locationMap.Remove(oldestKey); addressToKeyMap.Remove(oldestKey); } cachedKeys.Add(location.PrimaryKey); Debug.Log($"添加位置: {location.PrimaryKey} " ); } public IResourceLocation Locate (string key ) { if (locationMap.ContainsKey(key)) { return locationMap[key]; } if (addressToKeyMap.ContainsKey(key)) { string actualKey = addressToKeyMap[key]; if (locationMap.ContainsKey(actualKey)) { return locationMap[actualKey]; } } Debug.LogWarning($"未找到位置: {key} " ); return null ; } public bool Locate (string key, List<IResourceLocation> locations ) { var location = Locate(key); if (location != null ) { locations.Add(location); return true ; } return false ; } public bool Exists (string key, Type resourceType ) { return locationMap.ContainsKey(key); } public IList <IResourceLocation > GetDependencies (string key ) { if (dependencyMap.ContainsKey(key)) { return dependencyMap[key]; } return new List<IResourceLocation>(); } public void SetDependencies (string key, IList<IResourceLocation> dependencies ) { dependencyMap[key] = dependencies; } public string [] GetAllKeys () { var keys = new string [locationMap.Count]; locationMap.Keys.CopyTo(keys, 0 ); return keys; } public string GetLocatorStats () { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== ResourceLocator统计 ===" ); stats.AppendLine($"位置数量: {locationMap.Count} " ); stats.AppendLine($"依赖关系数: {dependencyMap.Count} " ); stats.AppendLine($"缓存键数: {cachedKeys.Count} " ); stats.AppendLine($"最大缓存大小: {maxCacheSize} " ); stats.AppendLine($"定位器ID: {locatorId} " ); return stats.ToString(); } public void CleanupLocator () { locationMap.Clear(); dependencyMap.Clear(); addressToKeyMap.Clear(); cachedKeys.Clear(); Debug.Log("ResourceLocator清理完成" ); } void OnDestroy () { CleanupLocator(); } }
ResourceManager与ResourceLocator的协作 ResourceManager和ResourceLocator之间存在紧密的协作关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.ResourceManagement;using UnityEngine.ResourceManagement.AsyncOperations;using UnityEngine.ResourceManagement.ResourceLocations;public class ResourceManagerLocatorIntegration : MonoBehaviour { [Header("集成配置" ) ] public bool enableCaching = true ; public float cacheTimeout = 300f ; private ResourceManager resourceManager; private ResourceLocatorArchitecture resourceLocator; private Dictionary<string , CachedLocation> locationCache = new Dictionary<string , CachedLocation>(); [System.Serializable ] public class CachedLocation { public IResourceLocation location; public DateTime cacheTime; public float timeout; } void Start () { InitializeIntegration(); } private void InitializeIntegration () { resourceManager = new ResourceManager(); resourceLocator = GetComponent<ResourceLocatorArchitecture>(); if (resourceLocator == null ) { resourceLocator = gameObject.AddComponent<ResourceLocatorArchitecture>(); } Debug.Log("ResourceManager与ResourceLocator集成初始化完成" ); } public AsyncOperationHandle <TObject > LoadAssetWithIntegration <TObject >(string address ) where TObject : class { if (enableCaching && locationCache.ContainsKey(address)) { var cached = locationCache[address]; if ((DateTime.Now - cached.cacheTime).TotalSeconds < cached.timeout) { return LoadFromCachedLocation<TObject>(cached.location); } else { locationCache.Remove(address); } } var location = resourceLocator.Locate(address); if (location == null ) { Debug.LogError($"无法定位资源: {address} " ); return default (AsyncOperationHandle<TObject>); } if (enableCaching) { locationCache[address] = new CachedLocation { location = location, cacheTime = DateTime.Now, timeout = cacheTimeout }; } return LoadFromLocation<TObject>(location); } private AsyncOperationHandle <TObject > LoadFromCachedLocation <TObject >(IResourceLocation location ) where TObject : class { var provider = GetProviderForLocation(location); if (provider == null ) { Debug.LogError($"无法获取提供者: {location.PrimaryKey} " ); return default (AsyncOperationHandle<TObject>); } var operation = resourceManager.CreateOperation<TObject>(provider, location, typeof (TObject)); resourceManager.StartOperation(operation, location); Debug.Log($"从缓存位置加载: {location.PrimaryKey} " ); return operation; } private AsyncOperationHandle <TObject > LoadFromLocation <TObject >(IResourceLocation location ) where TObject : class { var provider = GetProviderForLocation(location); if (provider == null ) { Debug.LogError($"无法获取提供者: {location.PrimaryKey} " ); return default (AsyncOperationHandle<TObject>); } var operation = resourceManager.CreateOperation<TObject>(provider, location, typeof (TObject)); resourceManager.StartOperation(operation, location); Debug.Log($"从位置加载: {location.PrimaryKey} " ); return operation; } private UnityEngine.ResourceManagement.ResourceProviders.IResourceProvider GetProviderForLocation (IResourceLocation location ) { return new MockResourceProvider(location.ProviderId); } public string GetIntegrationStats () { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== ResourceManager与ResourceLocator集成统计 ===" ); stats.AppendLine(resourceManager != null ? $"ResourceManager已初始化" : "ResourceManager未初始化" ); stats.AppendLine(resourceLocator != null ? $"ResourceLocator已初始化" : "ResourceLocator未初始化" ); stats.AppendLine($"位置缓存数: {locationCache.Count} " ); stats.AppendLine($"启用缓存: {enableCaching} " ); stats.AppendLine($"缓存超时: {cacheTimeout} s" ); if (resourceLocator != null ) { stats.AppendLine(); stats.AppendLine(resourceLocator.GetLocatorStats()); } return stats.ToString(); } public void CleanupIntegration () { locationCache.Clear(); if (resourceManager != null ) { resourceManager.Dispose(); } Debug.Log("集成系统清理完成" ); } void OnDestroy () { CleanupIntegration(); } }
地址解析流程 地址解析机制详解 Addressables的地址解析是整个系统的核心流程之一,它将开发者提供的地址字符串转换为实际的资源位置信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.ResourceManagement.ResourceLocations;public class AddressResolutionSystem : MonoBehaviour { [Header("地址解析配置" ) ] public bool enableAddressCaching = true ; public bool enablePatternMatching = true ; public int maxAddressCacheSize = 500 ; private Dictionary<string , ResolvedAddress> addressCache = new Dictionary<string , ResolvedAddress>(); private List<AddressPattern> addressPatterns = new List<AddressPattern>(); private ResourceLocatorArchitecture resourceLocator; [System.Serializable ] public class ResolvedAddress { public string originalAddress; public string resolvedKey; public IResourceLocation location; public DateTime resolveTime; public List<IResourceLocation> dependencies; } [System.Serializable ] public class AddressPattern { public string pattern; public string replacement; public int priority; } void Start () { resourceLocator = GetComponent<ResourceLocatorArchitecture>(); if (resourceLocator == null ) { resourceLocator = gameObject.AddComponent<ResourceLocatorArchitecture>(); } InitializeAddressPatterns(); } private void InitializeAddressPatterns () { addressPatterns.Add(new AddressPattern { pattern = "Prefabs/*" , replacement = "Assets/Prefabs/{0}.prefab" , priority = 1 }); addressPatterns.Add(new AddressPattern { pattern = "Textures/*" , replacement = "Assets/Textures/{0}.png" , priority = 2 }); addressPatterns.Add(new AddressPattern { pattern = "Audio/*" , replacement = "Assets/Audio/{0}.mp3" , priority = 3 }); Debug.Log($"初始化了 {addressPatterns.Count} 个地址模式" ); } public ResolvedAddress ResolveAddress (string address ) { if (enableAddressCaching && addressCache.ContainsKey(address)) { var cached = addressCache[address]; Debug.Log($"从缓存解析地址: {address} -> {cached.resolvedKey} " ); return cached; } string resolvedKey = address; if (enablePatternMatching) { resolvedKey = ApplyAddressPatterns(address); } var location = resourceLocator.Locate(resolvedKey); if (location == null ) { Debug.LogError($"无法解析地址: {address} -> {resolvedKey} " ); return null ; } var resolvedAddress = new ResolvedAddress { originalAddress = address, resolvedKey = resolvedKey, location = location, resolveTime = DateTime.Now, dependencies = new List<IResourceLocation>(resourceLocator.GetDependencies(resolvedKey)) }; if (enableAddressCaching) { CacheAddress(address, resolvedAddress); } Debug.Log($"解析地址: {address} -> {resolvedKey} " ); return resolvedAddress; } private string ApplyAddressPatterns (string address ) { var sortedPatterns = new List<AddressPattern>(addressPatterns); sortedPatterns.Sort((a, b) => a.priority.CompareTo(b.priority)); foreach (var pattern in sortedPatterns) { if (MatchPattern(address, pattern.pattern)) { string patternWithoutWildcard = pattern.pattern.Replace("*" , "" ); if (address.StartsWith(patternWithoutWildcard)) { string suffix = address.Substring(patternWithoutWildcard.Length); return pattern.replacement.Replace("{0}" , suffix); } } } return address; } private bool MatchPattern (string address, string pattern ) { if (pattern.EndsWith("*" )) { string prefix = pattern.Substring(0 , pattern.Length - 1 ); return address.StartsWith(prefix); } return address == pattern; } private void CacheAddress (string address, ResolvedAddress resolved ) { if (addressCache.Count >= maxAddressCacheSize) { var oldestKey = "" ; DateTime oldestTime = DateTime.MaxValue; foreach (var pair in addressCache) { if (pair.Value.resolveTime < oldestTime) { oldestTime = pair.Value.resolveTime; oldestKey = pair.Key; } } if (!string .IsNullOrEmpty(oldestKey)) { addressCache.Remove(oldestKey); } } addressCache[address] = resolved; } public List<ResolvedAddress> ResolveAddresses (string [] addresses ) { var results = new List<ResolvedAddress>(); foreach (string address in addresses) { var resolved = ResolveAddress(address); if (resolved != null ) { results.Add(resolved); } } return results; } public void PreResolveAddresses (string [] addresses ) { foreach (string address in addresses) { ResolveAddress(address); } Debug.Log($"预解析了 {addresses.Length} 个地址" ); } public string GetResolutionStats () { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== 地址解析统计 ===" ); stats.AppendLine($"缓存地址数: {addressCache.Count} " ); stats.AppendLine($"地址模式数: {addressPatterns.Count} " ); stats.AppendLine($"启用缓存: {enableAddressCaching} " ); stats.AppendLine($"启用模式匹配: {enablePatternMatching} " ); stats.AppendLine($"最大缓存大小: {maxAddressCacheSize} " ); if (addressCache.Count > 0 ) { stats.AppendLine(); stats.AppendLine("缓存详情:" ); int count = 0 ; foreach (var pair in addressCache) { if (count++ < 5 ) { stats.AppendLine($" {pair.Key} -> {pair.Value.resolvedKey} " ); } } if (addressCache.Count > 5 ) { stats.AppendLine($" ... 还有 {addressCache.Count - 5 } 个" ); } } return stats.ToString(); } public void ClearAddressCache () { addressCache.Clear(); Debug.Log("地址缓存已清理" ); } void OnDestroy () { ClearAddressCache(); } }
Provider系统详解 IResourceProvider接口 IResourceProvider是Addressables系统中负责实际资源加载的核心接口,所有资源加载操作最终都通过Provider来执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.ResourceManagement;using UnityEngine.ResourceManagement.ResourceLocations;using UnityEngine.ResourceManagement.ResourceProviders;public class ProviderSystemArchitecture : MonoBehaviour { [Header("Provider系统配置" ) ] public bool enableProviderCaching = true ; public int maxProviderCacheSize = 100 ; private Dictionary<string , IResourceProvider> providerCache = new Dictionary<string , IResourceProvider>(); private Dictionary<string , Type> providerTypeMap = new Dictionary<string , Type>(); private List<ActiveProviderOperation> activeOperations = new List<ActiveProviderOperation>(); [System.Serializable ] public class ActiveProviderOperation { public string providerId; public string locationKey; public DateTime startTime; public bool isComplete; } void Start () { InitializeProviderTypes(); } private void InitializeProviderTypes () { providerTypeMap["PrefabProvider" ] = typeof (PrefabProvider); providerTypeMap["TextureProvider" ] = typeof (TextureProvider); providerTypeMap["AudioProvider" ] = typeof (AudioProvider); providerTypeMap["SceneProvider" ] = typeof (SceneProvider); Debug.Log($"注册了 {providerTypeMap.Count} 个Provider类型" ); } public IResourceProvider GetProvider (string providerId ) { if (enableProviderCaching && providerCache.ContainsKey(providerId)) { return providerCache[providerId]; } IResourceProvider provider = CreateProvider(providerId); if (provider != null ) { if (enableProviderCaching) { CacheProvider(providerId, provider); } } return provider; } private IResourceProvider CreateProvider (string providerId ) { if (providerTypeMap.ContainsKey(providerId)) { Type providerType = providerTypeMap[providerId]; return (IResourceProvider)Activator.CreateInstance(providerType); } return new GenericResourceProvider(providerId); } private void CacheProvider (string providerId, IResourceProvider provider ) { if (providerCache.Count >= maxProviderCacheSize) { var firstKey = new List<string >(providerCache.Keys)[0 ]; providerCache.Remove(firstKey); } providerCache[providerId] = provider; } public void ProvideResource (IResourceLocation location, ProvideHandle provideHandle ) { string providerId = location.Provider; var provider = GetProvider(providerId); if (provider != null ) { var operation = new ActiveProviderOperation { providerId = providerId, locationKey = location.PrimaryKey, startTime = DateTime.Now, isComplete = false }; activeOperations.Add(operation); provider.Provide(provideHandle); Debug.Log($"开始提供资源: {location.PrimaryKey} 通过 {providerId} " ); } else { Debug.LogError($"无法找到Provider: {providerId} " ); provideHandle.Complete(null , false , new System.Exception($"Provider not found: {providerId} " )); } } public void ReleaseResource (IResourceLocation location, object asset ) { string providerId = location.Provider; var provider = GetProvider(providerId); if (provider != null ) { provider.Release(location, asset); Debug.Log($"释放资源: {location.PrimaryKey} " ); } else { Debug.LogError($"无法释放资源,Provider不存在: {providerId} " ); } } public string GetProviderStats () { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== Provider系统统计 ===" ); stats.AppendLine($"注册Provider类型数: {providerTypeMap.Count} " ); stats.AppendLine($"缓存Provider数: {providerCache.Count} " ); stats.AppendLine($"活跃操作数: {activeOperations.Count} " ); stats.AppendLine($"启用缓存: {enableProviderCaching} " ); stats.AppendLine($"最大缓存大小: {maxProviderCacheSize} " ); if (providerCache.Count > 0 ) { stats.AppendLine(); stats.AppendLine("缓存Provider:" ); foreach (var pair in providerCache) { stats.AppendLine($" {pair.Key} : {pair.Value.GetType().Name} " ); } } if (activeOperations.Count > 0 ) { stats.AppendLine(); stats.AppendLine("活跃操作:" ); foreach (var op in activeOperations) { float duration = (float )(DateTime.Now - op.startTime).TotalSeconds; stats.AppendLine($" {op.providerId} -> {op.locationKey} (运行 {duration:F2} s)" ); } } return stats.ToString(); } public void RegisterProviderType (string providerId, Type providerType ) { if (!typeof (IResourceProvider).IsAssignableFrom(providerType)) { Debug.LogError($"类型 {providerType.Name} 不实现 IResourceProvider 接口" ); return ; } providerTypeMap[providerId] = providerType; Debug.Log($"注册Provider类型: {providerId} -> {providerType.Name} " ); } public void CleanupProviders () { providerCache.Clear(); activeOperations.Clear(); Debug.Log("Provider系统清理完成" ); } void OnDestroy () { CleanupProviders(); } } public class PrefabProvider : IResourceProvider { public string ProviderId => "PrefabProvider" ; public void Provide (ProvideHandle provideHandle ) { string locationName = provideHandle.Location.PrimaryKey; System.Threading.Tasks.Task.Delay(50 ).ContinueWith(_ => { var mockPrefab = new GameObject(locationName); provideHandle.Complete(mockPrefab, true , null ); }); } public void Release (IResourceLocation location, object asset ) { if (asset is GameObject go) { GameObject.Destroy(go); } } } public class TextureProvider : IResourceProvider { public string ProviderId => "TextureProvider" ; public void Provide (ProvideHandle provideHandle ) { string locationName = provideHandle.Location.PrimaryKey; System.Threading.Tasks.Task.Delay(80 ).ContinueWith(_ => { var mockTexture = new Texture2D(128 , 128 ); provideHandle.Complete(mockTexture, true , null ); }); } public void Release (IResourceLocation location, object asset ) { if (asset is Texture2D texture) { UnityEngine.Object.Destroy(texture); } } } public class AudioProvider : IResourceProvider { public string ProviderId => "AudioProvider" ; public void Provide (ProvideHandle provideHandle ) { string locationName = provideHandle.Location.PrimaryKey; System.Threading.Tasks.Task.Delay(100 ).ContinueWith(_ => { var mockAudio = new AudioClip(); provideHandle.Complete(mockAudio, true , null ); }); } public void Release (IResourceLocation location, object asset ) { if (asset is AudioClip audioClip) { UnityEngine.Object.Destroy(audioClip); } } } public class SceneProvider : IResourceProvider { public string ProviderId => "SceneProvider" ; public void Provide (ProvideHandle provideHandle ) { string locationName = provideHandle.Location.PrimaryKey; System.Threading.Tasks.Task.Delay(200 ).ContinueWith(_ => { provideHandle.Complete(locationName, true , null ); }); } public void Release (IResourceLocation location, object asset ) { Debug.Log($"场景释放请求: {location.PrimaryKey} " ); } } public class GenericResourceProvider : IResourceProvider { public string ProviderId { get ; private set ; } public GenericResourceProvider (string providerId ) { ProviderId = providerId; } public void Provide (ProvideHandle provideHandle ) { string locationName = provideHandle.Location.PrimaryKey; System.Threading.Tasks.Task.Delay(30 ).ContinueWith(_ => { provideHandle.Complete(locationName, true , null ); }); } public void Release (IResourceLocation location, object asset ) { if (asset is UnityEngine.Object unityObj) { UnityEngine.Object.Destroy(unityObj); } } }
Handle机制深入 AsyncOperationHandle详解 AsyncOperationHandle是Addressables系统中管理异步操作的核心机制,它提供了统一的异步操作接口和生命周期管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.ResourceManagement;using UnityEngine.ResourceManagement.AsyncOperations;public class HandleMechanismDeepDive : MonoBehaviour { [Header("Handle机制配置" ) ] public bool enableHandleTracking = true ; public float handleTimeout = 60f ; public bool enableReferenceCounting = true ; private Dictionary<string , TrackedHandle> trackedHandles = new Dictionary<string , TrackedHandle>(); private int handleCounter = 0 ; [System.Serializable ] public class TrackedHandle { public string handleId; public AsyncOperationHandle handle; public DateTime createTime; public int referenceCount; public string resourceAddress; public AsyncOperationStatus status; public System.Exception error; } void Start () { Debug.Log("Handle机制深度解析系统启动" ); } public AsyncOperationHandle CreateTrackedHandle (string resourceAddress ) { string handleId = $"Handle_{++handleCounter:D4} " ; var trackedHandle = new TrackedHandle { handleId = handleId, resourceAddress = resourceAddress, createTime = DateTime.Now, referenceCount = enableReferenceCounting ? 1 : 0 , status = AsyncOperationStatus.None }; if (enableHandleTracking) { trackedHandles[handleId] = trackedHandle; } Debug.Log($"创建句柄: {handleId} -> {resourceAddress} " ); return CreateMockHandle(handleId, resourceAddress); } private AsyncOperationHandle CreateMockHandle (string handleId, string resourceAddress ) { var handle = new AsyncOperationHandle(); System.Threading.Tasks.Task.Delay(100 ).ContinueWith(_ => { UpdateHandleStatus(handleId, AsyncOperationStatus.Succeeded); }); return handle; } private void UpdateHandleStatus (string handleId, AsyncOperationStatus status ) { if (trackedHandles.ContainsKey(handleId)) { var tracked = trackedHandles[handleId]; tracked.status = status; if (status == AsyncOperationStatus.Failed) { tracked.error = new System.Exception($"操作失败: {tracked.resourceAddress} " ); } Debug.Log($"句柄状态更新: {handleId} -> {status} " ); } } public bool RetainHandle (string handleId ) { if (trackedHandles.ContainsKey(handleId)) { var tracked = trackedHandles[handleId]; tracked.referenceCount++; Debug.Log($"句柄引用增加: {handleId} , 当前引用: {tracked.referenceCount} " ); return true ; } Debug.LogWarning($"句柄不存在: {handleId} " ); return false ; } public bool ReleaseHandle (string handleId ) { if (trackedHandles.ContainsKey(handleId)) { var tracked = trackedHandles[handleId]; tracked.referenceCount--; Debug.Log($"句柄引用减少: {handleId} , 当前引用: {tracked.referenceCount} " ); if (enableReferenceCounting && tracked.referenceCount <= 0 ) { CleanupHandle(handleId); return true ; } return true ; } Debug.LogWarning($"句柄不存在: {handleId} " ); return false ; } private void CleanupHandle (string handleId ) { if (trackedHandles.ContainsKey(handleId)) { var tracked = trackedHandles[handleId]; Debug.Log($"清理句柄: {handleId} , 资源: {tracked.resourceAddress} " ); trackedHandles.Remove(handleId); } } public string GetHandleInfo (string handleId ) { if (trackedHandles.ContainsKey(handleId)) { var tracked = trackedHandles[handleId]; var info = new System.Text.StringBuilder(); info.AppendLine($"=== 句柄信息: {handleId} ===" ); info.AppendLine($"资源地址: {tracked.resourceAddress} " ); info.AppendLine($"创建时间: {tracked.createTime:HH:mm:ss.fff} " ); info.AppendLine($"引用计数: {tracked.referenceCount} " ); info.AppendLine($"状态: {tracked.status} " ); info.AppendLine($"运行时间: {(DateTime.Now - tracked.createTime).TotalSeconds:F2} s" ); if (tracked.error != null ) { info.AppendLine($"错误: {tracked.error.Message} " ); } return info.ToString(); } return $"句柄不存在: {handleId} " ; } public string GetAllHandleStats () { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== 所有句柄统计 ===" ); stats.AppendLine($"跟踪句柄数: {trackedHandles.Count} " ); stats.AppendLine($"总句柄计数: {handleCounter} " ); stats.AppendLine($"启用引用计数: {enableReferenceCounting} " ); stats.AppendLine($"启用跟踪: {enableHandleTracking} " ); stats.AppendLine($"超时时间: {handleTimeout} s" ); if (trackedHandles.Count > 0 ) { stats.AppendLine(); stats.AppendLine("句柄详情:" ); int count = 0 ; foreach (var pair in trackedHandles) { if (count++ < 10 ) { var tracked = pair.Value; float age = (float )(DateTime.Now - tracked.createTime).TotalSeconds; stats.AppendLine($" {pair.Key} : {tracked.resourceAddress} " + $"(引用:{tracked.referenceCount} , 状态:{tracked.status} , 年龄:{age:F1} s)" ); } } if (trackedHandles.Count > 10 ) { stats.AppendLine($" ... 还有 {trackedHandles.Count - 10 } 个" ); } } return stats.ToString(); } public void CheckTimeoutHandles () { var timeoutHandles = new List<string >(); foreach (var pair in trackedHandles) { var tracked = pair.Value; float age = (float )(DateTime.Now - tracked.createTime).TotalSeconds; if (age > handleTimeout) { timeoutHandles.Add(pair.Key); } } foreach (string handleId in timeoutHandles) { Debug.LogWarning($"句柄超时: {handleId} " ); CleanupHandle(handleId); } if (timeoutHandles.Count > 0 ) { Debug.Log($"清理了 {timeoutHandles.Count} 个超时句柄" ); } } public void BatchOperationHandles (List<string > handleIds, string operation ) { foreach (string handleId in handleIds) { switch (operation.ToLower()) { case "retain" : RetainHandle(handleId); break ; case "release" : ReleaseHandle(handleId); break ; case "info" : Debug.Log(GetHandleInfo(handleId)); break ; } } } public void CleanupAllHandles () { var handlesToCleanup = new List<string >(trackedHandles.Keys); foreach (string handleId in handlesToCleanup) { CleanupHandle(handleId); } handleCounter = 0 ; Debug.Log($"清理了所有 {handlesToCleanup.Count} 个句柄" ); } void Update () { if (Time.frameCount % 300 == 0 ) { CheckTimeoutHandles(); } } void OnDestroy () { CleanupAllHandles(); } }
AsyncOperationHandle生命周期 AsyncOperationHandle的生命周期管理是Addressables系统中确保资源正确加载和释放的关键机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.ResourceManagement;using UnityEngine.ResourceManagement.AsyncOperations;public class HandleLifecycleManager : MonoBehaviour { [Header("生命周期配置" ) ] public float operationTimeout = 120f ; public bool enableAutoRelease = true ; public float autoReleaseDelay = 5f ; private Dictionary<string , HandleLifecycleInfo> lifecycleInfos = new Dictionary<string , HandleLifecycleInfo>(); [System.Serializable ] public class HandleLifecycleInfo { public string handleId; public AsyncOperationHandle handle; public DateTime startTime; public DateTime completeTime; public AsyncOperationStatus status; public int referenceCount; public bool autoReleaseScheduled; public System.Action onCompleted; public System.Action<System.Exception> onError; public System.Action<float > onProgress; } void Start () { Debug.Log("Handle生命周期管理系统启动" ); } public string TrackHandleLifecycle (AsyncOperationHandle handle, System.Action onCompleted = null , System.Action<System.Exception> onError = null , System.Action<float > onProgress = null ) { string handleId = $"Lifecycle_{DateTime.Now:HHmmss} _{UnityEngine.Random.Range(1000 , 9999 )} " ; var lifecycleInfo = new HandleLifecycleInfo { handleId = handleId, handle = handle, startTime = DateTime.Now, status = AsyncOperationStatus.None, referenceCount = 1 , onCompleted = onCompleted, onError = onError, onProgress = onProgress }; lifecycleInfos[handleId] = lifecycleInfo; SetupHandleListeners(lifecycleInfo); Debug.Log($"开始跟踪句柄生命周期: {handleId} " ); return handleId; } private void SetupHandleListeners (HandleLifecycleInfo lifecycleInfo ) { lifecycleInfo.handle.Completed += (op) => { lifecycleInfo.completeTime = DateTime.Now; lifecycleInfo.status = op.Status; if (op.Status == AsyncOperationStatus.Succeeded) { lifecycleInfo.onCompleted?.Invoke(); Debug.Log($"句柄完成成功: {lifecycleInfo.handleId} " ); } else { lifecycleInfo.onError?.Invoke(op.OperationException); Debug.LogError($"句柄完成失败: {lifecycleInfo.handleId} , 错误: {op.OperationException?.Message} " ); } if (enableAutoRelease) { ScheduleAutoRelease(lifecycleInfo); } }; if (lifecycleInfo.onProgress != null ) { lifecycleInfo.handle.PercentCompleteChanged += (op) => { lifecycleInfo.onProgress?.Invoke(op.PercentComplete); }; } } private void ScheduleAutoRelease (HandleLifecycleInfo lifecycleInfo ) { lifecycleInfo.autoReleaseScheduled = true ; StartCoroutine(AutoReleaseCoroutine(lifecycleInfo)); } private System.Collections.IEnumerator AutoReleaseCoroutine (HandleLifecycleInfo lifecycleInfo ) { yield return new WaitForSeconds (autoReleaseDelay ) ; if (lifecycleInfos.ContainsKey(lifecycleInfo.handleId)) { ForceReleaseHandle(lifecycleInfo.handleId); } } public bool ForceReleaseHandle (string handleId ) { if (lifecycleInfos.ContainsKey(handleId)) { var lifecycleInfo = lifecycleInfos[handleId]; Debug.Log($"强制释放句柄: {handleId} " ); lifecycleInfos.Remove(handleId); return true ; } Debug.LogWarning($"句柄不存在: {handleId} " ); return false ; } public bool RetainHandleReference (string handleId ) { if (lifecycleInfos.ContainsKey(handleId)) { var lifecycleInfo = lifecycleInfos[handleId]; lifecycleInfo.referenceCount++; Debug.Log($"句柄引用增加: {handleId} , 当前: {lifecycleInfo.referenceCount} " ); return true ; } Debug.LogWarning($"句柄不存在: {handleId} " ); return false ; } public bool ReleaseHandleReference (string handleId ) { if (lifecycleInfos.ContainsKey(handleId)) { var lifecycleInfo = lifecycleInfos[handleId]; lifecycleInfo.referenceCount--; Debug.Log($"句柄引用减少: {handleId} , 当前: {lifecycleInfo.referenceCount} " ); if (lifecycleInfo.referenceCount <= 0 ) { return ForceReleaseHandle(handleId); } return true ; } Debug.LogWarning($"句柄不存在: {handleId} " ); return false ; } public string GetLifecycleStats () { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== Handle生命周期统计 ===" ); stats.AppendLine($"跟踪句柄数: {lifecycleInfos.Count} " ); stats.AppendLine($"操作超时: {operationTimeout} s" ); stats.AppendLine($"启用自动释放: {enableAutoRelease} " ); stats.AppendLine($"自动释放延迟: {autoReleaseDelay} s" ); if (lifecycleInfos.Count > 0 ) { stats.AppendLine(); stats.AppendLine("活跃句柄:" ); foreach (var pair in lifecycleInfos) { var info = pair.Value; float duration = (float )(DateTime.Now - info.startTime).TotalSeconds; string statusStr = info.status == AsyncOperationStatus.None ? "Running" : info.status.ToString(); string ageStr = info.completeTime != default (DateTime) ? $"{(float )(info.completeTime - info.startTime).TotalSeconds:F2} s" : $"{duration:F2} s" ; stats.AppendLine($" {pair.Key} : 引用{info.referenceCount} , 状态{statusStr} , 时长{ageStr} " ); } } return stats.ToString(); } public void CheckTimeoutHandles () { var timeoutHandles = new List<string >(); foreach (var pair in lifecycleInfos) { var info = pair.Value; float duration = (float )(DateTime.Now - info.startTime).TotalSeconds; if (duration > operationTimeout) { timeoutHandles.Add(pair.Key); } } foreach (string handleId in timeoutHandles) { Debug.LogWarning($"句柄超时: {handleId} " ); ForceReleaseHandle(handleId); } if (timeoutHandles.Count > 0 ) { Debug.Log($"清理了 {timeoutHandles.Count} 个超时句柄" ); } } public string GetHandleDetails (string handleId ) { if (lifecycleInfos.ContainsKey(handleId)) { var info = lifecycleInfos[handleId]; var details = new System.Text.StringBuilder(); details.AppendLine($"=== 句柄详细信息: {handleId} ===" ); details.AppendLine($"引用计数: {info.referenceCount} " ); details.AppendLine($"状态: {info.status} " ); details.AppendLine($"开始时间: {info.startTime:HH:mm:ss.fff} " ); details.AppendLine($"完成时间: {(info.completeTime != default (DateTime) ? info.completeTime.ToString("HH:mm:ss.fff" ) : "未完成" )} " ); details.AppendLine($"运行时长: {(float )(DateTime.Now - info.startTime).TotalSeconds:F2} s" ); details.AppendLine($"自动释放已安排: {info.autoReleaseScheduled} " ); return details.ToString(); } return $"句柄不存在: {handleId} " ; } void Update () { if (Time.frameCount % 600 == 0 ) { CheckTimeoutHandles(); } } void OnDestroy () { var handlesToCleanup = new List<string >(lifecycleInfos.Keys); foreach (string handleId in handlesToCleanup) { ForceReleaseHandle(handleId); } Debug.Log($"生命周期管理系统清理完成,共清理 {handlesToCleanup.Count} 个句柄" ); } }
通过本章的深入学习,您已经全面理解了Addressables系统的架构设计、核心组件关系、地址解析流程、Provider系统以及Handle机制的生命周期管理。这些知识将帮助您更好地理解和运用Addressables系统,为后续的底层原理学习打下坚实基础。