Unity does not occupy memory records when downloading large files

    /// <summary>
    /// 协程:下载文件
    /// </summary>
    /// <param name="url">请求的Web地址</param>
    /// <param name="filePath">文件保存路径</param>
    /// <param name="callBack">下载完成的回调函数</param>
    /// <returns></returns>
    public IEnumerator DownloadFile(string url, string filePath, Action<DownloadInfo> callBack)
    {
    
    
        //string url = "https://dldir1.qq.com/qqfile/qq/QQ9.0.4/23766/QQ9.0.4.exe";
        //string url = "http://192.168.1.9/123.zip";
        UnityWebRequest www = UnityWebRequest.Get(url);
        www.timeout = 30;
        DownloadHandlerFileRange downloadHandlerFile = new DownloadHandlerFileRange(filePath + Path.GetFileName(url), www);
        www.downloadHandler = downloadHandlerFile;
        www.SendWebRequest();

        if (null != www.error)
        {
    
    
            Debug.Log(www.error);
        }
        else
        {
    
    

            while (!www.isDone)
            {
    
    
                string _progress = (www.downloadProgress * 100).ToString("f0") + "%\n" + downloadHandlerFile.Speed;
                downloadInfo.Change(DownloadState.DOWNLOADING, _progress);
                if (callBack != null)
                    callBack(downloadInfo);
                print(_isStop);
                if (_isStop)
                {
    
    
                    downloadHandlerFile.ManualDispose();
                    break;
                }
                yield return 0;
            }
            if (www.isDone)
            {
    
    
                downloadInfo.Change(DownloadState.DOWNLOADCOMPLETE, url);
                if (callBack != null)
                    callBack(downloadInfo);
            }
        }
        www.Dispose();
    }


	/// <summary>
    /// 分段,断点下载文件
    /// </summary>
    /// <param name="loadPath">下载地址</param>
    /// <param name="savePath">保存路径</param>
    /// <returns></returns>
    ulong loadedBytes = 10000000;
    Coroutine coroutine;
    public IEnumerator BreakPointDownload(string loadPath, string savePath, Action StopCallback)
    {
    
    
        //UnityWebRequest 经配置可传输 HTTP HEAD 请求的 UnityWebRequest。
        UnityWebRequest headRequest = UnityWebRequest.Head(loadPath);
        //开始与远程服务器通信。
        yield return headRequest.SendWebRequest();

        if (!string.IsNullOrEmpty(headRequest.error))
        {
    
    
            Debug.LogError("获取不到资源文件");
            //yield break;
        }
        //获取文件总大小
        ulong totalLength = ulong.Parse(headRequest.GetResponseHeader("Content-Length"));
        Debug.Log("获取大小" + totalLength);
        headRequest.Dispose();
        UnityWebRequest Request = UnityWebRequest.Get(loadPath);
        //append设置为true文件写入方式为接续写入,不覆盖原文件。
        Request.downloadHandler = new DownloadHandlerFile(savePath, true);
        //创建文件
        FileInfo file = new FileInfo(savePath);
        //当前下载的文件长度
        ulong fileLength = (ulong)file.Length;

        //请求网络数据从第fileLength到最后的字节;
        Request.SetRequestHeader("Range", "bytes=" + fileLength + "-");

        if (!string.IsNullOrEmpty(Request.error))
        {
    
    
            Debug.LogError("下载失败" + Request.error);
        }
        if (fileLength < totalLength)
        {
    
    
            Request.SendWebRequest();
            while (!Request.isDone)
            {
    
    
                double progress = (Request.downloadedBytes + fileLength) / (double)totalLength;
                Debug.Log("下载进度:" + progress);
                //超过一定的字节关闭现在的协程,开启新的协程,将资源分段下载
                if (Request.downloadedBytes >= loadedBytes)
                {
    
    
                    StopCallback();

                    //如果 UnityWebRequest 在进行中,就停止。
                    Request.Abort();
                    if (!string.IsNullOrEmpty(Request.error))
                    {
    
    
                        Debug.LogError("下载失败" + Request.error);
                    }
                    coroutine = StartCoroutine(BreakPointDownload(loadPath, savePath, StopCallback));
                    yield return coroutine;
                }
                yield return null;
            }
        }
        if (string.IsNullOrEmpty(Request.error))
        {
    
    
            Debug.Log("下载成功" + savePath);
        }



        //表示不再使用此 UnityWebRequest,并且应清理它使用的所有资源。
        Request.Dispose();
    }
