1. 程式人生 > >AssetBundle管理機制(上)

AssetBundle管理機制(上)

atlas strong img rem byte das let atl 由於

AssetBundle內存管理機制

接上期AssetBundle打包的講解,我們今天為大家繼續探秘AssetBundle,從管理機制的角度出發,談談其資源加載和卸載的原理。

同時如果你恰有相關疑問,歡迎後臺留言給UWA,或者加入QQ群(465082844)討論,當然也不要忘記關註UWA哦。

◆◆◆◆

AssetBundle加載基礎


通過AssetBundle加載資源,分為兩步,第一步是獲取AssetBundle對象,第二步是通過該對象加載需要的資源。而第一步又分為兩種方式,下文中將結合常用的API進行詳細地描述。

第一步,獲取AssetBundle對象的常用API


方式一,先獲取WWW對象,再通過WWW.assetBundle獲取AssetBundle對象:

● public WWW(string url);

加載Bundle文件並獲取WWW對象,完成後會在內存中創建較大的WebStream(解壓後的內容,通常為原Bundle文件的4~5倍大小,紋理資源比例可能更大),因此後續的AssetBundle.Load可以直接在內存中進行。

技術分享

● public static WWW LoadFromCacheOrDownload(string url, int version, uint crc = 0);

加載Bundle文件並獲取WWW對象,同時將解壓形式的Bundle內容存入磁盤中作為緩存(如果該Bundle已在緩存中,則省去這一步),完成後只會在內存中創建較小的SerializedFile,而後續的AssetBundle.Load需要通過IO從磁盤中的緩存獲取。

● public AssetBundle assetBundle;

通過之前兩個接口獲取WWW對象後,即可通過WWW.assetBundle獲取AssetBundle對象。


方式二,直接獲取AssetBundle:

● public static AssetBundle CreateFromFile(string path);

通過未壓縮的Bundle文件,同步創建AssetBundle對象,這是最快的創建方式。創建完成後只會在內存中創建較小的SerializedFile,而後續的AssetBundle.Load需要通過IO從磁盤中獲取。

● public static AssetBundleCreateRequest CreateFromMemory(byte[] binary);

通過Bundle的二進制數據,異步創建AssetBundle對象。完成後會在內存中創建較大的WebStream。調用時,Bundle的解壓是異步進行的,因此對於未壓縮的Bundle文件,該接口與CreateFromMemoryImmediate等價。

● public static AssetBundle CreateFromMemoryImmediate(byte[] binary);

該接口是CreateFromMemory的同步版本。

註:5.3下分別改名為LoadFromFile,LoadFromMemory,LoadFromMemoryAsync並增加了LoadFromFileAsync,且機制也有一定的變化,可詳見Unity官方文檔。

第二步,從AssetBundle加載資源的常用API

● public Object Load(string name, Type type);

通過給定的名字和資源類型,加載資源。加載時會自動加載其依賴的資源,即Load一個Prefab時,會自動Load其引用的Texture資源。

● public Object[] LoadAll(Type type);

一次性加載Bundle中給定資源類型的所有資源。

● public AssetBundleRequest LoadAsync(string name, Type type);

該接口是Load的異步版本。

註:5.x下分別改名為LoadAsset,LoadAllAssets,LoadAssetAsync,並增加了LoadAllAssetsAsync。

◆◆◆◆

AssetBundle加載進階

接口對比:new WWW與WWW.LoadFromCacheOrDownload

前者的優勢

● 後續的Load操作在內存中進行,相比後者的IO操作開銷更小;

● 不形成緩存文件,而後者則需要額外的磁盤空間存放緩存;

● 能通過WWW.texture,WWW.bytes,WWW.audioClip等接口直接加載外部資源,而後者只能用於加載AssetBundle;

前者的劣勢

● 每次加載都涉及到解壓操作,而後者在第二次加載時就省去了解壓的開銷;

● 在內存中會有較大的WebStream,而後者在內存中只有通常較小的SerializedFile。(此項為一般情況,但並不絕對,對於序列化信息較多的Prefab,很可能出現SerializedFile比WebStream更大的情況)

內存分析

技術分享

在管理AssetBundle時,了解其加載過程中對內存的影響意義重大。在上圖中,我們在中間列出了AssetBundle加載資源後,內存中各類物件的分布圖,在左側則列出了每一類內存的產生所涉及到的加載API:

● WWW對象:在第一步的方式1中產生,內存開銷小;

● WebStream:在使用new WWW或CreateFromMemory時產生,內存開銷通常較大;

● SerializedFile:在第一步中兩種方式都會產生,內存開銷通常較小;

● AssetBundle對象:在第一步中兩種方式都會產生,內存開銷小;

● 資源(包括Prefab):在第二步中通過Load產生,根據資源類型,內存開銷各有大小;

● 場景物件(GameObject):在第二步中通過Instantiate產生,內存開銷通常較小。

在後續的章節中,我們還將針對該圖中各類內存物件分析其卸載的方式,從而避免內存殘留甚至泄露。

註意點

● CreateFromFile只能適用於未壓縮的AssetBundle,而Android系統下StreamingAssets是在壓縮目錄(.jar)中,因此需要先將未壓縮的AssetBundle放到SD卡中才能對其使用CreateFromFile。

Application.streamingAsstsPath = "jar:file://" Application.dataPath "!/assets/";

● iOS系統有256個開啟文件的上限,因此,內存中通過CreateFromFile或WWW.LoadFromCacheOrDownload加載的AssetBundle對象也會低於該值,在較新的版本中,如果LoadFromCacheOrDownload超過上限,則會自動改為new WWW的形式加載,而較早的版本中則會加載失敗。

● CreateFromFile和WWW.LoadFromCacheOrDownload的調用會增加RersistentManager.Remapper的大小,而PersistentManager負責維護資源的持久化存儲,Remapper保存的是加載到內存的資源HeapID與源數據FileID的映射關系,它是一個Memory Pool,其行為類似Mono堆內存,只增不減,因此需要對這兩個接口的使用做合理的規劃。

● 對於存在依賴關系的Bundle包,在加載時主要註意順序。舉例來說,假設CanvasA在BundleA中,所依賴的AtlasB在BundleB中,為了確保資源正確引用,那麽最晚創建BundleB的AssetBundle對象的時間點是在實例化CanvasA之前。即,創建BundleA的AssetBundle對象時、Load(“CanvasA”)時,BundleB的AssetBundle對象都可以不在內存中。

技術分享

● 根據經驗,建議AssetBundle文件的大小不超過1MB,因為在普遍情況下Bundle的加載時間與其大小並非呈線性關系,過大的Bundle可能引起較大的加載開銷。

● 由於WWW對象的加載是異步的,因此逐個加載容易出現下圖中CPU空閑的情況(選中幀處Vsync占了大部分),此時建議適當地同時加載多個對象,以增加CPU的使用率,同時加快加載的完成。

技術分享

以上是AssetBundle資源加載部分,有加載自然有卸載,鑒於篇幅,我們將另起一篇,敬請關註!

AssetBundle管理機制(上)