unity 背景交代
unity的新资源系统虽然有提供了远程加载的功能,但是调查了一轮,发现那套系统对于手游的资源更新还是不大理想,因为我们想通过一次远程加载后,就写入到本地,然后下次启动游戏的时候,我们能直接从本地读取,而不是又从新去下载!
unity的常规下载我们都知道怎么做,但是如果网络不稳定等,而且需要下载的单个文件比较大的时候,在没有断点续传时,会造成大量的流量消耗。所以我们需要配合服务器处理一套断点续传的功能,后端的知识就不在这里讲了!
大致原理
先说说断点续传的大致原理吧,后面会直接贴出代码参考! 众所周知,网络流的数据都是逐包接收的,一截一截的数据流在底层处理后交给前端。
在UnityWebRequest
里提供了一个让我们去继承的下载接口,透过ReceiveData
把每次接收道的数据包直接写到文件末尾,如果一个文件分成了100个包来传输,那么ReceiveData
就会调起至少100次,将每次的数据一次写到文件里。
根据上面说到的,如果中途断开了,因为之前接收到的包数据在磁盘中,故只需要把传输的开始index
上行到后端,后端将Index
后的字节流继续下发,前端ReceiveData
收到后append到文件末就可以;
在上面的情况下还是会有问题的,所以在下载完成后,最好是和后端的文件md5之类的校验一下文件的完整性!
代码
下面奉上DownloadHandle的代码,代码有大量的注释,应该都能看懂
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace WDframework
{
/// <summary>
/// 下载句柄
/// 注意,如果unity的权限不是require的话,下载会失败
/// </summary>
public class DownloadHandle : DownloadHandlerScript
{
#region 私有字段
/// <summary>
/// 文件保存的路径
/// </summary>
private string Path;
/// <summary>
/// 写的文件流
/// </summary>
private FileStream FileStream;
/// <summary>
/// http请求
/// </summary>
private UnityWebRequest UnityWebRequest;
/// <summary>
/// 本地已经下载的文件的大小
/// </summary>
private long LocalFileSize = 0;
/// <summary>
/// 文件的总大小
/// 无奈开启
/// </summary>
public long TotalFileSize = 0;
/// <summary>
/// 当前的文件大小
/// </summary>
private long CurFileSize = 0;
/// <summary>
/// 用作下载速度的时间统计
/// </summary>
private float LastTime = 0;
/// <summary>
/// 用来作为下载速度的大小统计
/// </summary>
private float LastDataSize = 0;
/// <summary>
/// 下载速度,单位:Byte/S
/// </summary>
private float DownloadSpeed = 0;
#endregion
/// <summary>
/// 使用1MB的缓存,在补丁2017.2.1p1中对DownloadHandlerScript的优化中,目前最大传入数据量也仅仅是1024*1024,再多也没用
/// </summary>
/// <param name="path">文件保存的路径</param>
/// <param name="request">UnityWebRequest对象,用来获文件大小,设置断点续传的请求头信息</param>
public DownloadHandle(string path, UnityWebRequest request): base(new byte[1024 * 1024])
{
Path = path;
FileStream = new FileStream(Path, FileMode.Append, FileAccess.Write);
UnityWebRequest = request;
this.LocalFileSize = File.Exists(path)? new System.IO.FileInfo(path).Length : 0;
CurFileSize = LocalFileSize;
// 发起请求
request.SetRequestHeader("Range", "bytes=" + LocalFileSize + "-");
}
/// <summary>
/// 析构
/// </summary>
~DownloadHandle()
{
Debug.LogError("~DownloadHandle");
Clean();
}
/// <summary>
/// 断点续传的时候是拿到多少就写入多少,不需要最后获取了
/// </summary>
/// <returns>null</returns>
protected override byte[] GetData()
{
return null;
}
/// <summary>
/// 断点续传的时候是拿到多少就写入多少,不需要最后获取了
/// </summary>
/// <returns>null</returns>
protected override string GetText()
{
return null;
}
/// <summary>
/// 在2017.3.0(包括该版本)以下的正式版本中存在一个性能上的问题
/// 该回调方法有性能上的问题,每次传入的数据量最大不会超过65536(2^16)个字节,不论缓存区有多大
/// 在下载速度中的体现,大约相当于每秒下载速度不会超过3.8MB/S
/// 这个问题在 "补丁2017.2.1p1" 版本中被优化(2017.12.21发布)(https://unity3d.com/cn/unity/qa/patch-releases/2017.2.1p1)
/// (965165) - Web: UnityWebRequest: improve performance for DownloadHandlerScript.
/// 优化后,每次传入数据量最大不会超过1048576(2^20)个字节(1MB),基本满足下载使用
/// </summary>
/// <param name="data">接收到的数据</param>
/// <param name="dataLength">字节长度</param>
/// <returns>成功</returns>
protected override bool ReceiveData(byte[] data, int dataLength)
{
if (data == null || dataLength == 0)
{
return false;
}
Debug.LogError("ReceiveData=>"+dataLength);
FileStream.Write(data, 0, dataLength);
CurFileSize += dataLength;
//统计下载速度
if (UnityEngine.Time.time - LastTime >= 1.0f)
{
DownloadSpeed = (CurFileSize - LastDataSize) / (UnityEngine.Time.time - LastTime);
LastTime = UnityEngine.Time.time;
LastDataSize = CurFileSize;
}
return true;
}
/// <summary>
/// 接受到文件头,需要从文件头里解释相应数据
/// </summary>
/// <param name="contentLength">内容</param>
protected override void ReceiveContentLength(int contentLength)
{
string contentLengthStr = UnityWebRequest.GetResponseHeader("Content-Length");
if (!string.IsNullOrEmpty(contentLengthStr))
{
try
{
TotalFileSize = long.Parse(contentLengthStr);
}
catch (System.FormatException e)
{
UnityEngine.Debug.Log("获取文件长度失败,contentLengthStr:" + contentLengthStr + "," + e.Message);
TotalFileSize = contentLength;
}
catch (System.Exception e)
{
UnityEngine.Debug.Log("获取文件长度失败,contentLengthStr:" + contentLengthStr + "," + e.Message);
TotalFileSize = contentLength;
}
}
else
{
TotalFileSize = contentLength;
}
//这里拿到的下载大小是待下载的文件大小,需要加上本地已下载文件的大小才等于总大小
TotalFileSize += LocalFileSize;
LastTime = UnityEngine.Time.time;
LastDataSize = CurFileSize;
}
protected override void CompleteContent()
{
base.CompleteContent();
}
/// <summary>
/// 调用UnityWebRequest.downloadProgress属性时,将会调用该方法,用于返回下载进度
/// </summary>
/// <returns>进度</returns>
protected override float GetProgress()
{
return TotalFileSize == 0 ? 0 : ((float)CurFileSize) / TotalFileSize;
}
#region 私有方法
/// <summary>
/// 关闭文件流
/// </summary>
public void Clean()
{
this.Dispose();
DownloadSpeed = 0.0f;
if (FileStream != null)
{
FileStream.Flush();
FileStream.Dispose();
FileStream = null;
}
}
#endregion
}
}
PREVIOUSMMORPG 游戏出海大突围(录屏)
NEXTunity手动合批