本篇主要讲解使用Addressables系统做资源更新,基于Addressables1.8.5版本,下面简称AA系统
这是AA系统自带的一个资源服务,我们可以指定一个目录存放远程资源,然后通过连接这个服务器,来更新资源.一般正式项目还是会自己去布置资源服务器,这个自带功能我们可以自己用来在开发时内部使用,毕竟配置很少,很便捷.
通过菜单栏可以打开这个工具,也可以在Group界面的Tools下拉列表中打开.
打开过后会看到下面的界面
可以通过这个窗口创建服务,Create中提供了两个选项
这里只说Local Hosting,创建一个本地资源服务器,创建过后点击enable,这个服务器就启动了.
这个服务提供了3个变量,可以填在Profile中.现在我们可以不用管它了.当有服务请求时,会产生输出在Console中.
首先创建4个Group,BuildPath决定了这个包被打包后存放的位置,LoadPath则是加载位置.每个包都可以存放在不同的位置.UpdateRestriction决定了这个包是静态还是非静态包
最后给每个Group里面添加一个预制件作为资源,然后打包验证一下更新流程.
这四个资源是包含一个文本的预制件,用文本表示自己是位于哪个Group中的,等打包过后,再修改预制件文本进行更新,看看是否有变化.
除了上面的HostingServices和Group的配置,还需要对AddressableAssetSettings文件进行配置
主要是上面几个参数
当这些都配置好了过后,开始打包测试.通过AA系统提供的默认打包脚本打包.
打包过后,可以看到远程资源都打包在了ServerData目录中,本地资源都打包在了Library/com.unity.addressables/StreamingAssetsCopy/aa/Android/中
同时在远程目录下会生成有catalog文件
.hash后缀的里面只包含一个hash值,是catalog文件的hash值,用于客户端检测catalog更新的时候会先对比这个hash值,才知道catalog文件有没有被修改,有修改才更新catalog文件.
.json文件就是catalog文件了,里面记录了每个AB包的hash值,地址,以及AB包里有哪些资源.客户端通过对比这些值,来检测是否有资源需要更新
而在Library/com.unity.addressables/StreamingAssetsCopy/aa/Android目录中还会生成一个catalog.json文件和settings.json文件以及link.xml
catalog.json文件与远程目录的catalog文件是一模一样的,我猜测这份文件是当没有开起远程更新的时候才会使用这份文件,开起远程更新过后,应该是使用的下载下来的catalog文件.
setting.json文件里面记录了catalog文件的远程更新地址,以及一些其它设置参数,实际上就是把工程中的AddressableAssetSettings文件转换成了json
还有一个十分重要的文件
这个后缀名为.bin的文件是记录所有AB包之间的依赖,以及分组情况的.相信之前自己做过AB资源管理的同学都知道.以前用BuildPiepline打AB包的时候都会生成Manifast文件来管理依赖,现在AA系统是用这个.bin文件来管理,Library/com.unity.addressables/StreamingAssetsCopy/Android目录下同样也会有一份一模一样的,用于在发布应用程序的时候一起打包进去.
现在我发布一个安卓的apk文件,进行测试,看看AA系统到底是如何进行资源加载和更新的.
public void LocalStatic()
{
Addressables.InstantiateAsync("Assets/Datas/local_static.prefab", parent);
}
public void LocalNonStatic()
{
Addressables.InstantiateAsync("Assets/Datas/local_non_static.prefab", parent);
}
public void RemoteStatic()
{
Addressables.InstantiateAsync("Assets/Datas/remote_static.prefab", parent);
}
public void RemoteNonStatic()
{
Addressables.InstantiateAsync("Assets/Datas/remote_non_static.prefab", parent);
}
现在可以看到4个资源都正常的加载出来了,同时我的服务器上输出了3条请求
第一个是在AA系统初始化的时候请求的catalog的hash值,根据我的配置,我勾选了Disable Catalog Update On Startup,并且我的代码还没有调用更新catalog,所以不应该进行自动更新.这里只是请求了.hash文件,并没有真正的对catalog文件进行更新.
后面两个文件就是我们的远程静态AB包和非静态的AB包.程序加载的时候,会根据本地catalog文件(Library/com.unity.addressables/StreamingAssetsCopy/aa/Android目录中的那个和远程服务器目录中的catalog文件一模一样的文件)来查找资源,检测到两个本地资源的地址,都在本地,会直接进行加载,而两个远程资源本地没有,会到服务器上去下载,所以服务器会输出这两条log.
UnityEngine.Application.persistentDataPath/UnityCache/Shared
这是资源缓存的位置,我发现当我开始实例化那4个对象时,会在这4个目录中生成对应的缓存文件
这里是资源缓存的位置,为什么会存在local的资源缓存呢,那是因为我之前对每个Group都勾选了Use Asset Bundle Cache,实际上这个选项对local的资源是没有用处的,反而会占用空间,所以对于local的Group应该把这个给取消掉.
资源已经可以正常下载,加载.现在开始改动资源,然后再看看之前发布的那个apk上的资源是否可以正常进行更新.现在我在编辑器中对每个资源进行修改,改成下图那样
修改过后,我需要对资源进行重新打包,这时候需要做两个操作
点击后会弹出对话框要求选择之前打包生成的.bin文件.然后会出现如下界面
然后点击apply changes,结果如下
首先可以看到local static 和remote static 的资源被移动到了一个名叫Content Update的Group中,这是工具自动生成的,是通过查询.bin文件,来找出相对于第一次打包被修改过的资源.但是发现一个问题,我明明对4个资源都进行了修改,但是这里只对static的资源进行了重新分配.
现在来详细说明一下静态Group和非静态Group.
现在看来Check for Content Update Restrictions这个操作,其实就是找出相对于第一次发布后的静态Group的改动,然后重新生成一个Group.
现在我们只打包了资源,并没有重新打包apk.我继续用之前的那个apk来测试.打开应用程序,分别点击下面4个加载按钮
可以看到当前的资源依然是之前的,这是因为我们还没有进行资源的更新,现在点击UpdateCatalog,执行如下代码,先更新catalog文件.
public async void UpdateCatalog()
{
//开始连接服务器检查更新
var handle = Addressables.CheckForCatalogUpdates(false);
await handle.Task;
Debug.Log("check catalog status " + handle.Status);
if (handle.Status == AsyncOperationStatus.Succeeded)
{
List catalogs = handle.Result;
if (catalogs != null && catalogs.Count > 0)
{
foreach (var catalog in catalogs)
{
Debug.Log("catalog " + catalog);
}
Debug.Log("download catalog start ");
var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
await updateHandle.Task;
foreach (var item in updateHandle.Result)
{
Debug.Log("catalog result " + item.LocatorId);
foreach (var key in item.Keys)
{
Debug.Log("catalog key " + key);
}
_updateKeys.AddRange(item.Keys);
}
Debug.Log("download catalog finish " + updateHandle.Status);
}
else
{
Debug.Log("dont need update catalogs");
}
}
Addressables.Release(handle);
}
首先调用Addressables.CheckForCatalogUpdates(false),这一步是去向服务器请求.hash文件,检测catalog文件是否需要更新,这一步也是AA系统初始化会做的.返回的字符串列表如果数量大于0,那么就说明catalog文件需要更新,那么就调用Addressables.UpdateCatalogs(catalogs, false).这个时候可以看到服务器的输出,访问了这两个文件,现在客户端本地的catalog文件已经到最新了
再点击DownLoad按钮下载更新资源
public IEnumerator DownAssetImpl()
{
var downloadsize = Addressables.GetDownloadSizeAsync(_updateKeys);
yield return downloadsize;
Debug.Log("start download size :" + downloadsize.Result);
if (downloadsize.Result > 0)
{
var download = Addressables.DownloadDependenciesAsync(_updateKeys, Addressables.MergeMode.Union);
yield return download;
//await download.Task;
Debug.Log("download result type " + download.Result.GetType());
foreach (var item in download.Result as List)
{
var ab = item.GetAssetBundle();
Debug.Log("ab name " + ab.name);
foreach (var name in ab.GetAllAssetNames())
{
Debug.Log("asset name " + name);
}
}
Addressables.Release(download);
}
Addressables.Release(downloadsize);
}
public void DownLoad()
{
StartCoroutine(DownAssetImpl());
}
Addressables.GetDownloadSizeAsync(_updateKeys)这个接口可以获取到需要下载的文件的总大小,而传入的_updateKeys是之前Addressables.UpdateCatalogs(catalogs, false)的返回值,这里面包含了需要更新的内容的key,我们也可以在这里进行筛选,假如有些资源是你不想更新的.接着调用Addressables.DownloadDependenciesAsync(_updateKeys, Addressables.MergeMode.Union)来进行真正的资源下载,注意这里有个坑,这个API返回的句柄不能用Task,会报空,不知道原因,也许是bug.当前版本1.8.5.
可以看到服务器上的日志又多了两条,说明我的客户端请求了这两个AB包.这与我们的预期是一样的,local和remote的静态包的内容都已经跑到了contentupdate这个AB包里了,remote的非静态包已经被覆盖更新了,被存放在服务器的资源目录中,而local的非静态包,被存放在Library/com.unity.addressables/StreamingAssetsCopy/aa/Android目录中,没有在服务器的资源目录中,所以这个包肯定是无法被更新的.下面我们再分别点击4个加载按钮测试下结果.
可以看到local static 和 remote static 已经通过contentupdate被正常下载更新,而remote non static是直接被覆盖了ab包更新.只有local non static 在加载的时候抛出了异常,无法加载.这里可能和大家想象的不一样,觉得local non static应该是用的老的资源来加载,其实不是.因为catalog文件被更新了,前面说过catalog文件会记录所有AB包的哈希值以及加载地址,当前catalog文件记录到local non static 的hash值是最新的,而我们当前留在手机上的local non static AB包却是之前的,而且这个AB包在catalog上记录的地址是在本地,无法向远程服务器下载.当AA系统发现hash值与AB包对应不上时,就当成资源缺失,自然就加载不出来这个资源了.