unity使用unitywebrequest 實現斷點續傳 熱更新

  網上關於斷點續傳的例子和教程呢共有很多,我結合了百家之長,經過大量測試,最終集成如下內容:

一,原理:

     當下載文件時斷網了,需要將已經下載完的 存儲到本地,當下次再打開下載時,從本地下載 的位置繼續下載,即所謂斷點續傳

說一下,我使用的是unity2018.3版本,所有使用的是unitywebrequest,www的沒用測試估計很少有人用了吧。

二,核心代碼

   首先存儲功能;

 FileStream fs = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.Write);

//這裏放請求下載文件的代碼,


//當網絡斷開或者退出時候

fs.Write(req.downloadHandler.data, 0, req.downloadHandler.data.Length);

  然後當再次打開下載的時候,要先讀取當前已經下載的文件的進度和大小,進行比對

        //先拿到要下載的文件的大小,關於這點有的教程是用httpwebrequest,但是實際使用發現會有問題,大部分機型可以,但是有些電腦訪問不了,不知道具體什麼原因
        var headRequest = UnityWebRequest.Head(adress);
        yield return headRequest.SendWebRequest();
        var totalLength = long.Parse(headRequest.GetResponseHeader("Content-Length"));

        FileStream fs = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.Write);
        //獲取文件現在的長度
        fileLength = fs.Length;
        Debug.Log("fileLength:" + fileLength);

        //創建網絡請求
        UnityWebRequest req = UnityWebRequest.Get(adress);
        if (fileLength > 0)
        {
            //設置開始下載文件從什麼位置開始
            req.SetRequestHeader("Range", "bytes=" + fileLength + "-");//這句很重要
            fs.Seek(fileLength, SeekOrigin.Begin);//將該文件的指針移動到當前長度,即繼續存儲
        }
if (fileLength < totalLength)
        {
            req.SendWebRequest();
            while (!req.isDone)
            {
                //slider.value = req.downloadProgress;
                yield return null;
            }
            Debug.Log(req.downloadHandler.data.Length);

            //將本次下載得到的數據存儲到文件中
            fs.Write(req.downloadHandler.data, 0, req.downloadHandler.data.Length);
            yield return new WaitForSeconds(0.1f);
            fileLength += req.downloadHandler.data.Length;
            fs.Close();
            fs.Dispose();
            if (fileLength < totalLength)
            {
                //ShowTip("下載失敗,請重啓後重試!");
                while (true)
                {
                    yield return null;
                }
            }
        }

三,小結

  斷點續傳還是比較簡單,以上就是採坑後發現的最終版,目前尚未發現問題,如有問題歡迎指出!

完整代碼如下;包含MD5校驗,熱更新功能!

using ICSharpCode.SharpZipLib.Zip;
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;




/// <summary>
/// 包體信息
/// </summary>
public class BundleInfo
{
    public string path;
    public string name;
    public int length;
    public string MD5;
}
/// <summary>
/// 下載包的信息
/// </summary>
public class DownLoadInfo
{
    public BundleInfo bundle;
    public long size;
    public string DownLoadPath;
    public string SavaPath;
    public string Fodler;
}
/// <summary>
/// 資源下載,檢查,更新
/// </summary>
public class Driver : MonoBehaviour
{
    /// <summary>
    /// 連接方式
    /// </summary>
    public URLType urlType = URLType.Project;

    /// <summary>
    /// 運行平臺
    /// </summary>
    public BuildPlat runPlatm = BuildPlat.Windows64;

    /// <summary>
    /// 資源加載方式
    /// </summary>
    public LoadMode loadMode = LoadMode.Resources;

    /// <summary>
    /// 加載進度條
    /// </summary>
    public Slider slider;
    /// <summary>
    /// 文本框
    /// </summary>
    public Text infoTex,tipTex,speedtext;

    /// <summary>
    /// 提示
    /// </summary>
    public GameObject Tip;

    /// <summary>
    /// 遊戲管理入口
    /// </summary>
    public GameObject Gameobj;

    private List<string> needDeCompress = new List<string>();//本次需要解壓的資源包

