Unity 下载文件 WWW与HttpWebRequest 断点续传

描述
对于一个网络游戏来说,下载网络上面的文件一定是必不可少的一个功能,例如更新资源包。在Unity中,我们可以用系统的WWW或者HttpWebRequest来实现文件的下载。其中有些较大的文件下载需要断点续传的功能(即下载了一部分突然中断下载后,再次下载直接从上次下载的地方继续下载,而不是重新下载),需要使用HttpWebRequest。

这一篇我们主要就讲一讲这两种下载方式的实现(主要都是代码)。

基类
首先先创建一个基类,里面存放下载需要的一些数据,例如文件url,下载存放路径等等。

public abstract class DownloadItem {
 
    /// <summary>
    /// 网络资源url路径
    /// </summary>
    protected string m_srcUrl;
    /// <summary>
    /// 资源下载存放路径,不包含文件名
    /// </summary>
    protected string m_savePath;
    /// <summary>
    /// 文件名,不包含后缀
    /// </summary>
    protected string m_fileNameWithoutExt;
    /// <summary>
    /// 文件后缀
    /// </summary>
    protected string m_fileExt;
    /// <summary>
    /// 下载文件全路径,路径+文件名+后缀
    /// </summary>
    protected string m_saveFilePath;
    /// <summary>
    /// 原文件大小
    /// </summary>
    protected long m_fileLength;
    /// <summary>
    /// 当前下载好了的大小
    /// </summary>
    protected long m_currentLength;
    /// <summary>
    /// 是否开始下载
    /// </summary>
    protected bool m_isStartDownload;
    public bool isStartDownload {
        get {
            return m_isStartDownload;
        }
    }
 
    public DownloadItem(string url, string path) {
        m_srcUrl = url;
        m_savePath = path;
        m_isStartDownload = false;
        m_fileNameWithoutExt = Path.GetFileNameWithoutExtension(m_srcUrl);
        m_fileExt = Path.GetExtension(m_srcUrl);
        m_saveFilePath = string.Format("{0}/{1}{2}", m_savePath, m_fileNameWithoutExt, m_fileExt);
    }
 
    /// <summary>
    /// 开始下载
    /// </summary>
    /// <param name="callback">下载完成回调</param>
    public virtual void StartDownload(Action callback = null) {
        if(string.IsNullOrEmpty(m_srcUrl) || string.IsNullOrEmpty(m_savePath)) {
            return;
        }
        //若存放目录不存在则创建目录
        FileTool.CreateDirectory(m_saveFilePath);
    }
 
    /// <summary>
    /// 获取下载进度
    /// </summary>
    /// <returns>进度,0-1</returns>
    public abstract float GetProcess();
 
    /// <summary>
    /// 获取当前下载了的文件大小
    /// </summary>
    /// <returns>当前文件大小</returns>
    public abstract long GetCurrentLength();
 
    /// <summary>
    /// 获取要下载的文件大小
    /// </summary>
    /// <returns>文件大小</returns>
    public abstract long GetLength();
 
    public abstract void Destroy();
}
WWW
www的方式下载很简单,等www读取好后,直接使用www.bytes去生成文件即可。这种方式文件是一次性生成的,适合小资源的下载

/// <summary>
/// WWW的方式下载
/// </summary>
public class WWWDownloadItem : DownloadItem {
 
    WWW m_www;
 
    public WWWDownloadItem(string url, string path) : base(url, path) {
 
    }
 
    public override void StartDownload(Action callback = null) {
        base.StartDownload();
        UICoroutine.uiCoroutine.StartCoroutine(Download(callback));
    }
 
    IEnumerator Download(Action callback = null) {
        m_www = new WWW(m_srcUrl);
        m_isStartDownload = true;
        yield return m_www;
        //WWW读取完成后,才开始往下执行
        m_isStartDownload = false;
 
        if(m_www.isDone) {
            byte[] bytes = m_www.bytes;
            //创建文件
            FileTool.CreatFile(m_saveFilePath, bytes);
        } else {
            Debug.Log("Download Error:" + m_www.error);
        }
 
        if(callback != null) {
            callback();
        }
    }
 
    public override float GetProcess() {
        if(m_www != null) {
            return m_www.progress;
        }
        return 0;
    }
 
    public override long GetCurrentLength() {
        if(m_www != null) {
            return m_www.bytesDownloaded;
        }
        return 0;
    }
 
    public override long GetLength() {
        return 0;
    }
 
    public override void Destroy() {
        if(m_www != null) {
            m_www.Dispose();
            m_www = null;
        }
    }
}
HTTP
这种方式下载,我们采用的是边读取边生成对应文件,适合较大文件下载。关于断点续传的处理,我们下载的时候,先根据读取的内容生成一个临时文件,当全部下载好后,才将这个临时文件转换成正式的文件名。若中途下载中断,已下载好的那部分文件依据在对应目录下,我们下次下载可以直接在上次下载的临时文件后添加内容。

所以下载逻辑就是,每次下载先判断是否有对应的临时文件,若没有则是第一次下载,从初始位置读取下载,若有该文件,则读取该文件信息,获取文件大小当之后的读取下载的偏移量,从当前位置开始下载,直至下载完成。

/// <summary>
/// HTTP的方式下载,支持断点续传
/// </summary>
public class HttpDownloadItem : DownloadItem {
    /// <summary>
    /// 临时文件后缀名
    /// </summary>
    string m_tempFileExt = ".temp";
    /// <summary>
    /// 临时文件全路径
    /// </summary>
    string m_tempSaveFilePath;
 
