• 支持断点续传的 文件下载器 实现方案


    一、功能设计

    文件下载器的作用

            文件下载器是应用程序的基础模块,为应用程序与外部网络交互提供了必要的桥梁。该模块设计初衷是为了热更新过程中,下载CDN站点上的文件资源,所以下载器会验证 要下载的文件是否存在于CDN中。如果存在允许下载器继续工作;如果不存在会跳过本地下载。做这层检测是为了安全性考虑,不允许随意下载网络资源。如果有需求可以跳过这层检测。

    什么是断点续传

            下载文件时,不必重头开始下载,而是从上次中断的位置继续下载,这样的功能就叫做断点续传。

    断点续传的作用

            在下载文件的过程中,打断文件下载的原因有很多,比如网络不稳定,导致下载,中断如果没有断点续传功能的话,中断之后需要重新开始下载。例如一个文件有100M大小,我下载了99M,马上就要下载完成了,这是突然网络中断导致下载失败了,我重新开始下载的时候发现又需要重新开始下载,这时候是不是会感觉心态崩了,如果有了断点续传功能的话,我下载了99M,即使网络中断,重连之后我的下载依旧是从99M的位置开始下载,这样给用户的体验就很棒了。

    断点续传实现思路

    1. 在下载文件的时候我们会先创建一个与下载文件对应的以.temp为后缀的临时文件, 下载的文件数据会写入这个临时文件中。
    2. 每次开始下载的时候会检查是否存在需下载文件的临时文件,如果存在,便从该文件数据长度的地方开始下载写入。
    3. 下载完成后便将临时文件移动到目标下载目录。
     

    二、代码设计

    下载器在物理结构上切分成了4个文件模块,每个模块各司其职。

    文章在讲解的时候会挑选模块内的主要的函数来讲解。

    文章格式按照

            模块中文名 类名

                    函数:函数中文名 函数名

                            具体作用解释。

    具体的函数实现可以去改模块底部完整代码部分,根据函数名搜索该函数即可。函数里也对每一句话添加了注释。如果还是不懂的可以私信我。

    下载器回调 DownloadHandler

     + 函数:下载数据回调 OnReceiveDataAction

            OnReceiveDataAction重写了DownloadHandlerScript内的函数。其主要作用就是我们程序内部需要拿到下载的进度数据。

    DownloadHandler.cs 完整代码

    1. public class DownloadHandler : DownloadHandlerScript
    2. {
    3. //下载速度限制1024KB
    4. const int DownLoadKB = 1024;
    5. //初始化下载句柄,定义每次下载的数据上线为 DownLoadKB KB
    6. //单位:字节 ,字节 = 1024字节 * DownLoadKB (转换成字节单位)
    7. public DownloadHandler() : base(new byte[1024 * DownLoadKB])
    8. {
    9. }
    10. //接收到数据的委托
    11. public BaseAction<byte[], int> OnReceiveDataAction;
    12. protected override bool ReceiveData(byte[] data, int dataLength)
    13. {
    14. if (null == data || dataLength == 0)
    15. return false;
    16. OnReceiveDataAction?.Invoke(data, dataLength);
    17. return true;
    18. }
    19. }

    单文件下载器 DownloadRoutine

    - 函数:下载存盘 Save

    下载文件中会调用,会检查当前内存缓存是否达到数据落地要求,如果达到,往硬盘里写入一次文件。

    - 函数:断点下载 Download(string url, uint beginPos)

    断点下载函数,从指定字节处下载这个文件。

    - 函数:开始下载 BeginDownload

    检查下载目录是否存在,不存在则创建;保存下载文件MD5;拼接真实链接;

    - 函数:内部下载 DownloadInner

    有相同文件比较MD5是否相同,若不同删除重新下载;定位断点续传文件下载位置;

    + 函数:下载 Download(string url)

    从头下载文件

    + 函数:开始下载 BeginDownload

    外部调用函数,会把相对url、资源信息、更新、完成回调都传入给该对象

    + 函数:更新 OnUpdate

    下载中回调;下载失败重试;下载完成操作;

    + 函数:重置 Reset

    关闭WebRequest下载器;接触文件占用;变量重置;

    + 函数:释放 Dispose

    对象或程序生命周期结束时调用,内部调用了 Reset还原类内类内成员的状态。

    DownloadRoutine.cs 完整代码

    1. //文件下载器
    2. public class DownloadRoutine:IDisposable
    3. {
    4. //Web请求(存了web的连接)
    5. private UnityWebRequest m_UnityWebRequest = null;
    6. //文件流(写入文件使用)
    7. private FileStream m_FileStream;
    8. //当前等待写入磁盘的大小(超过阈值才会把这一部分写入文件的尾部)
    9. private int m_CurrWaitFlushSize = 0;
    10. //上次写入的大小(上次写入文件流的全量大小)
    11. private int m_PrevWriteSize = 0;
    12. //文件总大小
    13. private ulong m_TotalSize;
    14. //当前下载的大小(下载了多少了)
    15. private ulong m_CurrDownloadSize = 0;
    16. //起始位置
    17. private uint m_BeginPos = 0;
    18. //当前下载文件的链接(url)
    19. private string m_CurrFileUrl;
    20. //下载到的本地路径
    21. private string m_DownloadLocalFilePath;
    22. //下载中的委托(string:url,ulong:下载的大小,float:下载百分比)
    23. private BaseAction<string, ulong, float> m_OnUpdate;
    24. //下载完毕回调
    25. private BaseAction<string, DownloadRoutine> m_OnComplete;
    26. //当前的资源包信息(*:是信息,不是文件实体;如果不是资源包,是其他的文件怎么办?比如mp4就不能下载了是吗?必须要把资源压到ab包里)
    27. private AssetBundleInfoEntity m_CurrAssetBundleInfo;
    28. //当前重试次数
    29. private int m_CurrRetry = 0;
    30. //上次重试时间
    31. private float m_PrevRetryTime = 0;
    32. //下载句柄(这个也是继承unity的然后自己封装的一层)
    33. private DownloadHandler m_DownloadHandler;
    34. /*
    35. * 功能:保存字节
    36. * buffer:文件流
    37. * downloadComplete:是否下载完成
    38. * bufferCount:文件流的总长度(单位:字节)
    39. */
    40. private void Save(byte[] buffer, bool downloadComplete = false, int bufferCount = 0)
    41. {
    42. if (null == buffer)
    43. return;
    44. //len是文件流的总长度
    45. int len = buffer.Length;
    46. //文件流的总长度-上一次写入的大小 = 这次要写入多少?
    47. int count = len - m_PrevWriteSize;
    48. //m_FileStream?.Write(buffer,m_PrevWriteSize,count);
    49. //把偏移写入的方法,换成全量写入了?
    50. m_FileStream?.Write(buffer, 0, bufferCount);
    51. m_PrevWriteSize = len;
    52. m_CurrWaitFlushSize += count;
    53. //内存中下载的文件大小超过了 FlushSize(2048k)*1024 =多少字节。 到达指定的字节数 || 下载完成 直接把流数据Append到文件尾部
    54. if (m_CurrWaitFlushSize >= GameEntry.Download.FlushSize * 1024 || downloadComplete)
    55. {
    56. m_CurrWaitFlushSize = 0;
    57. //内存缓冲区中的数据流,立即写入磁盘
    58. m_FileStream.Flush();
    59. }
    60. }
    61. //接受到数据后(接受到网络流中的数据后,会回调应用层,然后应用层会毁掉我们自己封装的这个函数)
    62. //没下载完成
    63. private void DownloadHandlerReceiveDataCallBack(byte[] buffer, int length)
    64. {
    65. Save(buffer, false, length);
    66. }
    67. /*
    68. * 函数功能:下载
    69. * url:文件链接
    70. * beginPos:该文件的起始下载位置 单位:字节(断点续传功能使用)
    71. */
    72. public void Download(string url, uint beginPos)
    73. {
    74. //开启web Request
    75. m_UnityWebRequest = UnityWebRequest.Get(url);
    76. //实例化下载器
    77. m_DownloadHandler = new DownloadHandler();
    78. //注册下载器回调
    79. m_DownloadHandler.OnReceiveDataAction += DownloadHandlerReceiveDataCallBack;
    80. //m_UnityWebRequest内的下载器使用自定义的下载器
    81. m_UnityWebRequest.downloadHandler = m_DownloadHandler;
    82. //下载器释放的时候 webRequest 也 跟着释放
    83. m_UnityWebRequest.disposeDownloadHandlerOnDispose = true;
    84. //定位下载的位置,从文件的哪部分开始下载(单位:字节)
    85. string headerValue = string.Format("bytes={0}-", beginPos.ToString());
    86. m_UnityWebRequest.SetRequestHeader("Range", headerValue);
    87. //发起请求
    88. m_UnityWebRequest.SendWebRequest();
    89. }
    90. public void Download(string url)
    91. {
    92. //开启web Request
    93. m_UnityWebRequest = UnityWebRequest.Get(url);
    94. //实例化下载器
    95. m_DownloadHandler = new DownloadHandler();
    96. //注册下载器回调
    97. m_DownloadHandler.OnReceiveDataAction += DownloadHandlerReceiveDataCallBack;
    98. //m_UnityWebRequest内的下载器使用自定义的下载器
    99. m_UnityWebRequest.downloadHandler = m_DownloadHandler;
    100. //下载器释放的时候 webRequest 也 跟着释放
    101. m_UnityWebRequest.disposeDownloadHandlerOnDispose = true;
    102. //发起请求
    103. m_UnityWebRequest.SendWebRequest();
    104. }
    105. //进行下载
    106. private void BeginDownload()
    107. {
    108. //目录
    109. string directory = Path.GetDirectoryName(m_DownloadLocalFilePath);
    110. //文件不存在,就创建一个
    111. if (!Directory.Exists(directory))
    112. {
    113. Directory.CreateDirectory(directory);
    114. }
    115. m_FileStream = new FileStream(m_DownloadLocalFilePath,FileMode.Create,FileAccess.Write);
    116. PlayerPrefs.SetString(m_CurrFileUrl,m_CurrAssetBundleInfo.MD5);
    117. //开始下载
    118. string url = string.Format("{0}{1}", GameEntry.Data.SysDataManager.CurrChannelConfig.RealSourceUrl, m_CurrFileUrl);
    119. Download(url);
    120. }
    121. //内部下载
    122. private void DownloadInner()
    123. {
    124. //本地是否有该文件
    125. if (File.Exists(m_DownloadLocalFilePath))
    126. {
    127. //验证md5,如果本地文件的md5和cdn的md5不一致,删除本地文件,重新下载
    128. if (PlayerPrefs.HasKey(m_CurrFileUrl))
    129. {
    130. //验证
    131. if (!PlayerPrefs.GetString(m_CurrFileUrl).
    132. Equals(m_CurrAssetBundleInfo.MD5, StringComparison.CurrentCultureIgnoreCase))
    133. {
    134. //本地文件和cdn md5不一致 删除本地文件
    135. File.Delete(m_DownloadLocalFilePath);
    136. BeginDownload();
    137. }
    138. else
    139. {
    140. //文件一致,打开文件
    141. m_FileStream = File.OpenWrite(m_DownloadLocalFilePath);
    142. //光标定位到文件的最后
    143. m_FileStream.Seek(0,SeekOrigin.End);
    144. //开始位置设置成文件的长度
    145. m_BeginPos = (uint)m_FileStream.Length;
    146. //开始下载,直接成功(只是走了一边下载的流程,其实根本没下载)
    147. string url = string.Format("{0}{1}",GameEntry.Data.SysDataManager.CurrChannelConfig.RealSourceUrl,m_CurrFileUrl);
    148. Download(url,m_BeginPos);
    149. }
    150. }
    151. }
    152. else
    153. {
    154. BeginDownload();
    155. }
    156. }
    157. //开始下载
    158. public void BeginDownload(string url, AssetBundleInfoEntity assetBundleInfoEntity,
    159. BaseAction<string, ulong, float> onUpdate = null,
    160. BaseAction<string, DownloadRoutine> onComplete = null)
    161. {
    162. m_CurrFileUrl = url;
    163. m_CurrAssetBundleInfo = assetBundleInfoEntity;
    164. m_OnUpdate = onUpdate;
    165. m_OnComplete = onComplete;
    166. m_DownloadLocalFilePath = string.Format("{0}/{1}",GameEntry.Resource.LocalFilePath,m_CurrFileUrl);
    167. //如果本地有这个文件,先删除
    168. if (File.Exists(m_DownloadLocalFilePath))
    169. {
    170. File.Delete(m_DownloadLocalFilePath);
    171. }
    172. m_DownloadLocalFilePath = m_DownloadLocalFilePath + ".temp";
    173. //如果通过这个函数调DownloadInnder,本地一定不会有这个文件了啊
    174. DownloadInner();
    175. }
    176. public void OnUpdate()
    177. {
    178. if (null == m_UnityWebRequest)
    179. return;
    180. //如果进行重试了,判断重试间隔
    181. if (m_CurrRetry > 0 && Time.time < m_PrevRetryTime + GameEntry.Download.RetryInterval)
    182. return;
    183. //大小=0,获取web中的内容大小数据
    184. if (m_TotalSize == 0)
    185. ulong.TryParse(m_UnityWebRequest.GetResponseHeader("Content-Length"),out m_TotalSize);
    186. //下载没完成
    187. if (!m_UnityWebRequest.isDone)
    188. {
    189. //使用 unityWebRequest里面的下载字节大小
    190. if (m_CurrDownloadSize < m_UnityWebRequest.downloadedBytes)
    191. {
    192. m_CurrDownloadSize = m_UnityWebRequest.downloadedBytes;
    193. //通知更新
    194. m_OnUpdate?.Invoke(m_CurrFileUrl,m_CurrDownloadSize,m_CurrDownloadSize/(float)m_TotalSize);
    195. }
    196. return;
    197. }
    198. //网络错误 || http 请求错误
    199. if (m_UnityWebRequest.isNetworkError || m_UnityWebRequest.isHttpError)
    200. {
    201. ++m_CurrRetry;
    202. m_PrevRetryTime = Time.time;
    203. //大于了重试次数
    204. if (m_CurrRetry > GameEntry.Download.Retry)
    205. {
    206. Reset();
    207. GameEntry.Log(LogCategory.Resource, "下载完毕url=>{0} 失败 当前重试次数{1}", m_UnityWebRequest.url, m_CurrRetry);
    208. //尝试重新下载
    209. DownloadInner();
    210. return;
    211. }
    212. GameEntry.Log(LogCategory.Resource, "下载完毕url=>{0} error=>{1}", m_UnityWebRequest.url, m_UnityWebRequest.error);
    213. Reset();
    214. }
    215. else
    216. {
    217. m_CurrDownloadSize = m_UnityWebRequest.downloadedBytes;
    218. //最后再更新一次
    219. m_OnUpdate?.Invoke(m_CurrFileUrl,m_CurrDownloadSize, m_CurrDownloadSize/(float)m_TotalSize);
    220. GameEntry.Log(LogCategory.Resource,"下载完毕url=>{0}",m_UnityWebRequest.url);
    221. Reset();
    222. //好像File里面没有Rename操作,把a/b/c/xxx.ab.temp 移动到a/b/c/xxx.ab
    223. //这好像是个改名的骚操作啊!
    224. File.Move(m_DownloadLocalFilePath, m_DownloadLocalFilePath.Replace(".temp",""));
    225. m_DownloadLocalFilePath = null;
    226. if (PlayerPrefs.HasKey(m_CurrFileUrl))
    227. {
    228. PlayerPrefs.DeleteKey(m_CurrFileUrl);
    229. }
    230. //这个文件 写入本地版本文件信息
    231. GameEntry.Resource.ResManager.SaveVersion(m_CurrAssetBundleInfo);
    232. //回调OnComplete函数
    233. m_OnComplete?.Invoke(m_CurrFileUrl,this);
    234. }
    235. }
    236. //重置对象信息(我认为每个可以被回收的类,都应该有Reset)
    237. public void Reset()
    238. {
    239. if (null != m_UnityWebRequest)
    240. {
    241. //中断连接|下载
    242. m_UnityWebRequest.Abort();
    243. //释放 m_UnityWebRequest 内部资源
    244. m_UnityWebRequest.Dispose();
    245. m_UnityWebRequest = null;
    246. }
    247. if (null != m_DownloadHandler)
    248. {
    249. //反注册回调
    250. m_DownloadHandler.OnReceiveDataAction -= DownloadHandlerReceiveDataCallBack;
    251. m_DownloadHandler = null;
    252. }
    253. if (null != m_FileStream)
    254. {
    255. //关闭文件句柄(解除对该文件的占用,在操作系统里更改这个文件的状态)
    256. m_FileStream.Close();
    257. //清理 m_FileStream 内部的资源
    258. m_FileStream.Dispose();
    259. m_FileStream = null;
    260. }
    261. m_PrevWriteSize = 0;
    262. m_TotalSize = 0;
    263. m_CurrDownloadSize = 0;
    264. m_CurrWaitFlushSize = 0;
    265. }
    266. public void Dispose()
    267. {
    268. Reset();
    269. }
    270. }

    多文件下载器 DownloadMultiRoutine

            多文件下载器内部也是调用了单文件下载器,本质上是对单文件下载器的封装实现。解决单文件下载器在下载依赖文件或资源包时不好管理问题,所以分出来了一个多文件下载器。

    - 函数:下载中回调 OnDownloadMultiUpdate

    统计下载数据,执行下载中回调

    - 函数:下载完成回调 OnDownloadMultiComplete

    检测继续下载;执行下载完成回调

    + 函数:更新 OnUpdate

    迭代执行DownloadRoutine内的OnUpdate

    + 函数:开始下载多文件 BeginDownloadMulti 

    下载数据记录;分配DownloadRoutine下载;

    + 函数:释放对象 Dispose 

    释放对象内部状态 

    DownloadMultiRoutine.cs 完整代码

    1. //多文件下载器
    2. public class DownloadMultiRoutine : IDisposable
    3. {
    4. //下载器链接
    5. private LinkedList m_ListDownloadRoutine;
    6. //需要下载的文件链表
    7. private LinkedList<string> m_ListNeedDownload;
    8. //多个文件下载中的回调函数
    9. private BaseAction<int, int, ulong, ulong> m_OnDownloadMultiUpdate;
    10. //多个文件下载完成的回调
    11. private BaseAction m_OnDownloadMultiComplete;
    12. //多文件下载,需要下载的文件数量
    13. private int m_DownloadMultiNeedCount = 0;
    14. //多文件 当前下载的数量
    15. private int m_DownloadMultiCurrCount = 0;
    16. //多文件 下载总共大小(单位:字节)
    17. private ulong m_DownloadMultiTotalSize = 0;
    18. //多文件 当前下载大小(单位:字节)
    19. private ulong m_DownloadMultiCurrSize = 0;
    20. //多文件 每个文件当前下载的大小(单位:字节)
    21. private Dictionary<string, ulong> m_dicDownloadMultiCurrSize;
    22. public DownloadMultiRoutine()
    23. {
    24. m_ListDownloadRoutine = new LinkedList();
    25. m_ListNeedDownload = new LinkedList<string>();
    26. m_dicDownloadMultiCurrSize = new Dictionary<string, ulong>();
    27. }
    28. public void OnUpdate()
    29. {
    30. LinkedListNode iter = m_ListDownloadRoutine.First;
    31. for (; iter != null;)
    32. {
    33. iter.Value.OnUpdate();
    34. iter = iter.Next;
    35. }
    36. }
    37. #region 下载多个文件
    38. //多文件 下载中回调
    39. private void OnDownloadMultiUpdate(string url, ulong currDownloadedSize, float progress)
    40. {
    41. //缓存当前文件下载的大小
    42. m_dicDownloadMultiCurrSize[url] = currDownloadedSize;
    43. ulong currSize = 0;
    44. IEnumeratorstring, ulong>> iter = m_dicDownloadMultiCurrSize.GetEnumerator();
    45. for (; iter.MoveNext();)
    46. {
    47. currSize += iter.Current.Value;
    48. }
    49. //算出当前下载的大小,保存
    50. m_DownloadMultiCurrSize = currSize;
    51. //安全保护
    52. if (m_DownloadMultiCurrSize > m_DownloadMultiTotalSize)
    53. {
    54. m_DownloadMultiCurrSize = m_DownloadMultiTotalSize;
    55. }
    56. //回调,通知当前的下载进度
    57. m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
    58. m_DownloadMultiCurrSize, m_DownloadMultiTotalSize);
    59. }
    60. //单个文件下载完毕回调
    61. private void OnDownloadMultiComplete(string fileUrl, DownloadRoutine routine)
    62. {
    63. //检查需要下载链表中 是否还有数据,如果有继续下载
    64. if (m_ListNeedDownload.Count > 0)
    65. {
    66. //让下载器继续工作,拿到头部数据
    67. string url = m_ListNeedDownload.First.Value;
    68. m_ListNeedDownload.RemoveFirst();
    69. AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
    70. routine.BeginDownload(url,entity,OnDownloadMultiUpdate, OnDownloadMultiComplete);
    71. }
    72. else
    73. {
    74. m_ListDownloadRoutine.Remove(routine);
    75. GameEntry.Pool.EnqueueClassObject(routine);
    76. }
    77. //当前已下载数量+1
    78. m_DownloadMultiCurrCount++;
    79. m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
    80. m_DownloadMultiCurrSize,m_DownloadMultiTotalSize);
    81. //全部下载完成
    82. if (m_DownloadMultiCurrCount == m_DownloadMultiNeedCount)
    83. {
    84. //结束的时候 直接把当前下载的大小设置为总大小
    85. m_DownloadMultiCurrSize = m_DownloadMultiTotalSize;
    86. m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
    87. m_DownloadMultiCurrSize, m_DownloadMultiTotalSize);
    88. m_OnDownloadMultiComplete?.Invoke(this);
    89. }
    90. }
    91. public void BeginDownloadMulti(LinkedList<string> lstUrl,
    92. BaseAction<int, int, ulong, ulong> onDownloadMultiUpdate = null,
    93. BaseAction onDownloadComplete = null)
    94. {
    95. m_OnDownloadMultiUpdate = onDownloadMultiUpdate;
    96. m_OnDownloadMultiComplete = onDownloadComplete;
    97. //需要下载的文件列表&字典 清空
    98. m_ListNeedDownload.Clear();
    99. m_dicDownloadMultiCurrSize.Clear();
    100. //下载器记录的下载数量和当前下载数量重置
    101. m_DownloadMultiNeedCount = 0;
    102. m_DownloadMultiCurrCount = 0;
    103. //下载器记录的下载大小数据重置
    104. m_DownloadMultiTotalSize = 0;
    105. m_DownloadMultiCurrSize = 0;
    106. //1.把需要下载的加入下载队列
    107. for (LinkedListNode<string> iter = lstUrl.First; iter != null; iter = iter.Next)
    108. {
    109. string url = iter.Value;
    110. //多文件下载器加了限制,只有CDN上的资源才能下载,否则不允许下载
    111. AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
    112. if (entity != null)
    113. {
    114. //?这里等于不就行了 +=个毛啊?
    115. m_DownloadMultiTotalSize += entity.Size;
    116. m_DownloadMultiNeedCount++;
    117. m_ListNeedDownload.AddLast(url);
    118. m_dicDownloadMultiCurrSize[url] = 0;
    119. }
    120. else
    121. {
    122. GameEntry.LogError("CDN站点无此资源=>" + url);
    123. }
    124. }
    125. //下载器数量,最大同时下载数(平衡下载速度和下载数量,之间做权衡)
    126. int routineCount = Math.Min(GameEntry.Download.DownloadRoutineCount, m_ListNeedDownload.Count) ;
    127. for (int i=0;i
    128. {
    129. //类对象池取一个对象
    130. DownloadRoutine routine = GameEntry.Pool.DequeueClassObject();
    131. //取头部的url,开始下载
    132. string url = m_ListNeedDownload.First.Value;
    133. m_ListNeedDownload.RemoveFirst();
    134. AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
    135. routine.BeginDownload(url,entity, OnDownloadMultiUpdate, OnDownloadMultiComplete);
    136. m_ListDownloadRoutine.AddLast(routine);
    137. }
    138. }
    139. #endregion
    140. public void Dispose()
    141. {
    142. LinkedListNode iter=m_ListDownloadRoutine.First;
    143. for (; iter != null;)
    144. {
    145. iter.Value.Dispose();
    146. iter = iter.Next;
    147. }
    148. m_ListDownloadRoutine.Clear();
    149. m_ListNeedDownload.Clear();
    150. m_dicDownloadMultiCurrSize.Clear();
    151. }
    152. }

    下载管理器 DownloadManager

            下载管理器中对,配置了下载数据的落地大小、重试次数、间隔等。管理器中存放了单下载器与多下载器的链表。

    + 函数:管理器初始化 Init

     初始化配置

    + 函数:下载单个文件 BeginDownloadSingle 

    分配下载器,下载单个文件;下载完成后移除下载器;执行完成回调;

    + 函数:下载文件列表 BeginDownloadMulti

    分配下载器,下载多个文件;下载完成后移除下载器;执行完成回调;

    + 函数:更新 OnUpdate

    执行单文件下载器和多文件下载器的OnUpdate

    + 函数:清理对象状态 Dispose

    调用单文件下载器和多文件下载器的Dispose

    DownloadManager.cs 完整代码

    1. //下载管理器
    2. public class DownloadManager : ManagerBase, IDisposable
    3. {
    4. //写入磁盘的缓存大小(单位:K 数据到达多少才写入磁盘)
    5. public int FlushSize
    6. {
    7. get;
    8. private set;
    9. }
    10. //每个多文件下载器中的下载器最大数量
    11. public int DownloadRoutineCount
    12. {
    13. get;
    14. private set;
    15. }
    16. //连接失败后 重试次数
    17. public int Retry
    18. {
    19. get;
    20. private set;
    21. }
    22. //重试间隔
    23. public int RetryInterval
    24. {
    25. get;
    26. private set;
    27. }
    28. //单文件下载器链表
    29. private LinkedList m_lstDownloadSingleRoutine;
    30. //多文件下载器连边
    31. private LinkedList m_lstDownloadMultiRoutine;
    32. public DownloadManager()
    33. {
    34. m_lstDownloadSingleRoutine = new LinkedList();
    35. m_lstDownloadMultiRoutine = new LinkedList();
    36. }
    37. public override void Init()
    38. {
    39. //TODO:这里应该读取配置的
    40. Retry = 5;
    41. RetryInterval = 60;
    42. DownloadRoutineCount = 5;
    43. FlushSize = 2048;//2Mb
    44. }
    45. #region BeginDownloadSingle 下载单一文件
    46. ///
    47. // 下载单个文件
    48. ///
    49. /// 文件链接
    50. /// 下载中的更新回调
    51. /// 下载完成的回调
    52. public void BeginDownloadSingle(string url, BaseAction<string, ulong, float> onUpdate = null, BaseAction<string> onComplete = null)
    53. {
    54. AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
    55. if (null == entity)
    56. {
    57. GameEntry.LogError("资源包无效=>" + url);
    58. return;
    59. }
    60. DownloadRoutine routine = GameEntry.Pool.DequeueClassObject();
    61. routine.BeginDownload(url, entity, onUpdate, onComplete: (string fileUrl, DownloadRoutine r) =>
    62. {
    63. //移除,归还到对象池
    64. m_lstDownloadSingleRoutine.Remove(r);
    65. GameEntry.Pool.EnqueueClassObject(r);
    66. onComplete?.Invoke(fileUrl);
    67. });
    68. m_lstDownloadSingleRoutine.AddLast(routine);
    69. }
    70. #endregion
    71. #region BeginDownloadMulti 下载多个文件
    72. ///
    73. /// 下载多个文件
    74. ///
    75. /// url链表
    76. /// 下载中更新回调
    77. /// 下载完成回调
    78. public void BeginDownloadMulti(LinkedList<string> lstUrl, BaseAction<int, int, ulong, ulong> onUpdate, BaseAction onComplete = null)
    79. {
    80. //从对象池里取一个多文件下载器
    81. DownloadMultiRoutine multiRoutine = GameEntry.Pool.DequeueClassObject();
    82. multiRoutine.BeginDownloadMulti(lstUrl, onUpdate, onDownloadComplete: (DownloadMultiRoutine r) =>
    83. {
    84. m_lstDownloadMultiRoutine.Remove(r);
    85. GameEntry.Pool.EnqueueClassObject(r);
    86. onComplete?.Invoke();
    87. });
    88. }
    89. #endregion
    90. //更新
    91. public void OnUpdate()
    92. {
    93. //遍历更新单文件下载器
    94. LinkedListNode iterSingleRoutine = m_lstDownloadSingleRoutine.First;
    95. for (; iterSingleRoutine != null;)
    96. {
    97. iterSingleRoutine.Value.OnUpdate();
    98. iterSingleRoutine = iterSingleRoutine.Next;
    99. }
    100. //循环更新多文件下载器
    101. LinkedListNode iterMultiRoutine = m_lstDownloadMultiRoutine.First;
    102. for (; iterMultiRoutine != null;)
    103. {
    104. iterMultiRoutine.Value.OnUpdate();
    105. iterMultiRoutine = iterMultiRoutine.Next;
    106. }
    107. }
    108. public void Dispose()
    109. {
    110. //清空单任务下载器链表
    111. LinkedListNode iter = m_lstDownloadSingleRoutine.First;
    112. for (; iter != null;)
    113. {
    114. iter.Value.Dispose();
    115. iter = iter.Next;
    116. }
    117. m_lstDownloadSingleRoutine.Clear();
    118. LinkedListNode iterMultiRoutine = m_lstDownloadMultiRoutine.First;
    119. for (; iterMultiRoutine != null;)
    120. {
    121. iterMultiRoutine.Value.Dispose();
    122. iterMultiRoutine = iterMultiRoutine.Next;
    123. }
    124. //清空多任务下载器链表
    125. m_lstDownloadMultiRoutine.Clear();
    126. }
    127. }

  • 相关阅读:
    数据库连接池--Druid(德鲁伊)
    支付宝支付前端如何显示
    基于神经网络的自动驾驶,人工智能在无人驾驶
    Java基础(二十四):MySQL
    linux内核启动过程分析
    golang - 控制协程并发数的3种方法
    重读Java设计模式: 适配器模式解析
    无限边界:现代整合安全如何保护云
    git 学习总结
    yolov4 预测框解码详解【附代码】
  • 原文地址:https://blog.csdn.net/qq_33531923/article/details/128166379