    private WaitForSeconds wait;
    private List<BundleInfo> hasDownLoad;//已經下載過的包體
    private long TotalLength;//需要下載的總大小
    private long hasDownloadLength;//已經下載的大小

    private void Awake()
    {    
        //運行設置
        Application.runInBackground = true;//後臺運行
        Screen.sleepTimeout = SleepTimeout.NeverSleep;//禁用休眠
        Application.targetFrameRate = 40;//限制幀率
        Screen.fullScreen = true;//啓用全屏
    }
    private IEnumerator Start()
    { 
   
        wait = new WaitForSeconds(0.01f);

        switch (loadMode)
        {
            case LoadMode.Resources:
                DestroySelf();
                break;
            case LoadMode.ABundle:
                yield return Init(urlType, runPlatm);
                break;
        }
    }
    /// <summary>
    /// 顯示提示信息
    /// </summary>
    /// <param name="str"></param>
    private void ShowTip(string str)
    {
        Tip.gameObject.SetActive(true);
        tipTex.text = str;
    }

    /// <summary>
    /// 初始化
    /// </summary>
    /// <param name="urlType"></param>
    /// <param name="runPlatm"></param>
    /// <param name="loading"></param>
    /// <returns></returns>
    public IEnumerator Init(URLType urlType, BuildPlat runPlatm)
    {
        ConfigSetting.urlType = urlType;
        ConfigSetting.RunPlat = runPlatm;
        needDeCompress.Clear();
        if (urlType != URLType.Project)
        {
            if (Application.internetReachability == NetworkReachability.NotReachable)
            {
                Debug.Log("無網絡連接。");
                ShowTip("網絡異常,請檢查網絡!");
                while (true)
                {
                    yield return null;
                }
            }
            yield return StartCoroutine(CheckMD5());
        }
        else
        {
            Debug.Log("加載本地文件");
        }
        yield return wait;
        Debug.Log("加載完畢!!");
    }


    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    private IEnumerator CheckMD5()
    {
        TotalLength = 0;
#if UNITY_ANDROID && !UNITY_EDITOR
        if (!File .Exists(ConfigSetting.CopyVersionPath))
        {
            Debug.Log("拷貝版本文件");
            //現將streamingassets下的版本文件拷貝到永久目錄
            string from = Application.streamingAssetsPath + "/version.txt"; ;
            WWW www = new WWW(from);
            yield return www;
            Debug.Log("error:" + www.error + "\n" + www.text);
            if (www.isDone)
            {
                Debug.Log("state:" + www.bytes.Length);
                //拷貝到指定路徑
                File.WriteAllBytes(ConfigSetting.CopyVersionPath, www.bytes);
            }
            yield return new WaitForSeconds(0.05f);
        }
#endif
        hasDownLoad = new List<BundleInfo>();
        string path = ConfigSetting.SeverAdress + "version.txt";
        Debug.Log("版本下載地址:" + path);
        UnityWebRequest req = UnityWebRequest.Get(path);
        yield return req.SendWebRequest();
        Debug.Log(path + "  ----  " + req.error);
        if (!string.IsNullOrEmpty(req.error))
        {
            ShowTip("服務器異常!!,請退出重試!!");
            while (true)
            {
                yield return null;
            }
        }
        string streamingpath;
        List<BundleInfo> localinfos;
        string msg = req.downloadHandler.text;
        Debug.Log("MSG:" + msg);
        List<BundleInfo> severinfos = JsonMapper.ToObject<List<BundleInfo>>(msg);
        streamingpath = ConfigSetting.CopyVersionPath;
#if UNITY_ANDROID && !UNITY_EDITOR
        string text = File.ReadAllText(streamingpath);
        Debug.Log("讀取:" + text);
        localinfos = JsonMapper.ToObject<List<BundleInfo>>(text);
        //yield return new WaitForSeconds(0.05f);
#else
        // streamingpath = Application.streamingAssetsPath + "/version.txt";//"jar:file://" + Application.dataPath + "!/assets" + "/" + ConfigSetting.RunPlat.ToString() + "/version.txt";
        Debug.Log("讀取路徑:" + streamingpath);
        UnityWebRequest loadVersion = UnityWebRequest.Get(streamingpath);
        yield return loadVersion.SendWebRequest();
        hasDownLoad = localinfos = JsonMapper.ToObject<List<BundleInfo>>(loadVersion.downloadHandler.text);
#endif
        List<DownLoadInfo> needdownload = new List<DownLoadInfo>();//需要下載 的列表
        List<DownLoadInfo> add = new List<DownLoadInfo>();//新增資源
        for (int i = 0; i < severinfos.Count; i++)
        {
            bool contain = false;
            for (int j = 0; j < localinfos.Count; j++)
            {
                if (localinfos[j].path == severinfos[i].path)
                {
                    if (localinfos[j].MD5 != severinfos[i].MD5)
                    {
                        DownLoadInfo info = new DownLoadInfo();
                        info.bundle = severinfos[i];
                        info .DownLoadPath = ConfigSetting.SeverAdress + severinfos[i].path;
                        info .SavaPath = ConfigSetting.Copy_OutPath + severinfos[i].path;
                        int bundleindex = info.SavaPath.LastIndexOf('/');
                        info .Fodler = info.SavaPath.Remove(bundleindex, info.SavaPath.Length - bundleindex);
                        var headRequest = UnityWebRequest.Head(info.DownLoadPath);
                        yield return headRequest.SendWebRequest();
                        info .size = long.Parse(headRequest.GetResponseHeader("Content-Length"));
                        Debug.Log("文件大小:"+info.size);
                        TotalLength += info.size;
                        needdownload.Add(info);
                        hasDownLoad.Remove(localinfos[j]);
                    }
                    contain = true;
                }

                if (j > 0 )
                    slider.value = j / severinfos.Count;
            }
            if (!contain)
            {
                DownLoadInfo info = new DownLoadInfo();
                info.bundle = severinfos[i];
                info.DownLoadPath = ConfigSetting.SeverAdress + severinfos[i].path;
                info.SavaPath = ConfigSetting.Copy_OutPath + severinfos[i].path;
                int bundleindex = info.SavaPath.LastIndexOf('/');
                info.Fodler = info.SavaPath.Remove(bundleindex, info.SavaPath.Length - bundleindex);
                var headRequest = UnityWebRequest.Head(info.DownLoadPath);
                yield return headRequest.SendWebRequest();
                info.size = long.Parse(headRequest.GetResponseHeader("Content-Length"));
               
                TotalLength += info.size;
                add.Add(info);
            }
        }
        if (File.Exists(ConfigSetting.DeCompressInfo))
            needDeCompress = JsonMapper.ToObject<List<string>>(File.ReadAllText(ConfigSetting.DeCompressInfo));

        if (add.Count == 0 && needdownload.Count == 0)
        {
            Debug.Log("無需更新,準備檢查資源!");
            if (File.Exists(ConfigSetting.DeCompressInfo))
            {
                //if (onLoading != null)
                //    onLoading.Invoke(1f, "無需更新,準備檢查資源!");

            }
            else
            {
                infoTex.text = "檢查完畢,進入遊戲!"; 
                yield break;
            }
        }
        else
        {
            FileInfo fileInfo = null;
            FileStream fs = null;
            //更新資源
            for (int i = 0; i < needdownload.Count; i++)
            {
                //zip包下載
                yield return DownLoad(needdownload[i]);
                continue;
                #region        //ab包下載方式
                //Debug.Log("下載路徑:" + loadpath);
                //UnityWebRequest uwr = UnityWebRequest.Get(loadpath);
                //yield return uwr.SendWebRequest();
                //while (uwr.downloadProgress < 1.0f)
                //{
                //    yield return null;
                //}
                //byte[] results = uwr.downloadHandler.data;
                ////fs.Write(字節數組, 開始位置, 數據長度);
                //fs.Write(results, 0, results.Length);
                //hasDownLoad.Add(needdownload[i]);
                //fs.Flush();     //文件寫入存儲到硬盤
                //yield return new WaitForEndOfFrame();
                //fs.Close();     //關閉文件流對象
                //fs.Dispose();   //銷燬文件對象
                #endregion
               
            }
            //新增資源
            for (int i = 0; i < add.Count; i++)
            {
                Debug.Log("需要下載:" + add[i].bundle.path + "\n 路徑:" + add[i].SavaPath);
                //if (add[i].bundle .path.Contains("//"))
                //{
                //    int index = add[i].bundle.path.LastIndexOf('/');
                //    string fodler = ConfigSetting.Copy_OutPath + add[i].bundle.path.Remove(index, add[i].bundle.path.Length - index);
                //    Debug.Log("文件夾:" + fodler);
                //    if (!Directory.Exists(fodler))
                //    {
                //        Debug.Log("創建文件夾:" + fodler);
                //        Directory.CreateDirectory(fodler);
                //    }
                //}
                if (!Directory.Exists(add [i].Fodler))
                {
                    Debug.Log("創建文件夾:" + add[i].Fodler);
                    Directory.CreateDirectory(add[i].Fodler);
                }
                //zip包下載
                //infoTex.text = "正在下載資源   " + i + "/" + add.Count;
                yield return DownLoad(add[i]);
                continue;
                #region  下載ab
                //fileInfo = new FileInfo(filepath);
                //fs = fileInfo.Create();
                //UnityWebRequest uwr = UnityWebRequest.Get(loadpath);
                //yield return uwr.SendWebRequest();
                //while (uwr.downloadProgress < 1.0f)
                //{
                //    yield return null;
                //}
                //byte[] results = uwr.downloadHandler.data;
                //Debug.Log("下載數據:" + results.Length);
                ////fs.Write(字節數組, 開始位置, 數據長度);
                //fs.Write(results, 0, results.Length);
                //hasDownLoad.Add(add[i]);
                //fs.Flush();     //文件寫入存儲到硬盤
                //fs.Close();     //關閉文件流對象
                //fs.Dispose();   //銷燬文件對象
                //yield return new WaitForEndOfFrame();
                //fileInfo = null;
                //fs = null;
            
                #endregion
            }

            speedtext.text = "";

            if (hasDownLoad.Count > 0)
            {
                string str = JsonMapper.ToJson(hasDownLoad);
                //Debug.Log(str);
                //存儲下載文件
                File.WriteAllText(ConfigSetting.CopyVersionPath, str);
                //存儲需要解壓的文件
                File.WriteAllText(ConfigSetting.DeCompressInfo, JsonMapper.ToJson(needDeCompress));
                hasDownLoad.Clear();
            }
        }
        int count = needDeCompress.Count;
        while (needDeCompress.Count > 0)
        {
            yield return SaveZip(needDeCompress[0], count);
            needDeCompress.RemoveAt(0);
        }
        Debug.Log("剩餘需要解壓個數:" + needDeCompress.Count);
        if (needDeCompress.Count == 0)
        {
            if (File.Exists(ConfigSetting.DeCompressInfo))
                File.Delete(ConfigSetting.DeCompressInfo);
        }
        else
        {
            //更新
            File.WriteAllText(ConfigSetting.DeCompressInfo, JsonMapper.ToJson(needDeCompress));
        }
        infoTex.text = "即將進入遊戲!!";
    }
 
