Unity的资源下载与缓存系统的实现
在Unity开发的游戏中,我们经常会下载,缓存,更新资源文件,达到更新游戏内容的目的。(主要是AssetBundle文件)
Unity中已经提供了简单的下载与缓存功能,WWW.LoadFromCacheOrDownload可以满足基本的文件下载和缓存的需求。
为什么要自己重新造轮子
为什么还要自己重新设计实现这个下载缓存系统呢。
对我们的项目来说,WWW.LoadFromCacheOrDownload有几个比较重大的缺点:
无法取得下载的资源文件的路径,导致无法实现对文件的管理。
在使用WWW.LoadFromCacheOrDownload前,需要等待Caching.ready,随着缓存文件的增加,Caching.ready的速度会越来越慢。
WWW不支持断点续传。断点续传需要设置HTTP Header,但是WWW会把设置了Header的Request全部作为POST来处理。 我们的游戏需要应对各种复杂的网络环境,在网络很差的环境下,一旦文件下载失败就从头开始下载是不可接受的。
WWW可设置的参数非常少,例如Timeout不可设置。
HTTP Client的选择
我们对比了几种Client,总结如下表。
Libray | 来源 | 优点 | 缺点 |
---|---|---|---|
WWW | Unity内置 | 可以使用Unity自带Cache功能;不会增加build的size | 设置Header会导致Request变成POST,因此无法用于CDN下载;无法设置Timeout,Redirect次数等参数 |
UnityWebRequest | Unity内置 | 不会增加build的size | 无法使用Unity的cache,需要自己实现cache;Timeout无法设置,无法使用HTTPS的证书;还处于Experimental中,经常会导致程序崩溃 |
BestHTTP | 第三方Asset | 可以断点续传;文档非常丰富;Redirect,Timeout等参数可以设置;可以使用HTTPS的self-signed certificate | 会增加数MB的build size;需要自己实现cache system |
HttpWebRequest | .NET / Mono | System.Net namespace,可以减少build size | 无法在Coroutines中使用 |
最终我们选择了BestHTTP,这是一个付费的Asset,价格55美元。好在并不需要每人一个license。给官方发邮件确认后,得知一个团队(公司)只需要购买一个license就可以了。
缓存系统的设计与实现
由于不使用WWW.LoadFromCacheOrDownload,所以需要自己实现缓存系统。
基本的需求是,游戏逻辑向缓存系统请求一个资源文件list:
- 当服务器的文件本地不存在时,下载并缓存。
- 当服务器的文件版本与本地不一致时,下载更新,替换本地文件
- 服务文件与本地文件一致,不下载,直接使用本地的缓存文件
- 检查下载文件完整性
资源文件版本控制
AssetBundle的依赖关系我们可以从Single Manifest文件中取得。Single Manifest还包含了每个AssetBundle文件的Hash值,用
AssetBundleManifest.GetAssetBundleHash方法可以取得该HASH值。可以用于确认本地文件和服务器文件的版本是否一致。
本想直接利用这个Hash值进行文件完整性验证,但是这个Hash的算法既不是MD5也不是SHA1,自己无法计算,所以就放弃了这个想法。
我们在AssetBundle build的同时,生成一个ResourceList的文本文件,里面记录文件的CRC和size。格式如下:
filename|CRC|size
加入文件大小的原因是,想要在下载的界面上显示剩余下载的大小,而SingleManifest文件中并没有提供文件的大小。
在每次下载前,先需要下载SingleManifest文件和ResourceList文件,然后再判定哪些文件需要下载,哪些文件从缓存中读取。
文件下载分为,新文件下载,文件更新下载,断点续传下载,从本地读取几种情况。判定方法如下图。
下载的文件都存储于Application.persistentDataPath中,该目录不会被系统清理缓存等操作被清空。但是注意Android手机如果SD卡被拔出,可能会造成之前写入的文件无法访问。
下载断点续传的实现
根据HTTP协议,在HTTP Request的Header中加入Range: bytes=start-end(单位是字节),就可以从上次中断的位置继续下载。
本地写入文件时,选择Append模式即可。
保持下载不中断
当下载文件较大时,下载可能会消耗比较长的时间。如果用户把手机放在一旁,手机自动进入休眠导致下载中断,或者切换到别的程序,而导致下载中断,会导致比较差的游戏体验。所以这个方面也要进行处理。
让设备不进入休眠
Screen.sleepTimeout = SleepTimeout.NeverSleep;
我们可以使用这个API来设置,在下载期间不让机器进入休眠状态。当下载结束后,再将这个设置恢复成默认。
当程序切换
Application.runInBackground = true;
使用这个API可以让程序切换到后台的时候仍然保持下载继续。同样不要忘记在下载结束后,把设置恢复。
网络连接情况的监测
当设备进行网络切换的时候,例如,从蜂窝移动网络切换到Wifi,从Wifi A切换到Wifi B,这种情况下,原本的连接一定会超时失败,但是我们不想等待超时,当网络连接发生变化时候,主动放弃连接,并进行Retry,以提高下载效率。
通过Unity中Application.internetReachability可以获取到当前网络状态,我们可以将其保存下来,然后在Update方法中进行检测,就可以得知网络是否发生了变化。
void Update () { if (Application.internetReachability != m_reachability) { m_request.Abort (); } m_reachability = Application.internetReachability; }