接上期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資源加載部分,有加載自然有卸載,鑒於篇幅,我們將另起一篇,敬請關注!