    /// <summary>
    /// 開始下載zip包
    /// </summary>
    /// <param name="adress"></param>
    /// <param name="savepath"></param>
    /// <returns></returns>
    private IEnumerator DownLoad(DownLoadInfo bundle)
    {
        Debug.Log("downloadpath: " + bundle.DownLoadPath );
        Debug.Log("savepath:" + bundle .SavaPath );
        
        Debug.Log("fodler:" + bundle .Fodler);
        if (!Directory.Exists(bundle.Fodler))
        {
            Directory.CreateDirectory(bundle.Fodler);
        }
        
        UnityWebRequest req = UnityWebRequest.Get(bundle.DownLoadPath);
        long fileLength = 0;
        //獲取下載文件的總長度
        Debug.Log("totalLength:" + bundle.size );//ConfigSetting.Copy_OutPath + ConfigSetting.AssetName;
        FileStream fs = new FileStream(bundle .SavaPath , FileMode.OpenOrCreate, FileAccess.Write);
        //獲取文件現在的長度
        fileLength = fs.Length;
        Debug.Log("fileLength:" + fileLength);
        if (fileLength > 0)
        {
            //設置開始下載文件從什麼位置開始
            req.SetRequestHeader("Range", "bytes=" + fileLength + "-");//這句很重要
            fs.Seek(fileLength, SeekOrigin.Begin);//將該文件的指針移動到當前長度,即繼續存儲
        }
        if (fileLength < bundle .size)
        {
            //float progress = 0;
            long hasload = 0;
            req.SendWebRequest();
            float usedTime = 0;
            long lastSecLoad = 0;
            while (!req.isDone)
            {
                usedTime += Time.deltaTime;
                if(usedTime >= 1)
                {
                    usedTime = 0;
                    speedtext.text  = GetSize((long)req.downloadedBytes - lastSecLoad) + "/s";
                    lastSecLoad = (long)req.downloadedBytes;
                }
                hasload = hasDownloadLength + (long)req.downloadedBytes;
                if (hasload > 0)
                {
                    slider.value = (float)hasload  / TotalLength;
                    infoTex.text = "正在下載資源 " + GetSize(hasload) + "/" + GetSize(TotalLength);
                }
                yield return null;
            }
            hasDownloadLength += (long)req.downloadedBytes;
            Debug.Log(req.downloadHandler.data.Length);
           
            //將本次下載得到的數據存儲到文件中
            fs.Write(req.downloadHandler.data, 0, req.downloadHandler.data.Length);
            yield return new WaitForSeconds(0.1f);
            fileLength += req.downloadHandler.data.Length;
            fs.Close();
            fs.Dispose();
            if (fileLength < bundle .size)
            {
                ShowTip("下載失敗,請重啓後重試!");
                while (true)
                {
                    yield return null;
                }
            }
        }
        hasDownLoad.Add(bundle .bundle);
        needDeCompress.Add(bundle .SavaPath);
    }

