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

描述

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

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

 

基类

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

public abstract class DownloadItem {

    /// 
    /// 网络资源url路径
    /// 
    protected string m_srcUrl;
    /// 
    /// 资源下载存放路径,不包含文件名
    /// 
    protected string m_savePath;
    /// 
    /// 文件名,不包含后缀
    /// 
    protected string m_fileNameWithoutExt;
    /// 
    /// 文件后缀
    /// 
    protected string m_fileExt;
    /// 
    /// 下载文件全路径,路径+文件名+后缀
    /// 
    protected string m_saveFilePath;
    /// 
    /// 原文件大小
    /// 
    protected long m_fileLength;
    /// 
    /// 当前下载好了的大小
    /// 
    protected long m_currentLength;
    /// 
    /// 是否开始下载
    /// 
    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);
    }

    /// 
    /// 开始下载
    /// 
    /// 下载完成回调
    public virtual void StartDownload(Action callback = null) {
        if(string.IsNullOrEmpty(m_srcUrl) || string.IsNullOrEmpty(m_savePath)) {
            return;
        }
        //若存放目录不存在则创建目录
        FileTool.CreateDirectory(m_saveFilePath);
    }

    /// 
    /// 获取下载进度
    /// 
    /// 进度,0-1
    public abstract float GetProcess();

    /// 
    /// 获取当前下载了的文件大小
    /// 
    /// 当前文件大小
    public abstract long GetCurrentLength();

    /// 
    /// 获取要下载的文件大小
    /// 
    /// 文件大小
    public abstract long GetLength();

    public abstract void Destroy();
}

WWW

www的方式下载很简单,等www读取好后,直接使用www.bytes去生成文件即可。这种方式文件是一次性生成的,适合小资源的下载

/// 
/// WWW的方式下载
/// 
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.instance.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

这种方式下载,我们采用的是边读取边生成对应文件,适合较大文件下载。关于断点续传的处理,我们下载的时候,先根据读取的内容生成一个临时文件,当全部下载好后,才将这个临时文件转换成正式的文件名。若中途下载中断,已下载好的那部分文件依据在对应目录下,我们下次下载可以直接在上次下载的临时文件后添加内容。

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

/// 
/// HTTP的方式下载,支持断点续传
/// 
public class HttpDownloadItem : DownloadItem {
    /// 
    /// 临时文件后缀名
    /// 
    string m_tempFileExt = ".temp";
    /// 
    /// 临时文件全路径
    /// 
    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.instance.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 {

    /// 
    /// 创建目录
    /// 
    /// 需要创建的目录路径
    public static void CreateDirectory(string filePath) {
        if(!string.IsNullOrEmpty(filePath)) {
            string dirName = Path.GetDirectoryName(filePath);
            if(!Directory.Exists(dirName)) {
                Directory.CreateDirectory(dirName);
            }
        }
    }

    /// 
    /// 创建文件
    /// 
    /// 文件路径
    /// 文件内容
    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();
                } else {
                    Debug.LogError("Init UICoroutine faild. GameObjet can not be null.");
                }
            }
            return mInstance;
        }
    }

    void Awake() {
        DontDestroyOnLoad(gameObject);
        mInstance = this;
    }

    void OnDestroy() {
        mInstance = null;
    }
}

 

你可能感兴趣的:(Unity)