一、功能設(shè)計(jì)
文件下載器的作用
????????文件下載器是應(yīng)用程序的基礎(chǔ)模塊,為應(yīng)用程序與外部網(wǎng)絡(luò)交互提供了必要的橋梁。該模塊設(shè)計(jì)初衷是為了熱更新過程中,下載CDN站點(diǎn)上的文件資源,所以下載器會(huì)驗(yàn)證 要下載的文件是否存在于CDN中。如果存在允許下載器繼續(xù)工作;如果不存在會(huì)跳過本地下載。做這層檢測(cè)是為了安全性考慮,不允許隨意下載網(wǎng)絡(luò)資源。如果有需求可以跳過這層檢測(cè)。
什么是斷點(diǎn)續(xù)傳
????????下載文件時(shí),不必重頭開始下載,而是從上次中斷的位置繼續(xù)下載,這樣的功能就叫做斷點(diǎn)續(xù)傳。
斷點(diǎn)續(xù)傳的作用
????????在下載文件的過程中,打斷文件下載的原因有很多,比如網(wǎng)絡(luò)不穩(wěn)定,導(dǎo)致下載,中斷如果沒有斷點(diǎn)續(xù)傳功能的話,中斷之后需要重新開始下載。例如一個(gè)文件有100M大小,我下載了99M,馬上就要下載完成了,這是突然網(wǎng)絡(luò)中斷導(dǎo)致下載失敗了,我重新開始下載的時(shí)候發(fā)現(xiàn)又需要重新開始下載,這時(shí)候是不是會(huì)感覺心態(tài)崩了,如果有了斷點(diǎn)續(xù)傳功能的話,我下載了99M,即使網(wǎng)絡(luò)中斷,重連之后我的下載依舊是從99M的位置開始下載,這樣給用戶的體驗(yàn)就很棒了。
斷點(diǎn)續(xù)傳實(shí)現(xiàn)思路
1. 在下載文件的時(shí)候我們會(huì)先創(chuàng)建一個(gè)與下載文件對(duì)應(yīng)的以.temp為后綴的臨時(shí)文件, 下載的文件數(shù)據(jù)會(huì)寫入這個(gè)臨時(shí)文件中。
2. 每次開始下載的時(shí)候會(huì)檢查是否存在需下載文件的臨時(shí)文件,如果存在,便從該文件數(shù)據(jù)長(zhǎng)度的地方開始下載寫入。
3. 下載完成后便將臨時(shí)文件移動(dòng)到目標(biāo)下載目錄。
?
二、代碼設(shè)計(jì)
下載器在物理結(jié)構(gòu)上切分成了4個(gè)文件模塊,每個(gè)模塊各司其職。
文章在講解的時(shí)候會(huì)挑選模塊內(nèi)的主要的函數(shù)來講解。
文章格式按照
? ? ? ? 模塊中文名 類名
? ? ? ? ? ? ? ? 函數(shù):函數(shù)中文名 函數(shù)名
? ? ? ? ? ? ? ? ? ? ? ? 具體作用解釋。
具體的函數(shù)實(shí)現(xiàn)可以去改模塊底部完整代碼部分,根據(jù)函數(shù)名搜索該函數(shù)即可。函數(shù)里也對(duì)每一句話添加了注釋。如果還是不懂的可以私信我。
下載器回調(diào) DownloadHandler
?+ 函數(shù):下載數(shù)據(jù)回調(diào)?OnReceiveDataAction
????????OnReceiveDataAction重寫了DownloadHandlerScript內(nèi)的函數(shù)。其主要作用就是我們程序內(nèi)部需要拿到下載的進(jìn)度數(shù)據(jù)。
DownloadHandler.cs 完整代碼
public class DownloadHandler : DownloadHandlerScript
{
//下載速度限制1024KB
const int DownLoadKB = 1024;
//初始化下載句柄,定義每次下載的數(shù)據(jù)上線為 DownLoadKB KB
//單位:字節(jié) ,字節(jié) = 1024字節(jié) * DownLoadKB (轉(zhuǎn)換成字節(jié)單位)
public DownloadHandler() : base(new byte[1024 * DownLoadKB])
{
}
//接收到數(shù)據(jù)的委托
public BaseAction<byte[], int> OnReceiveDataAction;
protected override bool ReceiveData(byte[] data, int dataLength)
{
if (null == data || dataLength == 0)
return false;
OnReceiveDataAction?.Invoke(data, dataLength);
return true;
}
}
單文件下載器 DownloadRoutine
- 函數(shù):下載存盤 Save
下載文件中會(huì)調(diào)用,會(huì)檢查當(dāng)前內(nèi)存緩存是否達(dá)到數(shù)據(jù)落地要求,如果達(dá)到,往硬盤里寫入一次文件。
- 函數(shù):斷點(diǎn)下載?Download(string url, uint beginPos)
斷點(diǎn)下載函數(shù),從指定字節(jié)處下載這個(gè)文件。
- 函數(shù):開始下載 BeginDownload
檢查下載目錄是否存在,不存在則創(chuàng)建;保存下載文件MD5;拼接真實(shí)鏈接;
- 函數(shù):內(nèi)部下載?DownloadInner
有相同文件比較MD5是否相同,若不同刪除重新下載;定位斷點(diǎn)續(xù)傳文件下載位置;
+ 函數(shù):下載 Download(string url)
從頭下載文件
+ 函數(shù):開始下載 BeginDownload
外部調(diào)用函數(shù),會(huì)把相對(duì)url、資源信息、更新、完成回調(diào)都傳入給該對(duì)象
+ 函數(shù):更新 OnUpdate
下載中回調(diào);下載失敗重試;下載完成操作;
+ 函數(shù):重置?Reset
關(guān)閉WebRequest下載器;接觸文件占用;變量重置;
+ 函數(shù):釋放 Dispose
對(duì)象或程序生命周期結(jié)束時(shí)調(diào)用,內(nèi)部調(diào)用了 Reset還原類內(nèi)類內(nèi)成員的狀態(tài)。
DownloadRoutine.cs 完整代碼
//文件下載器
public class DownloadRoutine:IDisposable
{
//Web請(qǐng)求(存了web的連接)
private UnityWebRequest m_UnityWebRequest = null;
//文件流(寫入文件使用)
private FileStream m_FileStream;
//當(dāng)前等待寫入磁盤的大?。ǔ^閾值才會(huì)把這一部分寫入文件的尾部)
private int m_CurrWaitFlushSize = 0;
//上次寫入的大?。ㄉ洗螌懭胛募鞯娜看笮。? private int m_PrevWriteSize = 0;
//文件總大小
private ulong m_TotalSize;
//當(dāng)前下載的大小(下載了多少了)
private ulong m_CurrDownloadSize = 0;
//起始位置
private uint m_BeginPos = 0;
//當(dāng)前下載文件的鏈接(url)
private string m_CurrFileUrl;
//下載到的本地路徑
private string m_DownloadLocalFilePath;
//下載中的委托(string:url,ulong:下載的大小,float:下載百分比)
private BaseAction<string, ulong, float> m_OnUpdate;
//下載完畢回調(diào)
private BaseAction<string, DownloadRoutine> m_OnComplete;
//當(dāng)前的資源包信息(*:是信息,不是文件實(shí)體;如果不是資源包,是其他的文件怎么辦?比如mp4就不能下載了是嗎?必須要把資源壓到ab包里)
private AssetBundleInfoEntity m_CurrAssetBundleInfo;
//當(dāng)前重試次數(shù)
private int m_CurrRetry = 0;
//上次重試時(shí)間
private float m_PrevRetryTime = 0;
//下載句柄(這個(gè)也是繼承unity的然后自己封裝的一層)
private DownloadHandler m_DownloadHandler;
/*
* 功能:保存字節(jié)
* buffer:文件流
* downloadComplete:是否下載完成
* bufferCount:文件流的總長(zhǎng)度(單位:字節(jié))
*/
private void Save(byte[] buffer, bool downloadComplete = false, int bufferCount = 0)
{
if (null == buffer)
return;
//len是文件流的總長(zhǎng)度
int len = buffer.Length;
//文件流的總長(zhǎng)度-上一次寫入的大小 = 這次要寫入多少?
int count = len - m_PrevWriteSize;
//m_FileStream?.Write(buffer,m_PrevWriteSize,count);
//把偏移寫入的方法,換成全量寫入了?
m_FileStream?.Write(buffer, 0, bufferCount);
m_PrevWriteSize = len;
m_CurrWaitFlushSize += count;
//內(nèi)存中下載的文件大小超過了 FlushSize(2048k)*1024 =多少字節(jié)。 到達(dá)指定的字節(jié)數(shù) || 下載完成 直接把流數(shù)據(jù)Append到文件尾部
if (m_CurrWaitFlushSize >= GameEntry.Download.FlushSize * 1024 || downloadComplete)
{
m_CurrWaitFlushSize = 0;
//內(nèi)存緩沖區(qū)中的數(shù)據(jù)流,立即寫入磁盤
m_FileStream.Flush();
}
}
//接受到數(shù)據(jù)后(接受到網(wǎng)絡(luò)流中的數(shù)據(jù)后,會(huì)回調(diào)應(yīng)用層,然后應(yīng)用層會(huì)毀掉我們自己封裝的這個(gè)函數(shù))
//沒下載完成
private void DownloadHandlerReceiveDataCallBack(byte[] buffer, int length)
{
Save(buffer, false, length);
}
/*
* 函數(shù)功能:下載
* url:文件鏈接
* beginPos:該文件的起始下載位置 單位:字節(jié)(斷點(diǎn)續(xù)傳功能使用)
*/
public void Download(string url, uint beginPos)
{
//開啟web Request
m_UnityWebRequest = UnityWebRequest.Get(url);
//實(shí)例化下載器
m_DownloadHandler = new DownloadHandler();
//注冊(cè)下載器回調(diào)
m_DownloadHandler.OnReceiveDataAction += DownloadHandlerReceiveDataCallBack;
//m_UnityWebRequest內(nèi)的下載器使用自定義的下載器
m_UnityWebRequest.downloadHandler = m_DownloadHandler;
//下載器釋放的時(shí)候 webRequest 也 跟著釋放
m_UnityWebRequest.disposeDownloadHandlerOnDispose = true;
//定位下載的位置,從文件的哪部分開始下載(單位:字節(jié))
string headerValue = string.Format("bytes={0}-", beginPos.ToString());
m_UnityWebRequest.SetRequestHeader("Range", headerValue);
//發(fā)起請(qǐng)求
m_UnityWebRequest.SendWebRequest();
}
public void Download(string url)
{
//開啟web Request
m_UnityWebRequest = UnityWebRequest.Get(url);
//實(shí)例化下載器
m_DownloadHandler = new DownloadHandler();
//注冊(cè)下載器回調(diào)
m_DownloadHandler.OnReceiveDataAction += DownloadHandlerReceiveDataCallBack;
//m_UnityWebRequest內(nèi)的下載器使用自定義的下載器
m_UnityWebRequest.downloadHandler = m_DownloadHandler;
//下載器釋放的時(shí)候 webRequest 也 跟著釋放
m_UnityWebRequest.disposeDownloadHandlerOnDispose = true;
//發(fā)起請(qǐng)求
m_UnityWebRequest.SendWebRequest();
}
//進(jìn)行下載
private void BeginDownload()
{
//目錄
string directory = Path.GetDirectoryName(m_DownloadLocalFilePath);
//文件不存在,就創(chuàng)建一個(gè)
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
m_FileStream = new FileStream(m_DownloadLocalFilePath,FileMode.Create,FileAccess.Write);
PlayerPrefs.SetString(m_CurrFileUrl,m_CurrAssetBundleInfo.MD5);
//開始下載
string url = string.Format("{0}{1}", GameEntry.Data.SysDataManager.CurrChannelConfig.RealSourceUrl, m_CurrFileUrl);
Download(url);
}
//內(nèi)部下載
private void DownloadInner()
{
//本地是否有該文件
if (File.Exists(m_DownloadLocalFilePath))
{
//驗(yàn)證md5,如果本地文件的md5和cdn的md5不一致,刪除本地文件,重新下載
if (PlayerPrefs.HasKey(m_CurrFileUrl))
{
//驗(yàn)證
if (!PlayerPrefs.GetString(m_CurrFileUrl).
Equals(m_CurrAssetBundleInfo.MD5, StringComparison.CurrentCultureIgnoreCase))
{
//本地文件和cdn md5不一致 刪除本地文件
File.Delete(m_DownloadLocalFilePath);
BeginDownload();
}
else
{
//文件一致,打開文件
m_FileStream = File.OpenWrite(m_DownloadLocalFilePath);
//光標(biāo)定位到文件的最后
m_FileStream.Seek(0,SeekOrigin.End);
//開始位置設(shè)置成文件的長(zhǎng)度
m_BeginPos = (uint)m_FileStream.Length;
//開始下載,直接成功(只是走了一邊下載的流程,其實(shí)根本沒下載)
string url = string.Format("{0}{1}",GameEntry.Data.SysDataManager.CurrChannelConfig.RealSourceUrl,m_CurrFileUrl);
Download(url,m_BeginPos);
}
}
}
else
{
BeginDownload();
}
}
//開始下載
public void BeginDownload(string url, AssetBundleInfoEntity assetBundleInfoEntity,
BaseAction<string, ulong, float> onUpdate = null,
BaseAction<string, DownloadRoutine> onComplete = null)
{
m_CurrFileUrl = url;
m_CurrAssetBundleInfo = assetBundleInfoEntity;
m_OnUpdate = onUpdate;
m_OnComplete = onComplete;
m_DownloadLocalFilePath = string.Format("{0}/{1}",GameEntry.Resource.LocalFilePath,m_CurrFileUrl);
//如果本地有這個(gè)文件,先刪除
if (File.Exists(m_DownloadLocalFilePath))
{
File.Delete(m_DownloadLocalFilePath);
}
m_DownloadLocalFilePath = m_DownloadLocalFilePath + ".temp";
//如果通過這個(gè)函數(shù)調(diào)DownloadInnder,本地一定不會(huì)有這個(gè)文件了啊
DownloadInner();
}
public void OnUpdate()
{
if (null == m_UnityWebRequest)
return;
//如果進(jìn)行重試了,判斷重試間隔
if (m_CurrRetry > 0 && Time.time < m_PrevRetryTime + GameEntry.Download.RetryInterval)
return;
//大小=0,獲取web中的內(nèi)容大小數(shù)據(jù)
if (m_TotalSize == 0)
ulong.TryParse(m_UnityWebRequest.GetResponseHeader("Content-Length"),out m_TotalSize);
//下載沒完成
if (!m_UnityWebRequest.isDone)
{
//使用 unityWebRequest里面的下載字節(jié)大小
if (m_CurrDownloadSize < m_UnityWebRequest.downloadedBytes)
{
m_CurrDownloadSize = m_UnityWebRequest.downloadedBytes;
//通知更新
m_OnUpdate?.Invoke(m_CurrFileUrl,m_CurrDownloadSize,m_CurrDownloadSize/(float)m_TotalSize);
}
return;
}
//網(wǎng)絡(luò)錯(cuò)誤 || http 請(qǐng)求錯(cuò)誤
if (m_UnityWebRequest.isNetworkError || m_UnityWebRequest.isHttpError)
{
++m_CurrRetry;
m_PrevRetryTime = Time.time;
//大于了重試次數(shù)
if (m_CurrRetry > GameEntry.Download.Retry)
{
Reset();
GameEntry.Log(LogCategory.Resource, "下載完畢url=>{0} 失敗 當(dāng)前重試次數(shù){1}", m_UnityWebRequest.url, m_CurrRetry);
//嘗試重新下載
DownloadInner();
return;
}
GameEntry.Log(LogCategory.Resource, "下載完畢url=>{0} error=>{1}", m_UnityWebRequest.url, m_UnityWebRequest.error);
Reset();
}
else
{
m_CurrDownloadSize = m_UnityWebRequest.downloadedBytes;
//最后再更新一次
m_OnUpdate?.Invoke(m_CurrFileUrl,m_CurrDownloadSize, m_CurrDownloadSize/(float)m_TotalSize);
GameEntry.Log(LogCategory.Resource,"下載完畢url=>{0}",m_UnityWebRequest.url);
Reset();
//好像File里面沒有Rename操作,把a(bǔ)/b/c/xxx.ab.temp 移動(dòng)到a/b/c/xxx.ab
//這好像是個(gè)改名的騷操作??!
File.Move(m_DownloadLocalFilePath, m_DownloadLocalFilePath.Replace(".temp",""));
m_DownloadLocalFilePath = null;
if (PlayerPrefs.HasKey(m_CurrFileUrl))
{
PlayerPrefs.DeleteKey(m_CurrFileUrl);
}
//這個(gè)文件 寫入本地版本文件信息
GameEntry.Resource.ResManager.SaveVersion(m_CurrAssetBundleInfo);
//回調(diào)OnComplete函數(shù)
m_OnComplete?.Invoke(m_CurrFileUrl,this);
}
}
//重置對(duì)象信息(我認(rèn)為每個(gè)可以被回收的類,都應(yīng)該有Reset)
public void Reset()
{
if (null != m_UnityWebRequest)
{
//中斷連接|下載
m_UnityWebRequest.Abort();
//釋放 m_UnityWebRequest 內(nèi)部資源
m_UnityWebRequest.Dispose();
m_UnityWebRequest = null;
}
if (null != m_DownloadHandler)
{
//反注冊(cè)回調(diào)
m_DownloadHandler.OnReceiveDataAction -= DownloadHandlerReceiveDataCallBack;
m_DownloadHandler = null;
}
if (null != m_FileStream)
{
//關(guān)閉文件句柄(解除對(duì)該文件的占用,在操作系統(tǒng)里更改這個(gè)文件的狀態(tài))
m_FileStream.Close();
//清理 m_FileStream 內(nèi)部的資源
m_FileStream.Dispose();
m_FileStream = null;
}
m_PrevWriteSize = 0;
m_TotalSize = 0;
m_CurrDownloadSize = 0;
m_CurrWaitFlushSize = 0;
}
public void Dispose()
{
Reset();
}
}
多文件下載器?DownloadMultiRoutine
????????多文件下載器內(nèi)部也是調(diào)用了單文件下載器,本質(zhì)上是對(duì)單文件下載器的封裝實(shí)現(xiàn)。解決單文件下載器在下載依賴文件或資源包時(shí)不好管理問題,所以分出來了一個(gè)多文件下載器。
- 函數(shù):下載中回調(diào) OnDownloadMultiUpdate
統(tǒng)計(jì)下載數(shù)據(jù),執(zhí)行下載中回調(diào)
- 函數(shù):下載完成回調(diào) OnDownloadMultiComplete
檢測(cè)繼續(xù)下載;執(zhí)行下載完成回調(diào)
+ 函數(shù):更新?OnUpdate
迭代執(zhí)行DownloadRoutine內(nèi)的OnUpdate
+ 函數(shù):開始下載多文件 BeginDownloadMulti?
下載數(shù)據(jù)記錄;分配DownloadRoutine下載;
+ 函數(shù):釋放對(duì)象 Dispose?
釋放對(duì)象內(nèi)部狀態(tài)?
DownloadMultiRoutine.cs 完整代碼
//多文件下載器
public class DownloadMultiRoutine : IDisposable
{
//下載器鏈接
private LinkedList<DownloadRoutine> m_ListDownloadRoutine;
//需要下載的文件鏈表
private LinkedList<string> m_ListNeedDownload;
//多個(gè)文件下載中的回調(diào)函數(shù)
private BaseAction<int, int, ulong, ulong> m_OnDownloadMultiUpdate;
//多個(gè)文件下載完成的回調(diào)
private BaseAction<DownloadMultiRoutine> m_OnDownloadMultiComplete;
//多文件下載,需要下載的文件數(shù)量
private int m_DownloadMultiNeedCount = 0;
//多文件 當(dāng)前下載的數(shù)量
private int m_DownloadMultiCurrCount = 0;
//多文件 下載總共大小(單位:字節(jié))
private ulong m_DownloadMultiTotalSize = 0;
//多文件 當(dāng)前下載大?。▎挝唬鹤止?jié))
private ulong m_DownloadMultiCurrSize = 0;
//多文件 每個(gè)文件當(dāng)前下載的大?。▎挝唬鹤止?jié))
private Dictionary<string, ulong> m_dicDownloadMultiCurrSize;
public DownloadMultiRoutine()
{
m_ListDownloadRoutine = new LinkedList<DownloadRoutine>();
m_ListNeedDownload = new LinkedList<string>();
m_dicDownloadMultiCurrSize = new Dictionary<string, ulong>();
}
public void OnUpdate()
{
LinkedListNode<DownloadRoutine> iter = m_ListDownloadRoutine.First;
for (; iter != null;)
{
iter.Value.OnUpdate();
iter = iter.Next;
}
}
#region 下載多個(gè)文件
//多文件 下載中回調(diào)
private void OnDownloadMultiUpdate(string url, ulong currDownloadedSize, float progress)
{
//緩存當(dāng)前文件下載的大小
m_dicDownloadMultiCurrSize[url] = currDownloadedSize;
ulong currSize = 0;
IEnumerator<KeyValuePair<string, ulong>> iter = m_dicDownloadMultiCurrSize.GetEnumerator();
for (; iter.MoveNext();)
{
currSize += iter.Current.Value;
}
//算出當(dāng)前下載的大小,保存
m_DownloadMultiCurrSize = currSize;
//安全保護(hù)
if (m_DownloadMultiCurrSize > m_DownloadMultiTotalSize)
{
m_DownloadMultiCurrSize = m_DownloadMultiTotalSize;
}
//回調(diào),通知當(dāng)前的下載進(jìn)度
m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
m_DownloadMultiCurrSize, m_DownloadMultiTotalSize);
}
//單個(gè)文件下載完畢回調(diào)
private void OnDownloadMultiComplete(string fileUrl, DownloadRoutine routine)
{
//檢查需要下載鏈表中 是否還有數(shù)據(jù),如果有繼續(xù)下載
if (m_ListNeedDownload.Count > 0)
{
//讓下載器繼續(xù)工作,拿到頭部數(shù)據(jù)
string url = m_ListNeedDownload.First.Value;
m_ListNeedDownload.RemoveFirst();
AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
routine.BeginDownload(url,entity,OnDownloadMultiUpdate, OnDownloadMultiComplete);
}
else
{
m_ListDownloadRoutine.Remove(routine);
GameEntry.Pool.EnqueueClassObject(routine);
}
//當(dāng)前已下載數(shù)量+1
m_DownloadMultiCurrCount++;
m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
m_DownloadMultiCurrSize,m_DownloadMultiTotalSize);
//全部下載完成
if (m_DownloadMultiCurrCount == m_DownloadMultiNeedCount)
{
//結(jié)束的時(shí)候 直接把當(dāng)前下載的大小設(shè)置為總大小
m_DownloadMultiCurrSize = m_DownloadMultiTotalSize;
m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
m_DownloadMultiCurrSize, m_DownloadMultiTotalSize);
m_OnDownloadMultiComplete?.Invoke(this);
}
}
public void BeginDownloadMulti(LinkedList<string> lstUrl,
BaseAction<int, int, ulong, ulong> onDownloadMultiUpdate = null,
BaseAction<DownloadMultiRoutine> onDownloadComplete = null)
{
m_OnDownloadMultiUpdate = onDownloadMultiUpdate;
m_OnDownloadMultiComplete = onDownloadComplete;
//需要下載的文件列表&字典 清空
m_ListNeedDownload.Clear();
m_dicDownloadMultiCurrSize.Clear();
//下載器記錄的下載數(shù)量和當(dāng)前下載數(shù)量重置
m_DownloadMultiNeedCount = 0;
m_DownloadMultiCurrCount = 0;
//下載器記錄的下載大小數(shù)據(jù)重置
m_DownloadMultiTotalSize = 0;
m_DownloadMultiCurrSize = 0;
//1.把需要下載的加入下載隊(duì)列
for (LinkedListNode<string> iter = lstUrl.First; iter != null; iter = iter.Next)
{
string url = iter.Value;
//多文件下載器加了限制,只有CDN上的資源才能下載,否則不允許下載
AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
if (entity != null)
{
//?這里等于不就行了 +=個(gè)毛???
m_DownloadMultiTotalSize += entity.Size;
m_DownloadMultiNeedCount++;
m_ListNeedDownload.AddLast(url);
m_dicDownloadMultiCurrSize[url] = 0;
}
else
{
GameEntry.LogError("CDN站點(diǎn)無此資源=>" + url);
}
}
//下載器數(shù)量,最大同時(shí)下載數(shù)(平衡下載速度和下載數(shù)量,之間做權(quán)衡)
int routineCount = Math.Min(GameEntry.Download.DownloadRoutineCount, m_ListNeedDownload.Count) ;
for (int i=0;i<routineCount;++i)
{
//類對(duì)象池取一個(gè)對(duì)象
DownloadRoutine routine = GameEntry.Pool.DequeueClassObject<DownloadRoutine>();
//取頭部的url,開始下載
string url = m_ListNeedDownload.First.Value;
m_ListNeedDownload.RemoveFirst();
AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
routine.BeginDownload(url,entity, OnDownloadMultiUpdate, OnDownloadMultiComplete);
m_ListDownloadRoutine.AddLast(routine);
}
}
#endregion
public void Dispose()
{
LinkedListNode<DownloadRoutine> iter=m_ListDownloadRoutine.First;
for (; iter != null;)
{
iter.Value.Dispose();
iter = iter.Next;
}
m_ListDownloadRoutine.Clear();
m_ListNeedDownload.Clear();
m_dicDownloadMultiCurrSize.Clear();
}
}
下載管理器?DownloadManager
? ? ? ? 下載管理器中對(duì),配置了下載數(shù)據(jù)的落地大小、重試次數(shù)、間隔等。管理器中存放了單下載器與多下載器的鏈表。
+ 函數(shù):管理器初始化 Init
?初始化配置
+ 函數(shù):下載單個(gè)文件 BeginDownloadSingle?
分配下載器,下載單個(gè)文件;下載完成后移除下載器;執(zhí)行完成回調(diào);
+ 函數(shù):下載文件列表?BeginDownloadMulti
分配下載器,下載多個(gè)文件;下載完成后移除下載器;執(zhí)行完成回調(diào);
+ 函數(shù):更新 OnUpdate
執(zhí)行單文件下載器和多文件下載器的OnUpdate文章來源:http://www.zghlxwxcb.cn/news/detail-730080.html
+ 函數(shù):清理對(duì)象狀態(tài) Dispose
調(diào)用單文件下載器和多文件下載器的Dispose文章來源地址http://www.zghlxwxcb.cn/news/detail-730080.html
DownloadManager.cs 完整代碼
//下載管理器
public class DownloadManager : ManagerBase, IDisposable
{
//寫入磁盤的緩存大?。▎挝唬篕 數(shù)據(jù)到達(dá)多少才寫入磁盤)
public int FlushSize
{
get;
private set;
}
//每個(gè)多文件下載器中的下載器最大數(shù)量
public int DownloadRoutineCount
{
get;
private set;
}
//連接失敗后 重試次數(shù)
public int Retry
{
get;
private set;
}
//重試間隔
public int RetryInterval
{
get;
private set;
}
//單文件下載器鏈表
private LinkedList<DownloadRoutine> m_lstDownloadSingleRoutine;
//多文件下載器連邊
private LinkedList<DownloadMultiRoutine> m_lstDownloadMultiRoutine;
public DownloadManager()
{
m_lstDownloadSingleRoutine = new LinkedList<DownloadRoutine>();
m_lstDownloadMultiRoutine = new LinkedList<DownloadMultiRoutine>();
}
public override void Init()
{
//TODO:這里應(yīng)該讀取配置的
Retry = 5;
RetryInterval = 60;
DownloadRoutineCount = 5;
FlushSize = 2048;//2Mb
}
#region BeginDownloadSingle 下載單一文件
/// <summary>
// 下載單個(gè)文件
/// </summary>
/// <param name="url">文件鏈接</param>
/// <param name="onUpdate">下載中的更新回調(diào)</param>
/// <param name="onComplete">下載完成的回調(diào)</param>
public void BeginDownloadSingle(string url, BaseAction<string, ulong, float> onUpdate = null, BaseAction<string> onComplete = null)
{
AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
if (null == entity)
{
GameEntry.LogError("資源包無效=>" + url);
return;
}
DownloadRoutine routine = GameEntry.Pool.DequeueClassObject<DownloadRoutine>();
routine.BeginDownload(url, entity, onUpdate, onComplete: (string fileUrl, DownloadRoutine r) =>
{
//移除,歸還到對(duì)象池
m_lstDownloadSingleRoutine.Remove(r);
GameEntry.Pool.EnqueueClassObject(r);
onComplete?.Invoke(fileUrl);
});
m_lstDownloadSingleRoutine.AddLast(routine);
}
#endregion
#region BeginDownloadMulti 下載多個(gè)文件
/// <summary>
/// 下載多個(gè)文件
/// </summary>
/// <param name="lstUrl">url鏈表</param>
/// <param name="onUpdate">下載中更新回調(diào)</param>
/// <param name="onComplete">下載完成回調(diào)</param>
public void BeginDownloadMulti(LinkedList<string> lstUrl, BaseAction<int, int, ulong, ulong> onUpdate, BaseAction onComplete = null)
{
//從對(duì)象池里取一個(gè)多文件下載器
DownloadMultiRoutine multiRoutine = GameEntry.Pool.DequeueClassObject<DownloadMultiRoutine>();
multiRoutine.BeginDownloadMulti(lstUrl, onUpdate, onDownloadComplete: (DownloadMultiRoutine r) =>
{
m_lstDownloadMultiRoutine.Remove(r);
GameEntry.Pool.EnqueueClassObject(r);
onComplete?.Invoke();
});
}
#endregion
//更新
public void OnUpdate()
{
//遍歷更新單文件下載器
LinkedListNode<DownloadRoutine> iterSingleRoutine = m_lstDownloadSingleRoutine.First;
for (; iterSingleRoutine != null;)
{
iterSingleRoutine.Value.OnUpdate();
iterSingleRoutine = iterSingleRoutine.Next;
}
//循環(huán)更新多文件下載器
LinkedListNode<DownloadMultiRoutine> iterMultiRoutine = m_lstDownloadMultiRoutine.First;
for (; iterMultiRoutine != null;)
{
iterMultiRoutine.Value.OnUpdate();
iterMultiRoutine = iterMultiRoutine.Next;
}
}
public void Dispose()
{
//清空單任務(wù)下載器鏈表
LinkedListNode<DownloadRoutine> iter = m_lstDownloadSingleRoutine.First;
for (; iter != null;)
{
iter.Value.Dispose();
iter = iter.Next;
}
m_lstDownloadSingleRoutine.Clear();
LinkedListNode<DownloadMultiRoutine> iterMultiRoutine = m_lstDownloadMultiRoutine.First;
for (; iterMultiRoutine != null;)
{
iterMultiRoutine.Value.Dispose();
iterMultiRoutine = iterMultiRoutine.Next;
}
//清空多任務(wù)下載器鏈表
m_lstDownloadMultiRoutine.Clear();
}
}
到了這里,關(guān)于U3D客戶端框架之支持?jǐn)帱c(diǎn)續(xù)傳的文件下載器實(shí)現(xiàn)方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!