    Stream readSteam;
    /// <summary> 
    /// 解壓功能(下載後直接解壓壓縮文件到指定目錄) 
    /// </summary> 
    /// <param name="zipedFolder">指定解壓目標目錄(每一個Obj對應一個Folder)</param> 
    /// <param name="password">密碼</param> 
    /// <returns>解壓結果</returns> 
    private IEnumerator SaveZip(string zipPath, int index)
    {
        Debug.Log("開始解壓:" + zipPath);
        if (!File.Exists(zipPath))
        {
            Debug.Log("已經解壓過了,無需解壓  \n " + zipPath);
            yield break;
        }
        //byte[] ZipByte = File.ReadAllBytes(savepath);
        ZipInputStream zipStream = null;
        ZipEntry entry = null;
        string fileName;
        int length = zipPath.LastIndexOf('/');
        string fodlerPath = zipPath.Remove(length, zipPath.Length - length);
        Debug.Log("文件夾: " + fodlerPath);
        if (!Directory.Exists(fodlerPath))
        {
            Directory.CreateDirectory(fodlerPath);
        }

        //直接使用 
        readSteam = new FileStream(zipPath, FileMode.Open, FileAccess.Read);//new MemoryStream(ZipByte);
        zipStream = new ZipInputStream(readSteam);
        byte[] data;
        int size = 2048;
        float saved = 0;
        while ((entry = zipStream.GetNextEntry()) != null)
        {
            //Debug.Log("總數據:" + zipStream.Length);
            data = new byte[zipStream.Length];
            saved += zipStream.Length;
            fileName = fodlerPath + "/" + entry.Name;
            if (entry.IsDirectory)
            {
                if (!Directory.Exists(fileName))
                {
                    Directory.CreateDirectory(fileName);
                }
            }
            else if (!string.IsNullOrEmpty(entry.Name))
            {
                Debug.Log("filename: " + entry.Name);
                if (entry.Name.Contains("\\") || entry.Name.Contains("/"))
                {
                    string[] array = entry.Name.Split("\\".ToCharArray());
                    string parentDir = fodlerPath + "/" + array[0];
                    for (int i = 1; i < array.Length - 1; i++)
                    {
                        parentDir += "/";
                        parentDir += array[i];
                    }
                    Debug.Log("所屬文件夾:" + parentDir);
                    if (!Directory.Exists(parentDir))
                    {
                        Debug.Log("創建文件夾:" + parentDir);
                        Directory.CreateDirectory(parentDir);
                    }
                    fileName = parentDir + "/" + array[array.Length - 1];
                }
                Debug.Log("savepath:" + fileName);
                ;
                //WaitForSeconds sec = new WaitForSeconds(0.0001f);
                using (FileStream s = File.Create(fileName))
                {
                    //size = zipStream.Read(data, 0, data.Length);
                    //s.Write(data, 0, size);
                    while (true)
                    {
                        size = zipStream.Read(data, 0, data.Length);
                        if (size <= 0) break;
                        s.Write(data, 0, size);
                        //yield return null;
                        saved += 2048;
                        slider.value = saved / readSteam.Length;
                        ////Debug.Log(saved / zipStream.Length);
                        //if (onLoading != null)
                        //{
                        //    onLoading.Invoke(saved / stream.Length, "正在解壓 " + (index - needDeCompress.Count + 1) + "/" + index);
                        //}
                    }
                    //saved = 0;
                    s.Close();
                }
                yield return null;
            }
            infoTex.text = "正在解壓... " + (index - needDeCompress.Count + 1) + "/" + index;
            yield return null;
        }
        zipStream.Close();
        readSteam.Close();
        readSteam = null;
        yield return wait;
        Debug.Log("解壓完成");
        File.Delete(zipPath);
    }


