---
title: "【资源】Asset Bundle"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/unity-asset-bundle/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/Unity/【资源】Asset Bundle.md"
content_hash: 0d72986014343748da6fc7d6bbd5572e31b8a0044d6bc6e07456af363b46cf5e
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# 【资源】Asset Bundle

> 创建时间：2020/6/16 20:26

  * 【资源】Asset Bundle
    * Introduction
      * 资源加载的方式
        * Resources加载的缺点
      * AssetBundle是什么
        * 从资源的角度
        * 从API的角度
      * 用处
    * 内存结构
    * 关于打包
      * 分组策略
        * 逻辑实体分组
        * 按照类型分组
        * 按照使用分组
      * 依赖打包
      * 关于压缩
        * LZMA和LZ4
        * BuildAssetBundleOptions
      * assetBundleVariant 及自动标记打包API
    * 加载
      * AssetBundle
        * LoadFromFile
        * LoadFromMemory
        * LoadFromStream
      * WWW.LoadFromCacheOrDownload（Obsolete）
      * UnityWebRequest - AssetBundleDownloadHandler
        * 使用建议
      * 同步和异步的优缺点
      * Manifests的加载
    * 卸载
      * AssetBundle.Unload(true)
      * AssetBundle.Unload(false)
      * 建议
    * 其他功能
      * AssetDatabase
      * Asset PostProcessor
    * AB包内部结构
    * 加密
      * 一般思路
      * 耗费小的思路-偏移量
      * LoadFromStream
    * 文件校验
    * 发布
      * 和项目一起安装
      * 安装后再下载它们
      * 缓存机制
    * 相关优化
      * 冗余问题
      * 图集重复问题
      * 其他问题
    * UnityAssetBundleBrowserTool
      * StreamingAssets
    * Ref

## Introduction

### 资源加载的方式

Resources加载 - Resources.Load()来直接加载Resources文件夹下的资源

AssetBundle

Addressable

#### Resources加载的缺点

  * 对内存管理造成一定的负担。

  * 在打开应用时加载时间很长。

  * Resources文件夹下的所有资源统一合并到一个序列化文件中（可以看成统一打一个大包，巨型AB包有什么问题它就有什么问题），对资源优化有一定的限制。

  * 不建议大量使用Resources。

### AssetBundle是什么

#### 从资源的角度

它是一个 **存在于硬盘上的文件** 。可以称之为 **压缩包** 。这个压缩包可以认为是一个文件夹，里面包含了多个文件。这些文件可以分为两类：serialized file 和 resource files。（序列化文件和源文件）

  * serialized files：资源被打碎放在一个对象中，最后统一被写进一个单独的文件（只有一个）。相当于是一个头文件，里面记录了关于这个AB的相关信息，当我们调用 **LoadFromFile** 接口加载AB的时候实际上就是去加载的这一部分信息。这部分信息在Profiler里面的SerilizeField选项里。

  * resource files（Bundle Content ）：某些二进制资源（图片、声音）被单独保存，方便快速加载

#### 从API的角度

它是一个AssetBundle对象，我们可以通过代码从一个特定的压缩包加载出来的对象。这个对象包含了所有我们当初添加到这个压缩包里面的内容，我们可以通过这个对象加载出来使用。

### 用处

  1. AssetBundle是一个压缩包包含模型、贴图、预制体、声音、甚至整个场景，可以在游戏运行的时候被加载；

  2. AssetBundle自身保存着互相的依赖关系；

  3. 压缩包可以使用LZMA和LZ4压缩算法，减少包大小，更快的进行网络传输；

  4. 把一些可以下载内容放在AssetBundle里面，可以减少安装包的大小；

## 内存结构

![Alt text](/media/7756549eab8af9cc5a7d.png)

## 关于打包

AB包数量较多，包内资源较少 - 加载一个AB包到内存的时间短，玩家不会有卡顿感，但每个资源实际上加载时间变长。热更新灵活，要更新下载的包体较小。IO次数过多，增大了硬件设备耗能和发热压力。

AB包数量较少，包内资源较多 - 加载一个AB包到内存的时间较长，玩家会有卡顿感，但之后包内的每个资源加载很快。热更新不灵活，要更新下载的包体较大。IO次数不多，硬件压力小。