using UnityEngine.Networking;
using System.IO;
using System;
using UnityEngine;

/// <summary>
/// 使用方式:
/// UnityWebRequest unityWebRequest = new UnityWebRequest("url");
/// unityWebRequest.downloadHandler = new DownloadHandlerFileRange("文件保存的路径", unityWebRequest);
/// unityWebRequest.SendWebRequest();
/// 
/// 参考自: https://gist.github.com/TouiSoraHe/19f2afeee1334cb8e42b3c8f2c283a65
/// 更完整的使用方式见:DownloadPatchSupportResume()方法
/// </summary>
public class DownloadHandlerFileRange : DownloadHandlerScript
{
    
    
    /// <summary>
    /// 文件正式开始下载事件,此事件触发以后即可获取到文件的总大小
    /// </summary>
    public event System.Action StartDownloadEvent;

    /// <summary>
    /// ReceiveData()之后调用的回调,参数是本地文件已经下载的总大小
    /// </summary>
    public event System.Action<ulong> DownloadedSizeUpdateEvent;

    #region 属性
    /// <summary>
    /// 下载速度,单位:KB/S 保留两位小数
    /// </summary>
    public string Speed
    {
    
    
        get
        {
    
    
            return DownloadSpeed.ToString("f2") + "kb/s";
        }
    }

    /// <summary>
    /// 文件的总大小
    /// </summary>
    public long FileSize
    {
    
    
        get
        {
    
    
            return TotalFileSize;
        }
    }

    /// <summary>
    /// 下载进度[0,1]
    /// </summary>
    public float DownloadProgress
    {
    
    
        get
        {
    
    
            return GetProgress();
        }
    }
    #endregion

    #region 公共方法
    /// <summary>
    /// 使用1MB的缓存,在补丁2017.2.1p1中对DownloadHandlerScript的优化中,目前最大传入数据量也仅仅是1024*1024,再多也没用
    /// </summary>
    /// <param name="path">文件保存的路径</param>
    /// <param name="request">UnityWebRequest对象,用来获文件大小,设置断点续传的请求头信息</param>
    public DownloadHandlerFileRange(string path, UnityWebRequest request) : base()
    {
    
    
        Debug.Log($"[Download] DownloadHandlerFileRange start");
        sw.Start();
        Path = path;
        FileStream = new FileStream(Path, FileMode.Append, FileAccess.Write);
        unityWebRequest = request;
        if (File.Exists(path))
        {
    
    
            LocalFileSize = new System.IO.FileInfo(path).Length;
        }
        CurFileSize = LocalFileSize;
        unityWebRequest.SetRequestHeader("Range", "bytes=" + LocalFileSize + "-");
        Debug.Log($"[Download] DownloadHandlerFileRange end LocalFileSize {
      
      LocalFileSize}");
    }

    /// <summary>
    /// 清理资源,该方法没办法重写,只能隐藏,如果想要强制中止下载,并清理资源(UnityWebRequest.Dispose()),该方法并不会被调用,这让人很苦恼
    /// </summary>
    public new void Dispose()
    {
    
    
        Debug.Log("[Download] Dispose()");
        Clean();
    }


    public void ManualDispose()
    {
    
    
        Debug.Log("[Download] ManualDispose()");
        Clean();
    }
    #endregion

