網上關於斷點續傳的例子和教程呢共有很多,我結合了百家之長,經過大量測試,最終集成如下內容:
一,原理:
當下載文件時斷網了,需要將已經下載完的 存儲到本地,當下次再打開下載時,從本地下載 的位置繼續下載,即所謂斷點續傳
說一下,我使用的是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;
}
}
}