### 分组策略

  1. 把经常更新的资源放在一个单独的包里面，跟不经常更新的包分离

  2. 把需要同时加载的资源放在一个包里面

  3. 可以把其他包共享的资源放在一个单独的包里面

  4. 把一些需要同时加载的小资源打包成一个包

  5. 如果对于一个同一个资源有两个版本，可以考虑通过后缀来区分 v1 v2 v3 unity3dv1 unity3dv2

#### 逻辑实体分组

a. 一个UI界面或者所有UI界面一个包（这个界面里面的贴图和布局信息一个包）
b. 一个角色或者所有角色一个包（这个角色里面的模型和动画一个包）
c. 所有的场景所共享的部分一个包（包括贴图和模型）

#### 按照类型分组

所有 **声音** 资源打成一个包，所有 **shader** 打成一个包，所有 **模型** 打成一个包，所有材质打成一个包

#### 按照使用分组

把在某一时间内使用的所有资源打成一个包。可以按照关卡分，一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包。也可以按照场景分，一个场景所需要的资源一个包

### 依赖打包

(Mat + Cube1) (Mat + Cube2)
(Mat) (Cube1) (Cube2)
Unity会自动识别，共享的依赖如果已经单独打包，则不会重复打包

### 关于压缩

  * 加载时间：当从本地存储或本地缓存加载时，未压缩的AssetBundles加载速度比压缩的AssetBundles快得多。

  * 构建时间：在压缩文件时，LZMA和LZ 4非常慢，统一编辑器依次处理AssetBundles。拥有大量资产Bundles的项目将花费大量的时间压缩它们。

  * 应用程序大小：如果AssetBundles是在应用程序中提供的，那么压缩它们将减少应用程序的总大小。或者，AssetBundles可以在安装后下载。

  * 内存使用：在Unity 5.3之前，Unity的所有解压缩机制都要求在解压缩之前将整个压缩的AssetBundles加载到内存中。如果内存使用很重要，请使用未压缩或LZ 4压缩AssetBundles。

#### LZMA和LZ4

LZMA是流压缩方式（stream-based）。流压缩再处理整个数据块时使用同一个字典，它提供了最大可能的压缩率，但是只支持顺序读取。所以加载AB包时，需要将整个包解压之后才能加载包内资源，会造成卡顿和额外内存占用。

LZ4是块压缩方式（chunk-based）。块压缩的数据被分为大小相同的块，并被分别压缩。不需要完整解压就可以加载包内资源。如果需要实时解压随机读取，块压缩是比较好的选择。
使用LZ4压缩，可以获得可以跟不压缩想媲美的加载速度，而且比不压缩文件要小。

![Alt text](/media/4d44e1a815a41a64ab3f.png)

#### BuildAssetBundleOptions

> BuildAssetBundles底层会对传入的BuildAssetBundleOptions值进行处理，根据二进制位数来判断使用哪种策略构建AB包。因此如果在构建AB包时想要使用多种策略，用&连接即可。

BuildAssetBundleOptions.None：使用LZMA算法压缩，压缩的包更小，但是加载时间更长。使用之前需要整体解压。一旦被解压，这个包会使用LZ4重新压缩。。这就是为什么第一次加载时间长，之后加载时间就没那么长了。在下载的时候可以使用LZMA算法，一旦它被下载了之后，它会使用LZ4算法保存到本地上。

BuildAssetBundleOptions.UncompressedAssetBundle：不压缩，包大，加载快

DisableWriteTypeTree - 8：不包含TypeTree信息。虽然可以使得AB包更小，但是对低版本不兼容。不建议使用。

DeterministicAssetBundle - 16：创建一个哈希来映射存储在AB包里的对象的id。

ForceRebuildAssetBundle - 32：强制重建AB包。

IgnoreTypeTreeChanges - 64：当做增量构建检测时，忽略type tree的变化。

AppendHashToAssetBundleName - 128：添加哈希到AB包名。

ChunkBasedCompression - 256：使用LZ4压缩，压缩率没有LZMA高，但是我们可以加载指定资源而不用解压全部。

