UnityWebRequestRenewalAsync.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using UnityEngine;
  5. using UnityEngine.Networking;
  6. namespace ET
  7. {
  8. public class UnityWebRequestRenewalUpdateSystem: UpdateSystem<UnityWebRequestRenewalAsync>
  9. {
  10. public override void Update(UnityWebRequestRenewalAsync self)
  11. {
  12. self.Update();
  13. }
  14. }
  15. /// <summary>
  16. /// 断点续传实现
  17. /// </summary>
  18. public class UnityWebRequestRenewalAsync: Entity, IUpdate
  19. {
  20. public static AcceptAllCertificate certificateHandler = new AcceptAllCertificate();
  21. public UnityWebRequest headRequest;
  22. public bool isCancel;
  23. public ETTask tcs;
  24. //请求资源类型
  25. private class RequestType
  26. {
  27. public const int None = 0; //未开始
  28. public const int Head = 1; //文件头
  29. public const int Data = 2; //文件数据
  30. }
  31. //当前请求资源类型
  32. private int requestType = RequestType.None;
  33. //多任务同时请求
  34. private readonly List<UnityWebRequest> dataRequests = new List<UnityWebRequest>();
  35. //当前下载的文件流 边下边存文件流
  36. private FileStream fileStream;
  37. //资源地址
  38. private string Url;
  39. //每个下载包长度
  40. private int packageLength = 1000000; //1M
  41. //同时开启任务最大个数
  42. private int maxCount = 20;
  43. //已经写入文件的大小
  44. private long byteWrites;
  45. //文件总大小
  46. private long totalBytes;
  47. //当前请求的位置
  48. private long downloadIndex;
  49. //文件下载是否出错
  50. private string dataError
  51. {
  52. get
  53. {
  54. foreach (UnityWebRequest webRequest in this.dataRequests)
  55. {
  56. if (!string.IsNullOrEmpty(webRequest.error))
  57. {
  58. return webRequest.error;
  59. }
  60. }
  61. return "";
  62. }
  63. }
  64. //批量开启任务下载
  65. private void DownloadPackages()
  66. {
  67. if (dataRequests.Count >= maxCount || this.downloadIndex == totalBytes - 1)
  68. {
  69. return;
  70. }
  71. //开启一个下载任务
  72. void DownloadPackage(long start, long end)
  73. {
  74. this.downloadIndex = end;
  75. Log.Debug($"Request Data ({start}~{end}):{Url}");
  76. UnityWebRequest request = UnityWebRequest.Get(Url);
  77. dataRequests.Add(request);
  78. request.certificateHandler = certificateHandler;
  79. request.SetRequestHeader("Range", $"bytes={start}-{end}");
  80. request.SendWebRequest();
  81. }
  82. //开启批量下载
  83. for (int i = dataRequests.Count; i < maxCount; i++)
  84. {
  85. long start = this.byteWrites + i * packageLength;
  86. long end = this.byteWrites + (i + 1) * packageLength - 1;
  87. if (end > this.totalBytes)
  88. {
  89. end = this.totalBytes - 1;
  90. }
  91. DownloadPackage(start, end);
  92. if (end == this.totalBytes - 1)
  93. {
  94. break;
  95. }
  96. }
  97. }
  98. //一次批量下载完成后写文件
  99. private void WritePackages()
  100. {
  101. //写入单个包
  102. void WritePackage(UnityWebRequest webRequest)
  103. {
  104. byte[] buff = webRequest.downloadHandler.data;
  105. if (buff != null && buff.Length > 0)
  106. {
  107. this.fileStream.Write(buff, 0, buff.Length);
  108. this.byteWrites += buff.Length;
  109. }
  110. Log.Debug($"write file Length:{byteWrites}");
  111. }
  112. //从第一个开始顺序写入
  113. while (this.dataRequests.Count > 0 && dataRequests[0].isDone)
  114. {
  115. UnityWebRequest first = dataRequests[0];
  116. dataRequests.RemoveAt(0);
  117. WritePackage(first);
  118. first.Dispose();
  119. }
  120. }
  121. //更新文件体下载
  122. private void UpdatePackages()
  123. {
  124. if (this.isCancel)
  125. {
  126. this.tcs.SetException(new Exception($"request data error: {dataError}"));
  127. return;
  128. }
  129. if (!string.IsNullOrEmpty(dataError))
  130. {
  131. this.tcs.SetException(new Exception($"request data error: {dataError}"));
  132. return;
  133. }
  134. this.WritePackages();
  135. if (this.byteWrites == this.totalBytes)
  136. {
  137. this.tcs.SetResult();
  138. }
  139. else
  140. {
  141. this.DownloadPackages();
  142. }
  143. }
  144. //更新文件头下载
  145. private void UpdateHead()
  146. {
  147. if (this.isCancel)
  148. {
  149. this.tcs.SetException(new Exception($"request error: {this.headRequest.error}"));
  150. return;
  151. }
  152. if (!this.headRequest.isDone)
  153. {
  154. return;
  155. }
  156. if (!string.IsNullOrEmpty(this.headRequest.error))
  157. {
  158. this.tcs.SetException(new Exception($"request error: {this.headRequest.error}"));
  159. return;
  160. }
  161. this.tcs.SetResult();
  162. }
  163. //检测是不是同一个文件
  164. private bool CheckSameFile(string modifiedTime)
  165. {
  166. string cacheValue = PlayerPrefs.GetString(Url);
  167. string currentValue = this.totalBytes + "|" + modifiedTime;
  168. if (cacheValue == currentValue)
  169. {
  170. return true;
  171. }
  172. PlayerPrefs.SetString(Url, currentValue);
  173. PlayerPrefs.Save();
  174. Log.Debug($"断点续传下载一个新的文件:{Url} cacheValue:{cacheValue} currentValue:{currentValue}");
  175. return false;
  176. }
  177. /// <summary>
  178. /// 断点续传入口
  179. /// </summary>
  180. /// <param name="url">文件下载地址</param>
  181. /// <param name="filePath">文件写入路径</param>
  182. /// <param name="packageLength">单个任务包体字节大小</param>
  183. /// <param name="maxCount">同时开启最大任务个数</param>
  184. /// <returns></returns>
  185. public async ETTask DownloadAsync(string url, string filePath, int packageLength = 1000000, int maxCount = 20)
  186. {
  187. try
  188. {
  189. url = url.Replace(" ", "%20");
  190. this.Url = url;
  191. this.packageLength = packageLength;
  192. this.maxCount = maxCount;
  193. Log.Debug("Web Request:" + url);
  194. #region Download File Header
  195. this.requestType = RequestType.Head;
  196. //下载文件头
  197. Log.Debug($"Request Head: {Url}");
  198. this.tcs = ETTask.Create(true);
  199. this.headRequest = UnityWebRequest.Head(Url);
  200. this.headRequest.SendWebRequest();
  201. await this.tcs;
  202. this.totalBytes = long.Parse(this.headRequest.GetResponseHeader("Content-Length"));
  203. string modifiedTime = this.headRequest.GetResponseHeader("Last-Modified");
  204. Log.Debug($"totalBytes: {this.totalBytes}");
  205. this.headRequest?.Dispose();
  206. this.headRequest = null;
  207. #endregion
  208. #region Check Local File
  209. //打开或创建
  210. fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
  211. //获取已下载长度
  212. this.byteWrites = fileStream.Length;
  213. //通过本地缓存的服务器文件修改时间和文件总长度检测服务器是否是同一个文件 不是同一个从头开始写入
  214. if (!CheckSameFile(modifiedTime))
  215. {
  216. this.byteWrites = 0;
  217. }
  218. Log.Debug($"byteWrites: {this.byteWrites}");
  219. if (this.byteWrites == this.totalBytes)
  220. {
  221. Log.Debug("已经下载完成2");
  222. return;
  223. }
  224. //设置开始写入位置
  225. fileStream.Seek(this.byteWrites, SeekOrigin.Begin);
  226. #endregion
  227. #region Download File Data
  228. //下载文件数据
  229. requestType = RequestType.Data;
  230. Log.Debug($"Request Data: {Url}");
  231. this.tcs = ETTask.Create(true);
  232. this.DownloadPackages();
  233. await this.tcs;
  234. #endregion
  235. }
  236. catch (Exception e)
  237. {
  238. Log.Error($"下载:{Url} Exception:{e}");
  239. throw;
  240. }
  241. }
  242. //下载进度
  243. public float Progress
  244. {
  245. get
  246. {
  247. if (this.totalBytes == 0)
  248. {
  249. return 0;
  250. }
  251. return (float) ((this.byteWrites + ByteDownloaded) / (double) this.totalBytes);
  252. }
  253. }
  254. //当前任务已经下载的长度
  255. public long ByteDownloaded
  256. {
  257. get
  258. {
  259. long length = 0;
  260. foreach (UnityWebRequest dataRequest in this.dataRequests)
  261. {
  262. length += dataRequest.downloadHandler.data.Length;
  263. }
  264. return length;
  265. }
  266. }
  267. public void Update()
  268. {
  269. if (this.requestType == RequestType.Head)
  270. {
  271. this.UpdateHead();
  272. }
  273. if (this.requestType == RequestType.Data)
  274. {
  275. this.UpdatePackages();
  276. }
  277. }
  278. public override void Dispose()
  279. {
  280. if (this.IsDisposed)
  281. {
  282. return;
  283. }
  284. base.Dispose();
  285. headRequest?.Dispose();
  286. headRequest = null;
  287. foreach (UnityWebRequest dataRequest in this.dataRequests)
  288. {
  289. dataRequest.Dispose();
  290. }
  291. dataRequests.Clear();
  292. this.fileStream?.Close();
  293. this.fileStream?.Dispose();
  294. this.fileStream = null;
  295. this.isCancel = false;
  296. }
  297. }
  298. }