文章链接
内存管理
镜像加载和卸载
当使用可寻址资产时,确保适当的内存管理的主要方法是正确镜像加载和卸载调用。如何执行取决于您的资产类型和加载方法。但是,在所有情况下,释放方法都可以采用已加载的资产,也可以采用加载返回的操作句柄。例如,在场景创建过程中(如下所述),加载返回一个AsyncOperationHandle
资产加载
要加载资产,请使用Addressables.LoadAssetAsync(单个资产加载)或Addressables.LoadAssetsAsync(多个资产加载)。
注意:LoadAssetAsync旨在与映射到单个条目的键一起使用。如果提供的键与多个条目匹配(例如,广泛使用的标签),则该方法会将其找到的第一个匹配项加载到给定的键中。这是不确定性的,因为它可能会受到构建顺序的影响。
这会将资产加载到内存中而不实例化它。每次执行load调用时,都会为每个加载的Asset的ref-count加一。如果LoadAssetAsync使用相同的地址调用三次,则将返回三个不同的AsyncOperationHandle结构实例,所有实例都引用相同的基础操作。
该操作对应的资产的引用计数为3。如果加载成功,则结果AsyncOperationHandle结构将在.Result属性中包含Asset 。您可以使用已加载的资产通过Unity的内置实例化方法实例化,该方法不会增加Addressables引用计数。
要卸载资产,请使用Addressables.Release减少引用计数的方法。当给定资产的引用计数为零时,该资产就可以卸载了,并减少了任何依赖项的引用计数。
注意:根据现有依赖关系,资产可能会立即卸载,也可能不会立即卸载。有关更多信息,请阅读有关何时清除内存的部分。
场景加载
要加载场景,请使用Addressables.LoadSceneAsync。您可以使用此方法以Single模式加载并关闭所有打开的场景,或者Additive模式(有关更多信息,请参见有关场景模式加载的文档)。
要卸载场景,请使用Addressables.UnloadSceneAsync,或在Single模式下打开一个新的场景。
您可以使用Addressables接口(例如,Addressables.LoadSceneAsync)或使用SceneManager.LoadScene或SceneManager.LoadSceneAsync方法来打开新场景。打开一个新的场景会关闭当前场景,并适当减少引用计数。
GameObject实例化
要加载和实例化GameObject资产,请使用Addressables.InstantiateAsync。这将实例化通过指定location参数定位的预制件。可寻址对象系统将加载预制件及其依赖项,从而增加所有关联资产的引用计数。
InstantiateAsync在同一地址上调用三次将导致所有从属资产的引用计数为3。与LoadAssetAsync三次调用不同,每次InstantiateAsync调用都返回AsyncOperationHandle指向唯一操作的指针。这是因为每个的结果InstantiateAsync都是唯一的实例。您需要单独释放每个返回的AsyncOperationHandle或GameObject实例。
InstantiateAsync与其他加载调用之间的另一个区别是可选trackHandle参数。设置为false时,AsyncOperationHandle在释放实例时必须保持使用。这效率更高,但是需要更多的开发工作。
要销毁实例化的GameObject,请使用Addressables.ReleaseInstance或关闭包含实例化对象的Scene。可以在Additive或Single模式下加载(因此关闭)此场景。也可以使用Addressables或SceneManagementAPI 加载此场景。如上所述,如果设置trackHandle为false,则只能Addressables.ReleaseInstance使用句柄进行调用,而不能使用实际的GameObject进行调用。
注意:如果您调用Addressables.ReleaseInstance在不是使用Addressables的API创建的实例或在trackHandle==false下创建的实例,则系统会检测到并返回false以表明该方法无法释放指定的实例。在这种情况下,实例不会被销毁。
Addressables.InstantiateAsync有一些相关的开销,因此,如果您需要每帧实例化同一对象数百次,请考虑通过Addressables API加载,然后通过其他方法实例化。
在这种情况下,您可以调用Addressables.LoadAssetAsync,然后保存结果并调用GameObject.Instantiate()该结果。这样可以灵活地以同步方式进行调用Instantiate。缺点是可寻址系统不知道您创建了多少个实例,如果管理不当可能会导致内存问题。
例如,一个有纹理的Prefab将不再对其有效加载的纹理进行引用,从而导致渲染问题(或更糟)。由于您可能不会立即触发内存卸载(请参阅下面有关清除内存的部分),因此这类问题可能很难找到。
数据载入
不需要AsyncOperationHandle.Result释放的接口仍然需要释放操作本身。这些示例包括Addressables.LoadResourceLocationsAsync和Addressables.GetDownloadSizeAsync。它们将加载您可以访问的数据,直到释放操作为止。他们的释放通过Addressables.Release完成。
背景交互
在AsyncOperationHandle.Result字段中不返回任何内容的操作具有可选参数,以在完成时自动释放操作句柄。如果在完成操作后不再需要这些操作手柄之一,则将autoReleaseHandle参数设置为true以确保清理了操作手柄。
如果您需要在操作句柄完成后检查句柄的Status,则这种情形autoReleaseHandle应该设置为false。这些接口的示例为Addressables.DownloadDependenciesAsync和Addressables.UnloadScene。
可寻址事件查看器(The Addressables Event Viewer)
使用Addressables Event Viewer窗口来监视所有可寻址系统操作的引用计数。要访问编辑器中的窗口,选择Window > Asset Management > Addressables > Event Viewer。
重要说明:为了在事件查看器中查看数据,必须在AddressableAssetSettings对象的Inspector面板中启用Send Profiler Events设置。对Send Profiler Events的更改将反映在以下内部版本中。
这意味着,在使用Use Existing Build播放模式脚本时进入 播放模式 将使用最新生成期间设置的值。或者在使用Use Asset Database或Simulate Groups播放模式脚本时进入播放模式时会获取当前状态,因为这些播放模式脚本会在进入播放模式时重建设置数据。
事件查看器中提供了以下信息:
- 白色竖线表示发生加载请求的帧。
- 蓝色背景指示当前正在加载资产。
- 图表的绿色部分表示资产的当前引用计数。
请注意,事件查看器仅关注引用计数,而不关注内存消耗(有关更多信息,请参见下面的清除内存部分)。
在“资产”列下列出的内容中,您将看到每帧以下每一行:
- FPS:每秒帧数。
- MonoHeap:正在使用的RAM量。
- 事件计数:一帧中的事件总数。
- 实例化计数:一帧中对Addressables.InstantiateAsync的调用总数。
- 资产请求:显示一段时间内某个操作的引用计数。如果资产请求具有任何依赖关系,则会出现一个三角形,您可以单击该三角形以查看子级的请求操作。
您可以单击左箭头和右箭头以逐帧浏览帧,或单击Current跳到最新的帧。按+按钮展开一行以获取更多详细信息。
事件查看器中显示的信息与您用来创建播放模式数据的构建脚本有关。
使用事件查看器时,请避免使用Use Asset Database构建的脚本,因为它不考虑资产之间的任何共享依赖关系。请改用Simulate Groups脚本或 Use Existing Build 脚本,但是后者更适合于“事件查看器”,因为它可以更准确地监视引用计数。
将事件查看器连接到独立播放器
要将事件查看器连接到独立播放器,进入构建菜单,选择要使用的平台,并确保同时启用了Development Build和Autoconnect Profiler。接下来,通过选择Window > Analysis > Profiler来打开Unity Profiler,然后在顶部工具栏上选择要构建的平台。最后,在Build Settings window中选择Build and Run,事件查看器将自动连接并显示所选独立播放器的事件。
何时清除内存?
一个资产不再被引用(在profiler中蓝色部分的末尾表示)并不一定意味着资产已卸载。常见的适用场景涉及AssetBundle中的多个Assets 。例如:
- 您在AssetBundle(stuff)中有三个Assets(tree,tank和cow)。
- 当tree加载时,profiler显示一个引用计数给tree,和一个引用计数给stuff。
- 稍后,在tank加载时,profiler为tree和tank显一个引用计数,为stuff bundle显示两个引用计数。
- 如果释放tree,则他的ref-count变为零,蓝色条消失。
在此示例中,此时tree尚未实际卸载资产。您可以加载AssetBundle或其部分内容,但是不能部分卸载AssetBundle。stuff在捆绑包本身完全卸载之前,不会卸载任何资产。该规则的例外是引擎接口Resources.UnloadUnusedAssets。在上述情况下执行此方法将导致tree卸载。由于可寻址对象系统无法识别这些事件,因此profiler图仅反映可寻址对象的引用计数(不完全是内存的内容)。请注意,如果选择使用Resources.UnloadUnusedAssets,这是一个非常慢的操作,并且只能在不会显示任何故障的屏幕(例如加载屏幕)上调用。
AssetBundle依赖项
加载可寻址资产会加载所有AssetBundle依赖关系,并保持它们一直加载,直到您调用Addressables.Release从加载方法返回的句柄为止。
当一个AssetBundle中的资产引用另一个AssetBundle中的资产时,将创建AssetBundle依赖项。所有这些AssetBundle的依赖关系都可以视为依赖关系图。在构建过程的目录生成阶段,Addressables遍历此图以计算必须为每个可寻址资产加载的所有AssetBundle。由于依赖性是在AssetBundle级别计算的,因此单个AssetBundle中的所有可寻址资产都具有相同的依赖性。将具有外部引用(引用另一个AssetBundle中的对象)的可寻址资产添加到AssetBundle中,会将AssetBundle添加为AssetBundle中所有其他可寻址资产的依赖项。
例如:
BundleA包含可寻址资产Asset1和Asset2。Asset2引用包含在BundleB中的Asset3,。即使Asset1没有引用BundleB,BundleB也仍然是Asset1的依赖因为Asset1处于BundleA,而在BundleA上有引用BundleB。
注意
从1.13.0版开始,所需的AssetBundles集的依赖关系计算已更改。在1.13.0之前,仅加载了包含请求的可寻址资产的资产的AssetBundle。在上面的示例中,Asset1不会依赖BundleB。先前的行为导致当另一个AssetBundle引用的AssetBundle卸载并重新加载时,引用中断。