StrictMode - 512：如果在构建时有任何错误，则不允许构建成功。

DryRunBuild - 1024：干构建。

DisableLoadAssetByFileName - 4069：禁止AB包通过文件名加载资源。

DisableLoadAssetByFileNameWithExtension - 8192：禁止AB包通过文件扩展名加载资源。

AssetBundleStripUnityVersion：构建时从压缩文件和序列化文件的header中移除Unity版本号。

### assetBundleVariant 及自动标记打包API

**different AssetBundle variants will have the same internal IDs** 。因此，AssetBundle的后缀名不仅仅是名称那么简单。而加载使用时，就看作不同名称的AB即可。

> 注意：assetBundleName中设置后缀名 “Cube.ab”，并不能启到AssetBundle Variant的作用。后缀名应该在AssetImporter.assetBundleVariant中设置。

variant 的用处：

  * 好一点设备上可以加载高模，而差一点的设备中只能加载低模

  * 做分包，不同版本包

  * 做多语言包

手动设置name & variant name:

![Alt text](/media/a0d3516af14aa48a4a5f.png)

代码自动设置：

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

## 加载

加载流程可归为两步：

  1. 从目标路径读取并创建 AssetBundle 对象

  2. 从 AssetBundle 对象中创建 Asset

### AssetBundle

#### LoadFromFile

从磁盘路径读取 AssetBundle 文件，如果文件内容是以 LZMA 格式压缩的，则会将内容解压进内存保存。未压缩(uncompressed) 和块压缩(chunk-compressed) 的部分将会通过 IO 从磁盘读取。

如果AB包是LZ4加载方式，它只会加载AB包的Header，之后需要什么资源再加载那部分的AB包chunk。极大的减少了内存占用。

> 但在Editor环境下，API还是会把整个AssetBundle加载到内存中，不应影响实际运行的设备上的性能。

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

#### LoadFromMemory

从托管代码的字节数组（C#中的byte[]）中加载AssetBundle。要提前用其它的方式将资源的二进制数组加入到内存中。然后该接口会将源数据从托管代码字节数组复制到新分配的、连续的本机内存块中。

如果AssetBundle使用了LZMA压缩，在复制过程中AssetBundle会被解压；如果使用了LZ4压缩或者没有压缩，AssetBundle会被逐字复制（be copied verbatim）。

当资源有加密需求时，可先把AB读取到内存当中，进行解密后再调用该AssetBundle.LoadFromMemory 进行加载。

此API消耗的最大内存量将至少是AssetBundle的两倍：本机内存中的一个副本，和LoadFromMemory(Async)从托管字节数组中复制的一个副本。

因此，从通过此API创建的AssetBundle加载的资产将在内存中一次在托管代码字节数组中，一次在AssetBundle的栈内存副本中，第三次在GPU或系统内存中，用于Asset本身。

适用于需要对原始数据进行解密的方式。

> **若把Start()函数声明成返回IEnumrator，编译器会自动将Start的调用处理成协程的模式**

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

#### LoadFromStream

AssetBundle.LoadFromStream可进行流式加载，不需要将AB全部读到内存中再解密、加载，而是可以通过每次像Buffer中读一部分，解密一部分的方式进行加载，不会多占用一份很大的内存。如果使用该接口，需要自定义一个继承FileStream类，然在Read和Write方法内对byte数组进行异或加密解密处理。

> 与 LoadFromFile 类似， LZMA 格式压缩的会被解压进内存，未压缩(uncompressed) 和块压缩(chunk-compressed) 的部分将会直接通过 Stream 来读取。
>
> 在加载AssetBundle或捆绑包中的任何资产时，请勿释放Stream对象。它的寿命应该比AssetBundle长。这意味着在调用AssetBundle.Unload之后处理Stream对象。

<https://www.xuanyusong.com/archives/4607>

### WWW.LoadFromCacheOrDownload（Obsolete）

Loading an AB from a remote location will automatically **cache** the AssetBundles. If the AssetBundle is compressed, a worker thread will spin up to decompress the bundle and write it to the cache. **Once a bundle has been decompressed and cached, it will load exactly like AssetBundle.LoadFromFile .**

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