    public HttpDownloadItem(string url, string path) : base(url, path) {
        m_tempSaveFilePath = string.Format("{0}/{1}{2}", m_savePath, m_fileNameWithoutExt, m_tempFileExt);
    }
 
    public override void StartDownload(Action callback = null) {
        base.StartDownload();
        UICoroutine.uiCoroutine.StartCoroutine(Download(callback));
    }
 
    IEnumerator Download(Action callback = null) {
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(m_srcUrl);
        request.Method = "GET";
 
        FileStream fileStream;
        if(File.Exists(m_tempSaveFilePath)) {
            //若之前已下载了一部分,继续下载
            fileStream = File.OpenWrite(m_tempSaveFilePath);
            m_currentLength = fileStream.Length;
            fileStream.Seek(m_currentLength, SeekOrigin.Current);
 
            //设置下载的文件读取的起始位置
            request.AddRange((int)m_currentLength);
        } else {
            //第一次下载
            fileStream = new FileStream(m_tempSaveFilePath, FileMode.Create, FileAccess.Write);
            m_currentLength = 0;
        }
 
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        Stream stream = response.GetResponseStream();
        //总的文件大小=当前需要下载的+已下载的
        m_fileLength = response.ContentLength + m_currentLength;
 
        m_isStartDownload = true;
        int lengthOnce;
        int bufferMaxLength = 1024 * 20;
 
        while(m_currentLength < m_fileLength) {
 
            byte[] buffer = new byte[bufferMaxLength];
            if(stream.CanRead) {
                //读写操作
                lengthOnce = stream.Read(buffer, 0, buffer.Length);
                m_currentLength += lengthOnce;
                fileStream.Write(buffer, 0, lengthOnce);
            } else {
                break;
            }
            yield return null;
        }
 
        m_isStartDownload = false;
        response.Close();
        stream.Close();
        fileStream.Close();
 
        //临时文件转为最终的下载文件
        File.Move(m_tempSaveFilePath, m_saveFilePath);
 
        if(callback != null) {
            callback();
        }
    }
 
    public override float GetProcess() {
        if(m_fileLength > 0) {
            return Mathf.Clamp((float)m_currentLength / m_fileLength, 0, 1);
        }
        return 0;
    }
 
    public override long GetCurrentLength() {
        return m_currentLength;
    }
 
    public override long GetLength() {
        return m_fileLength;
    }
 
    public override void Destroy() {
    }
}
补充
上述代码用到了两个自定义的方法FileTool.CreateDirectory()和FileTool.CreatFile(),内容很简单,如下

public class FileTool {
 
    /// <summary>
    /// 创建目录
    /// </summary>
    /// <param name="filePath">需要创建的目录路径</param>
    public static void CreateDirectory(string filePath) {
        if(!string.IsNullOrEmpty(filePath)) {
            string dirName = Path.GetDirectoryName(filePath);
            if(!Directory.Exists(dirName)) {
                Directory.CreateDirectory(dirName);
            }
        }
    }
 
    /// <summary>
    /// 创建文件
    /// </summary>
    /// <param name="filePath">文件路径</param>
    /// <param name="bytes">文件内容</param>
    public static void CreatFile(string filePath, byte[] bytes) {
        FileInfo file = new FileInfo(filePath);
        Stream stream = file.Create();
 
        stream.Write(bytes, 0, bytes.Length);
 
        stream.Close();
        stream.Dispose();
    }
}
使用
public class DownloadDemo : MonoBehaviour {
 
    DownloadItem m_item;
    string testScrUrl = "http://dlsw.baidu.com/sw-search-sp/soft/ca/13442/Thunder_dl_7.9.42.5050.1449557123.exe";
    int count = 0;
 
    void Start() {
        Debug.Log(Application.persistentDataPath);
 
        //m_item = new WWWDownloadItem(testScrUrl, Application.persistentDataPath);
        //m_item.StartDownload(DownloadFinish);
 
        m_item = new HttpDownloadItem(testScrUrl, Application.persistentDataPath);
        m_item.StartDownload(DownloadFinish);
    }
 
    void Update() {
        count++;
 
        if(count % 20 == 0) {
            if(m_item != null && m_item.isStartDownload) {
                Debug.Log("下载进度------" + m_item.GetProcess() + "------已下载大小---" + m_item.GetCurrentLength());
            }
        }
    }
 
    void DownloadFinish() {
        Debug.Log("DownloadFinish!!!");
    }
}
 

补充:由于个人失误,忘记添加了UICoroutine相关的代码,深表歉意。UICoroutine在这个例子中的作用其实就是场景中新建一个GameObject,专门用来启用协程,因为前面的几个类没有继承于MonoBehaviour,所以无法直接调用StartCoroutine方法

public class UICoroutine : MonoBehaviour {
    private static UICoroutine mInstance = null;
 
    public static UICoroutine uiCoroutine {
        get {
            if(mInstance == null) {
                GameObject go = new GameObject();
                if(go != null) {
                    go.name = "_UICoroutine";
                    go.AddComponent<UICoroutine>();
                } else {
                    Debug.LogError("Init UICoroutine faild. GameObjet can not be null.");
                }
            }
            return mInstance;
        }
    }
 
    void Awake() {
        DontDestroyOnLoad(gameObject);
        mInstance = this;
    }
 
    void OnDestroy() {
        mInstance = null;
    }
}
 
————————————————
版权声明:本文为CSDN博主「王王王渣渣」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wangjiangrong/article/details/80096240

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值