首先现在Unity插件那么的广泛的情况下,很多东西都不需要自己实现,直接使用第三方插件就可以了,但为什么这里需要自己写,接下来说明原因。
在Unity商城中有很多关于关于网络接口调用的插件,其中有一款叫BestHTTP这款使用比较广泛的插件,不知道朋友们是不是都知道,是不是都有用过,很多功能都已经给你实现好了,你只需要调用就可以了,插件很好用是吧。
以前我也用这个插件,在很多方面的确很好用,但是在个别平台有个很严重的问题,这个插件的调用占用内存会非常高,而且有一定的内存泄露。
在PC和移动端,由于现在硬件设备配置普遍都比较高,所以可能感觉的不会很明显,但在Webgl端,也是我经历之后才在之后的项目中不再使用这款插件的原因。在Unity的Webgl,限制了最多只能使用2048m的缓存,超过这个缓存值,网页就会报异常,到现在我也不知道该怎么解决这个问题,希望有知道的朋友告知一下,谢谢。
继续说原因,在Webgl端使用这个插件会有很明显的内存被大量占用,并且有一定的内存泄露,之前有一个项目,加载场景AB包,提一嘴本身每个平台的AB大小都是不一样的,虽然没比较过所有平台的,但是Webgl的AB包会比PC和移动端的大很多。
加载100m左右场景AB包,使用该插件加载只能加载2-3次就会直接报内存溢出,也就是前面说的超过了2048m的缓存上限。
因此后续就改成用Unity自带的UnityWebRequest类了,使用自带的也会有多占用内存的现象但是比该插件就会好很多,例如加载100m左右的ab能够加载12-15次时,才有可能会出现内存溢出的问题,如果有些数据释放的及时,甚至能加载更多次。
说了一大端的原因,那么接着就进入正题,首先下载,下载有个小问题,就是我们拿不到下载文件的总大小,这里我就提供解决方案,具体的实现根据自身项目使用
解决方案:
1、如果文件保存在对象存储上的,有一个Head请求,可以拿到文件的信息,里面包含了文件大小,但是这需要对象存储开启Head请求的权限
2、后台自己写一个保存文件的总大小,下载前先调用获取文件信息
3、UnityWebRequest中提供了两个数据,一个是progress进度,一个是当前下载量,那么其实可以通过 当前下载量/progress,反推出文件大小,只不过这个方式可能存在误差,但应该误差不会很大,所以如果不想多次请求的话可以使用这种方式
1、通过Head请求获取文件信息(方法使用了异步,使用异步需要扩写异步Task.GetAwaiter(),这方面已经有现成的异步插件了,以从夸克下载地址获取(不定时会更新该插件,如果其他文章中涉及到第三方插件,也会在该地址中提供测试的插件下载):https://pan.quark.cn/s/58004b052787)
public static async void GetDownloadFileSize(string downloadUrl, Action<WebRequetState, UnityWebRequest, ulong, string> outputAction = null, Dictionary<string, string> headers = null)
{
using (UnityWebRequest webRequest = UnityWebRequest.Head(downloadUrl))
{
if (headers != null)
{
foreach (var info in headers)
{
webRequest.SetRequestHeader(info.Key, info.Value);
}
}
webRequest.useHttpContinue = false;
outputAction?.Invoke(WebRequetState.WebReuqetStart, webRequest, 0, "开始获取文件大小");
await webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.Success)
{
ulong.TryParse(webRequest.GetResponseHeader("Content-Length"), out ulong size);
outputAction?.Invoke(WebRequetState.WebReuqetSucceed, webRequest, size, "获取文件大小成功");
}
else
{
Debug.LogWarning(webRequest.result == UnityWebRequest.Result.ConnectionError ? "连接失败" : "获取数据失败" + $" Method = {webRequest.method} Code = {webRequest.responseCode} error = {webRequest.error}");
outputAction?.Invoke(WebRequetState.WebReuqetFailed, webRequest, 0, webRequest.error);
}
}
}
2、下载文件的代码(通过协程方式,如果不通过协程,也可以用异步多线程,如果这两个都不使用,那就是主线程的Update去刷新)
protected virtual System.Collections.IEnumerator DownloadCoroutine()
{
using (request = UnityWebRequest.Get(Url))
{
request.useHttpContinue = false;
request.downloadHandler = new DownloadHandlerBuffer();
var wait = new WaitForEndOfFrame();
request.SendWebRequest();
do
{
CurrentSize = request.downloadedBytes;
if (request.downloadProgress > 0.00f)
{
//TotalSize = (ulong)(CurrentSize / request.downloadProgress);
//TODO:下载进度,可以用来显示进度条
}
yield return wait;
}
while (!request.isDone);
if (request.result == UnityWebRequest.Result.Success)
{
//下载成功时的操作,通过request.downloadHandler.data获取文件二进制数据
}
else
{
//下载失败时的操作
}
request.Dispose();
}
}
}
3、上传文件(可以改成协程,每帧调用显示上传进度)
/// <summary>
/// 上传文件
/// </summary>
/// <param name="uploadUrl">上传接口路径</param>
/// <param name="fileName">文件名</param>
/// <param name="fileData">文件数据</param>
/// <param name="headers">请求头</param>
/// <param name="outputAction">回调(上传状态,Request对象,返回数据,Message)</param>
/// <param name="fileKey">文件key字段</param>
/// <param name="contentType">内容Type</param>
public static async void UploadFile(string uploadUrl, string fileName, byte[] fileData, Dictionary<string, string> headers = null, Action<WebRequetState, UnityWebRequest, string, string> outputAction = null, string fileKey = "", string contentType = "")
{
List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
var fileSection = (fileKey == null || fileKey == string.Empty) ? new MultipartFormFileSection(fileName, fileData) : new MultipartFormFileSection(fileKey, fileData, fileName, contentType);
formData.Add(fileSection);
using (UnityWebRequest webRequest = UnityWebRequest.Post(uploadUrl, formData))
{
if (headers != null)
{
foreach (var info in headers)
{
webRequest.SetRequestHeader(info.Key, info.Value);
}
}
webRequest.useHttpContinue = false;
outputAction?.Invoke(WebRequetState.WebReuqetStart, webRequest, string.Empty, "开始上传");
await webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.Success)
{
outputAction?.Invoke(WebRequetState.WebReuqetSucceed, webRequest, webRequest.downloadHandler.text, "上传成功");
}
else
{
Debug.LogWarning(webRequest.result == UnityWebRequest.Result.ConnectionError ? "连接失败" : "上传失败" + $" Method = {webRequest.method} Code = {webRequest.responseCode} error = {webRequest.error}");
outputAction?.Invoke(WebRequetState.WebReuqetFailed, webRequest, webRequest.error, "上传失败");
}
}
}
基础的方法就这些,以后会整理一个专门的工具类脚本