### UnityWebRequest - AssetBundleDownloadHandler

它使用工作线程，将下载的数据流存储到一个固定大小的缓冲区中，然后根据下载处理程序的配置方式将缓冲数据放到临时存储或AssetBundle缓存中。

它使用工作线程，将下载的数据流存储到一个固定大小的缓冲区中，然后根据下载处理程序的配置方式将缓冲数据放到临时存储或AssetBundle缓存中。

所有这些操作都发生在非托管代码中，消除了增加堆内存的风险。此外，该下载处理程序并不会保留所有下载字节的栈内存副本，从而进一步减少了下载AssetBundle的内存开销。

如果将缓存信息提供给UnityWebRequest对象，一旦有请求的AssetBundle已经存在于Unity的缓存中，那么AssetBundle将立即可用，并且此API的行为将会与AssetBundle.LoadFromFile相同操作。

> 从服务器下载
>
>   1. NetBox可以在本地启动服务器，把当前目录作为服务器端的网站
>
>

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

#### 使用建议

  1. 一般来说，只要有可能，就应该使用AssetBundle.LoadFromFile。这个API在速度、磁盘使用和运行时内存使用方面是最有效的。

  2. 对于必须下载或热更新AssetBundles的项目，强烈建议使用UnityWebRequest。

  3. 当使用UnityWebRequest时，要确保下载程序代码在加载AssetBundle后正确地调用Dispose。另外，C#的using语句是确保UnityWebRequest被安全处理的最方便的方法。

  4. 对于需要独特的、特定的缓存或下载需求的大项目，可以考虑使用自定义的下载器。编写自定义下载程序是一项重要并且复杂的任务，任何自定义的下载程序都应该与AssetBundle.LoadFromFile保持兼容。

  5. 在多数情况下，最好在玩家进入应用程序的性能关键区域（如主游戏关卡或世界）之前加载尽可能多的所需对象。这在移动平台上尤为重要，因为在移动平台上，访问本地存储的速度很慢，并且在运行时加载和卸载对象会触发垃圾回收。

### 同步和异步的优缺点

Ref: <https://blog.csdn.net/qq_40093529/article/details/85290686>
**异步**
优点：速度快，与主线程无关，
缺点：调用比较麻烦，因为你不知道啥时候你的资源准备好了，最好的做法也是使用回调，这样回调就会很多，很乱个人感觉管理起来很不舒服。

**同步**
优点：管理起来方便，而且资源准备好了是可以及时返回的，
缺点：是没有异步快
综合上诉，最终我选择了 同步，因为我不希望代码不整洁，也不希望写太多的回调函数来通知调用者，资源准备妥当了。那么问题来了，如何解决同步的缺点呢。也就是卡主线程。之前一直以为corotine这玩意可以帮到我们。但是当我深入理解了coroutine以后发现他其实也是在主线程中的。最终我选择了使用c# 的多线程机制来解决这个问题。

### Manifests的加载

加载Manifests文件可以处理资源的依赖

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

## 卸载

卸载有两个方面

  1. 减少内存使用

  2. 有可能导致丢失
所以什么时候去卸载资源是我们要关注的

### AssetBundle.Unload(true)

不仅Object被销毁，而且Instance ID的GUID和Local ID引用变无效。
When unloadAllLoadedObjects is true, all objects that were loaded from this bundle will be destroyed as well. If there are GameObjects in your Scene referencing those assets, the references to them will become missing.

卸载所有资源，即使有资源被使用着
1\. 在关卡切换、场景切换
2\. 资源没被用的时候调用

### AssetBundle.Unload(false)

虽Object不被销毁，但Instance ID的GUID和Local ID引用变无效。场景中的物体会与该AB包分离链接。即该物体的instance ID引用的GUID和Local ID会断开引用，无法再通过该instance ID找到GUID和Local ID。
如果再次加载该AB包时，分离了链接的物体不会受该新加载的AB包管理。因此如果不注意的话可能会导致一些不可控的问题。Unity中有Resources.UnloadUnusedAssets()方法可以很好地解决这个问题。