    #region 私有方法
    /// <summary>
    /// 关闭文件流
    /// </summary>
    private void Clean()
    {
    
    
        Debug.Log("[Download] Clean()");
        DownloadSpeed = 0.0f;
        if (FileStream != null)
        {
    
    
            FileStream.Flush();
            FileStream.Dispose();
            FileStream = null;
        }
    }
    #endregion

    #region 私有继承的方法
    /// <summary>
    /// 下载完成后清理资源
    /// </summary>
    protected override void CompleteContent()
    {
    
    
        Debug.Log("[Download] CompleteContent()");
        base.CompleteContent();
        Clean();
    }

    /// <summary>
    /// 调用UnityWebRequest.downloadHandler.data属性时,将会调用该方法,用于以byte[]的方式返回下载的数据,目前总是返回null
    /// </summary>
    /// <returns></returns>
    protected override byte[] GetData()
    {
    
    
        Debug.Log("[Download] GetData()");
        return null;
    }

    /// <summary>
    /// 调用UnityWebRequest.downloadProgress属性时,将会调用该方法,用于返回下载进度
    /// </summary>
    /// <returns></returns>
    protected override float GetProgress()
    {
    
    
        return TotalFileSize == 0 ? 0 : ((float)CurFileSize) / TotalFileSize;
    }

    /// <summary>
    /// 调用UnityWebRequest.downloadHandler.text属性时,将会调用该方法,用于以string的方式返回下载的数据,目前总是返回null
    /// </summary>
    /// <returns></returns>
    protected override string GetText()
    {
    
    
        return null;
    }

    //Note:当下载的文件数据大于2G时,该int类型的参数将会数据溢出,所以先自己通过响应头来获取长度,获取不到再使用参数的方式
    protected override void ReceiveContentLengthHeader(ulong contentLength)
    {
    
    
        Debug.Log($"[Download] ReceiveContentLength {
      
      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 = (long)contentLength;
            }
            catch (System.Exception e)
            {
    
    
                UnityEngine.Debug.Log("获取文件长度失败,contentLengthStr:" + contentLengthStr + "," + e.Message);
                TotalFileSize = (long)contentLength;
            }
        }
        else
        {
    
    
            TotalFileSize = (long)contentLength;
        }
        //这里拿到的下载大小是待下载的文件大小,需要加上本地已下载文件的大小才等于总大小
        TotalFileSize += LocalFileSize;
        LastTime = UnityEngine.Time.time;
        LastDataSize = CurFileSize;
        if (StartDownloadEvent != null)
        {
    
    
            StartDownloadEvent();
        }
    }

    //在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),基本满足下载使用
    protected override bool ReceiveData(byte[] data, int dataLength)
    {
    
    
        if (data == null || dataLength == 0 || unityWebRequest.responseCode > 400)
        {
    
    
            Debug.Log($"[Download] ReceiveData return false: data: {
      
      data} length: {
      
      dataLength} responseCode: {
      
      unityWebRequest.responseCode}");
            return false;
        }
        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;
        //}
        DownloadSpeed = dataLength / 1024f / (float)sw.Elapsed.TotalSeconds;

        DownloadedSizeUpdateEvent?.Invoke((ulong)CurFileSize);

        return true;
    }

    ~DownloadHandlerFileRange()
    {
    
    
        Debug.Log("[Download] ~DownloadHandlerFileRange()");
        Clean();
    }
    #endregion

    #region 私有字段
    private string Path;//文件保存的路径
    private FileStream FileStream;
    private UnityWebRequest unityWebRequest;
    private long LocalFileSize = 0;//本地已经下载的文件的大小
    private long TotalFileSize = 0;//文件的总大小
    private long CurFileSize = 0;//当前的文件大小
    private float LastTime = 0;//用作下载速度的时间统计
    private float LastDataSize = 0;//用来作为下载速度的大小统计
    private float DownloadSpeed = 0;//下载速度,单位:Byte/S
    private System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    #endregion
}

Guess you like

Origin blog.csdn.net/qq_39556084/article/details/125403820