基类
首先先创建一个基类,里面存放下载需要的一些数据,例如文件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();
}
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();
}
}
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;
}
}
使用
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!!!");
}
}