When unloadAllLoadedObjects is false, compressed file data inside the bundle itself will be freed, but any instances of objects loaded from this bundle will remain intact.

If an application must use AssetBundle.Unload(false), then individual Objects can only be unloaded in two ways:

  1. 先去除对不想要的Objects的引用（包括场景和代码当中），然后调用 Resources.UnloadUnusedAssets.

  2. 场景切换的时候：Load a scene non-additively. This will destroy all Objects in the current scene and invoke `Resources.UnloadUnusedAssets` automatically.

### 建议

大多数项目应该使用AssetBundle.Unload(True)，并采用一种方法来确保对象不被复制。
两种常见的方法是：
（1）在应用程序的生命周期内定义一个合适的节点，并在此期间卸载不需要的AssetBundle，例如在关卡切换或加载屏幕期间。这是最简单和最常见的选择。
（2）维护单个对象的引用计数，并仅当所有组成对象都未使用时才卸载AssetBundles。这允许应用程序在不重复内存的情况下卸载和重新加载单个对象。

## 其他功能

### AssetDatabase

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

  * AssetDatabase.GetAllAssetBundleNames()

  * AssetDatabase.GetAssetPathsFromAssetBundle

  * AssetDatabase.RemoveAssetBundleName()

  * AssetDatabase.GetUnusedAssetBundleNames()

  * AssetDatabase.RemoveUnusedAssetBundleNames()

  * The callback AssetPostProcessor.OnPostprocessAssetbundleNameChanged is called if user changes the AssetBundle name of an asset.

### Asset PostProcessor

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

## AB包内部结构

AssetBundleFileHeader：记录了版本号、压缩等主要描述信息。

AssetFileHeader：包含一个文件列表，记录了每个资源的name、offset、length等信息。

Asset1：

AssetHeader：记录了TypeTree大小、文件大小、format等信息。
TypeTree（可选，有不要TypeTree的构建方式）：记录了Asset对象的class ID。Unity可以用class ID来序列化和反序列化一个类。（每个class对应了一个ID，如0是Object类，1是GameObject类等。具体可在Unity官网上查询。）
ObjectPath：记录了path ID（资源唯一索引ID）等。
AssetRef：记录了AB包对外部资源对引用情况。
Asset2…

.manifest文件

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

## 加密

### 一般思路

加密思路：
1、在构建完AB包后，可以将AB包中的内容以byte[]形式读取。
2、之后选用任意加密方式对该byte[]加密。
3、加密完后重新写入AB包中。
4、AB包加密完成。

> 这样对AB包加密之后，如果使用AssetBundle.LoadFromFile()来加载加密的AB包是会报错的，因为Unity以及无法识别加密过后的内容。

解密思路：
1、先以byte[]形式读取AB包中的内容。
2、之后使用对应的解密算法对该byte[]进行解密。
3、解密过后的byte[]通过AssetBundle.LoadFromMemory()来进行加载。
4、AB包加载完成。

占用内存的峰值至少是他所处理的AssetBundle大小的两倍：一份由此API创建在本机内存中的副本和一份传递给此API的位于托管字节数组中的副本。通过此API从AssetBundle加载的Asset会在内存中被复制3次：在托管代码中的字节数组、本机内存中的副本以及在GPU或系统内存中的Asset本体。

### 耗费小的思路-偏移量

在构建完AB包之后，在AB包前插入N个随机byte。AssetBundle.LoadFromFile()的第三个参数是AB包内容的byte偏移量。也就是说从offset个byte开始读取AB包的内容。

> 对于资源加密，我们大多数时候能做到的是防小白不防专家，不管你是采用简单的或者复杂的，在反编译高手手里都有点苍白

```csharp
class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;
        dyn = dyn + 3;
        //obj = obj + 3; 报错

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

```

具体实现可参考：<https://zhuanlan.zhihu.com/p/75964237>

### LoadFromStream

<https://www.xuanyusong.com/archives/4607>

## 文件校验