    /// <summary>
    /// 獲取
    /// </summary>
    /// <param name="b"></param>
    /// <returns></returns>
    public string GetSize(long b)
    {
        if (b.ToString().Length <= 10)
            return GetMB(b);
        if (b.ToString().Length >= 11 && b.ToString().Length <= 12)
            return GetGB(b);
        if (b.ToString().Length >= 13)
            return GetTB(b);
        return String.Empty;
    }

    /// <summary>
    /// 將B轉換爲TB
    /// </summary>
    /// <param name="b"></param>
    /// <returns></returns>
    private string GetTB(long b)
    {
        for (int i = 0; i < 4; i++)
        {
            b /= 1024;
        }
        return b + "TB";
    }

    /// <summary>
    /// 將B轉換爲GB
    /// </summary>
    /// <param name="b"></param>
    /// <returns></returns>
    private string GetGB(long b)
    {
        for (int i = 0; i < 3; i++)
        {
            b /= 1024;
        }
        return b + "GB";
    }

    /// <summary>
    /// 將B轉換爲MB
    /// </summary>
    /// <param name="b"></param>
    /// <returns></returns>
    private string GetMB(long size)
    {
        float count = (float)size; 
        if (count < 1024)
            return count.ToString("f2") + "b";
        if (count < 1024 * 1024)
            return (count / 1024).ToString("f2") + "kb";
       
        for (int i = 0; i < 2; i++)
        {
            count /= 1024;
        }
        return count.ToString("f2") + "MB";
    }
    /// <summary>
    /// 將B轉換爲KB
    /// </summary>
    /// <param name="b"></param>
    /// <returns></returns>
    private string GetKB(long size)
    {
        size /= 1024;
        return size + "KB";
    }

    public void Quit()
    {
#if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
#endif
        Application.Quit();
    }

    private void OnDestroy()
    {
        if (urlType != URLType.Project && hasDownLoad.Count > 0)
        {
            string str = JsonMapper.ToJson(hasDownLoad);
            //Debug.Log(str);
            //存儲下載文件
            File.WriteAllText(ConfigSetting.CopyVersionPath, str);
            //存儲需要解壓的文件
            File.WriteAllText(ConfigSetting.DeCompressInfo, JsonMapper.ToJson(needDeCompress));
            hasDownLoad.Clear();
        }
        if(readSteam !=null)
        {
            readSteam.Close();
            readSteam = null;
        }
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章