UnityWebRequestRenewalAsync.cs 11 KB

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