CRC MD5 SHA1
相同点：
CRC、MD5、SHA1都是通过对数据进行计算，来生成一个校验值，该校验值用来校验数据的完整性。
不同点：

  1. 算法不同。CRC采用多项式除法，MD5和SHA1使用的是替换、轮转等方法；

  2. 校验值的长度不同。CRC校验位的长度跟其多项式有关系，一般为16位或32位；MD5是16个字节（128位）；SHA1是20个字节（160位）；

  3. 校验值的称呼不同。CRC一般叫做CRC值；MD5和SHA1一般叫做哈希值（Hash）或散列值；

  4. 安全性不同。这里的安全性是指检错的能力，即数据的错误能通过校验位检测出来。CRC的安全性跟多项式有很大关系，相对于MD5和SHA1要弱很多；MD5的安全性很高，不过大概在04年的时候被山东大学的王小云破解了；SHA1的安全性最高。

  5. 效率不同，CRC的计算效率很高；MD5和SHA1比较慢。

  6. 用途不同。CRC一般用作通信数据的校验；MD5和SHA1用于安全（Security）领域，比如文件校验、数字签名等。

## 发布

### 和项目一起安装

  * 减少项目构建时间并允许更简单的迭代开发。

  * 发布可更新内容的初始修订版。

### 安装后再下载它们

这就允许在安装后再更新游戏内容，而不必强迫用户重新下载整个应用程序。
将它们放在某个Web服务器上，并通过UnityWebRequest发布。Unity将自动在本地存储上缓存下载的AssetBundles。如果下载的AssetBundle是LZMA压缩的，那么AssetBundle将以未压缩或重新压缩的形式存储在缓存中，就像LZ 4一样（依赖Caching.compressionEnabled设置），以便将来更快地加载。如果下载的包是LZ 4压缩的，AssetBundles将被压缩存储。如果缓存被填满，Unity将从缓存中删除最近使用最少的AssetBundle。

### 缓存机制

Unity有一个内置的AssetBundle缓存系统，可以用来缓存通过UnityWebRequest API下载的AssetBundles，该API的重载会接受一个AssetBundle版本号作为参数。这个数字不是存储在AssetBundles里的，也不是由AssetBundles系统生成的。

缓存系统跟踪传递给UnityWebRequest的最后一个版本号。当使用版本号调用此API时，缓存系统通过比较版本号来检查是否存在缓存的AssetBundle。如果这些数字匹配，系统将加载缓存的AssetBundle。如果数字不匹配，或者没有缓存的AssetBundle，那么Unity将下载一个新的副本。此新副本将与新的版本号相关联。

缓存系统中的AssetBundle只通过它们的文件名来标识，而不是通过下载它们的完整URL。这意味着具有相同文件名的AssetBundle可以存储在多个不同的位置，例如CDN。只要文件名相同，缓存系统就会将它们识别为相同的AssetBundle。

## 相关优化

### 冗余问题

在Unity5.0后，BuildAssetBundleOptions.CollectDependencies永久开启，即Unity会自动检测物体引用的资源并且一并打包，防止资源丢失遗漏的问题出现。有些情况下，如果没指定某公共资源的存放在哪个AB包中，这个公共资源就会被自动打进引用它的AB包中，所以出现多个不同的AB包中有重复的资源存在的现象。这就是资源冗余。

a. 把需要共享的资源打包到一起
b. 分割包，这些包不是在同一时间使用的
c. 把共享部分打包成一个单独的包

### 图集重复问题

在Unity当中，Sprite2D会被打包到一个图集当中（由Packing Tag决定）。如果不指定PackingTag，Sprite会打包到同一个图集当中。一个Sprite打包到AssetBundle中时，它所在的整个图集都会被打包进去。
解决方法：确保同一个图集当中的图片打包到同一个AssetBundle当中去。

### 其他问题

  * Android贴图问题

  * iOS文件处理重复fixed in Unity 5.3.2p2.

## UnityAssetBundleBrowserTool

### StreamingAssets

Build的时候，该文件夹下的所有东西会被原封不动地打包到我们的安装包当中。
一般放一些二进制文件、 AssetBundles。

## Ref

<https://segmentfault.com/a/1190000038943912>
<https://blog.uwa4d.com/archives/USparkle_Addressable3.html>

<https://learn.unity.com/tutorial/assets-resources-and-assetbundles>
