AB包
1.AB包内部结构
AB包简单说就是一串二进制数据,这串数据存的是资源(预制、材质、贴图、模型等)。这串数据分为:
1.头文件
AssetbundleFileHeader
记录这个AB的信息:版本号、压缩格式等信息。
AssetFileHeader
记录AB的文件信息(一个文件列表、记录了每个资源的name、offset、length等信息,后面读取具体资源的使用用)
2.具体的资源
头文件后面跟着的是n段数据,一段数据就是一个资源(也有一些信息、其中比较重要的就是资源类型id(0:Object、1:GameObject等)、对外部资源的引用情况)。
2..mainfest文件
.mainfest是一个文本文件里面有ab包的校验码,依赖以及资源内容等相关信息,这个信息是给开发读的,同时这个信息也作为二进制打入了ab包里面,所以使用ab包资源的时候,可以不用带.manifest文件。
内容:
`ManifestFileVersion: 0 # 文件版本 CRC: 2657307167 # CRC校验码 Hashes: # 哈希 AssetFileHash: # AB包中所有资源的哈希,可用于增量更新检测 serializedVersion: 2 # Unity序列化版本 Hash: 717e408ba50ee41b0960161fd2d5a827 TypeTreeHash: # AB包中所有类型的哈希,可用于增量更新检测 serializedVersion: 2 # Unity序列化版本 Hash: 8d552bf2f5bdba1177c938cb98ca6f2f HashAppended: 0 ClassTypes: # TypeTree - Class: 1 # GameObject Script: {instanceID: 0} - Class: 21 # Material Script: {instanceID: 0} ... Assets: # 包含资源 - Assets/Bundle/.../a.prefab - Assets/Bundle/.../b.prefab - Assets/Bundle/.../c.spriteatlas Dependencies: # AB包依赖 - /Users/apple/.../AssetBundles/Android/q - /Users/apple/.../AssetBundles/Android/w - /Users/apple/.../AssetBundles/Android/e - /Users/apple/.../AssetBundles/Android/r - /Users/apple/.../AssetBundles/Android/t`
3.AB变体
简单说就是一个资源的分类标签,如同一个图片的高清和低清资源。
4.压缩格式
LZMA和LZ4
LZMA需要完整解压之后才能加载包内资源(但是压缩出来的包体小),LZ4不需要完整解压就可以加载包内资源(数据被分为大小相同的块,并被分别压缩,加载时只加载Header,用哪块的数据就解压对应的块就可以,内存占用小)。
默认使用LZMA压缩方式,因为第一次解压之后,该包又会使用LZ4再次压缩。(这就是为什么第一次加载时间长,后面就没这么长了)
5.获取AB包
AssetBundle.LoadFromFile(string path)
AssetBundle.LoadFromFileAsync(string path)
最常用的方法,从文件中加载AB包,它从一个给定的路径来加载AB包。如果AB包是LZ4加载方式,它只会加载AB包的Header,之后需要什么资源再加载那部分的AB包chunk。极大的减少了内存占用。
AssetBundle.LoadFromStream(Stream stream)
AssetBundle.LoadFromStreamAsync(Stream stream)
不咋用,从流中加载AB包,它从一个Stream中加载AB包。跟LoadFromFile一样,如果AB包是LZ4加载方式,它也是只会加载AB包的Header。(LoadFromStreamAsync是它的异步版本)
AssetBundle.LoadFromMemory(byte[] binary)
AssetBundle.LoadFromMemoryAsync(byte[] binary)
一般是网络接口用,LoadFromMemory是从内存中加载AB包,它从内存中的byte[]中加载AB包。它会完整的把AB包加载出来,适用于ab包已经被加载进内存的情况(参数是byte数组)。
6.内存占用
现在的主流做法是在进游戏之前下载热更的包并存在本地,所以不会有WebStream这部分,当然,如果是运行时下载资源就还是要用到这个。
Assetbundle的内存占用:压缩的AB包 + 解压缩的AB包(也就是具体的资源,LZ4的不会全解,只解要用的部分)+ 实例化的资源。
7.资源卸载
1.卸载ab包
1.ab.Unload(false) : 卸载掉紫色部分(压缩的AB包)。会解除最上面部分(实例)和最下面部分(ab包)的链接关系(Instance ID的GUID和Local ID引用变无效,其实也就是ab解出来的资源和ab关联没了),再次加载一个紫色部分,原来的绿色部分不会受新加载的紫色部分的管理。因此如果不注意的话可能会导致一些不可控的问题,Unity中有Resources.UnloadUnusedAssets()方法可以很好地解决这个问题。
2.ab.Unload(true) : 卸载掉紫色部分(压缩的AB包)以及绿色部分(具体的Asset),注意这个举动可能会引发最上面的区域(实例化区)引用的资源丢失。
2.卸载ab包中解出来的资源
1.Resources.UnloadUnusedAssets : 卸载掉没有引用的绿色部分(压缩的AB包)。
2.Resources.UnloadAsset():卸载Asset,但只能用来卸载具体资源(文本、音频、贴图),对于GameObject、Assetbundle内部的Assetbundle、脚本、Component都不会卸载。
项目中用的卸载方法是:
1.多数假跳转场景:只清除资源缓存、ab缓存,确保ab和资源没有引用,不调用ab.UnLoad。
2.少数真跳转场景(战斗、关卡等):清除资源缓存、ab缓存,确保ab和资源没有引用,unity切场景会自动调用ab.Unload和Resources.UnloadUnusedAssets
3.游戏退出:所有资源、ab包卸载。
8.依赖问题
依赖问题,通俗的话来说就是A包中某资源用了B包中的某资源。然而如果A包加载了,B包没有加载,这就会导致A包中的资源出现丢资源的现象。
所以要尽量避免加载A包之前还要加载B包的问题。
在Unity5.0后,BuildAssetBundleOptions.CollectDependencies永久开启,即Unity会自动检测物体引用的资源并且一并打包,防止资源丢失遗漏的问题出现,但会导致资源冗余(同一份资源被打进不同的包中)。
最好就是将一些公共资源单独打到一个ab包中,并提前加载完成,尽可能减少AB包的依赖。
todo Tools.AnarysicsResources
9.细粒度问题
细粒度问题即每个AB包分别放入多少资源的问题,一个好的策略至关重要。
加载资源时,先要加载AB包,再加载资源。如果AB包使用了LZMA或LZ4压缩算法,还需要先给AB包解压。
AB包数量较多,包内资源较少
AB包数量较少,包内资源较多
加载一个AB包到内存的时间短,玩家不会有卡顿感,但每个资源实际上加载时间变长。
加载一个AB包到内存的时间较长,玩家会有卡顿感,但之后包内的每个资源加载很快。
热更新灵活,要更新下载的包体较小。
热更新不灵活,要更新下载的包体较大。
IO次数过多,增大了硬件设备耗能和发热压力。
IO次数不多,硬件压力小。
策略:
1.按照类型:根据文件夹,比如模型、图片、动画。
2.按照功能:同一个模型文件夹下可能有场景资源、人物资源,根据不同的功能进行打包。
3.按照模块:比如我们玩家模型1-10级,或者不同的极端需要不同的模型,我们就按照模块进行打包。
4.公共的资源单独打一个包。(公共资源:声音、shader等)
5.每个特效和他所依赖的材质贴图打成一个包。
6.代码可以分功能打包(不超过3m)
7.同时使用的尽量打一个包,不同时使用的尽量打成两个包。
8.经常更新和不经常更新的对象拆分到不同的AB包中。
9.依赖文件根据上面说的就行
10.资源校验
验证本地文件的hashcode和unity的hashcode(这两个code是固定的,不会随着设备的不同而变化)
散列算法就是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。
也就是说这两个code变了,这个文件也就变了(损坏了)。
11.meta文件和asset的文本
meta文件主要是记录文件的GUID、文件导入设置、所在ab包以及依赖的ab包。guid是唯一的,可以通过guid找到这个文件。
unity的资源都可以使用文本打开,可以通过文本来清晰的看到资源的引用关系。
比如:
这是一个材质的文本,右边的图是将材质的贴图从空改成一个图集的某一张图片。
可以看到m_Texture的值变成了fileID - guid-...。
fileID:在资源内部的子成员的定位Id,比如赋过来的这张图集这个里面有很多张图片,每张图片对应一个fileID,通过fileID和GUID就可以找到该图集下的这张图片。
fileID + GUID可以定位到某个文件的某个子文件。
InstanceID:虽然GUID和Local ID比较好用,但是毕竟因为存在磁盘上,读取比较耗时。因此Unity缓存一个instance ID对应Object,通过instance ID快速找到Object。instance ID是一种快速获取对象实例的ID,包含着对GUID和Local ID的引用。
12.打ab包工具
https://naotu.baidu.com/file/33e98b92b2d522ee9fba22be1bcc7330
13.一些问题
material的资源实例在加载的时候要检查其shader是否支持(material.shader.isSupported),如果不支持指定shader,就从这个包里找一个能用的shader赋上(苟一下,防止unity闪退),并报错,基本这种问题在上线前就被QA测出来了修掉了。
ParticleSystem的material(可能是第三方的),要在检测的回调中检测是否是粒子,是的话要检查其shader是否支持,不支持的话要找一个能用的shader放上(苟一下,防止unity闪退),并报错,基本这种问题在上线前就被QA测出来了修掉了。