当前位置:   article > 正文

Unity边玩边下限制下载速度技术实现_unity downloadhandlerfile

unity downloadhandlerfile

Unity提供了DownloadHandlerFile类来进行文件的下载,如果是那种网络比较好的宽带每秒下载速度可以达到20M以上,这样导致IO容易卡住。如果是进游戏前那种提前下载肯定没问题,但是边玩边下这种如果不限制下载速度那么游戏就不会那么流畅了。

Unity提供了DownloadHandlerScript类,开始我以为只要用FileStream自己来写一个比较小长度的Buffer就可以解决问题。如下代码所示,实际测试了一下ReveiveData会在一帧内回调多次导致write操作卡住IO,所以此思路只能作罢。

  1. public class CustomDownloadHandler : DownloadHandlerScript
  2. {
  3. FileStream fileStream;
  4. private int m_receiveLength = 0;
  5. ulong m_ContentLength;
  6. public CustomDownloadHandler(byte[] preallocatedBuffer) : base(preallocatedBuffer)
  7. {
  8. int size = preallocatedBuffer.Length;
  9. fileStream = new FileStream(Application.persistentDataPath + "/1.bundle", FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, size);
  10. }
  11. protected override bool ReceiveData(byte[] data, int dataLength)
  12. {
  13. Debug.Log(Time.frameCount + " " + dataLength); //1帧内需要写入大量数据导致IO卡住
  14. m_receiveLength += dataLength;
  15. fileStream.Write(data, 0, dataLength);
  16. return true;
  17. }
  18. //....略
  19. }

既然Unity的API实现不了只能使用C#的API了。我们先达成一个共识,边玩边下同一时刻只能下载一个文件(游戏不卡顿优先,其次才是下载),所以缓冲Buffer可以分配一个静态的。假设最大的下载速度是1M/S 每秒30帧那么每帧Buffer的长度1024/30*1024。

每帧处理的Buffer字节数组已经确定,接着就是要开线程下载了。使用await Task.Run来开线程,它的好处是可以等子线程的下载任务结束在回到主线程,这样就可以把下载完成的事件抛出让逻辑层处理。下载过程中还需要考虑强制断开的问题,可以使用CancellationToken即可。

下载连接建立好以后就开始下载,启动一个while循环,为了避免IO的卡住,这里需要让线程sleep下来。最后就是上完整的代码了。

  1. using System;
  2. using System.IO;
  3. using System.Net;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using UnityEngine;
  7. public class DownloadHandler
  8. {
  9. public struct Result
  10. {
  11. public string error;
  12. public bool isHttpError => !string.IsNullOrEmpty(error);
  13. }
  14. static int DEFAULT_SLEEP_TIME = 33;
  15. static int DEFAULT_DOWNLOAD_SPEED = 1024;
  16. static byte[] DEFAULT_BUFFER = new byte[DEFAULT_DOWNLOAD_SPEED / 30 * 1024];
  17. static int DEFAULT_DOWNLOAD_TIMEOUT = 5;
  18. public event Action<Result> completed;
  19. public float Progress;
  20. public ulong DownloadedBytes;
  21. public bool IsDone;
  22. private string m_File;
  23. private string m_Url;
  24. private int m_SleepTime;
  25. private Result m_Result;
  26. private Stream m_Stream;
  27. private FileStream m_FileStream;
  28. private HttpWebRequest m_Request;
  29. private HttpWebResponse m_Response;
  30. private CancellationTokenSource m_Cts;
  31. /// <summary>
  32. /// 创建下载对象
  33. /// </summary>
  34. /// <param name="url">下载路径</param>
  35. /// <param name="file">保存路径</param>
  36. /// <param name="speed">每秒最大小速度,KB单位</param>
  37. public DownloadHandler(string url,string file,int speed)
  38. {
  39. m_File = file;
  40. m_Url = url;
  41. m_SleepTime = (int)(DEFAULT_SLEEP_TIME * Mathf.Max(1, (float)DEFAULT_DOWNLOAD_SPEED / speed));
  42. }
  43. //开始下载
  44. public void StartDownload()
  45. {
  46. Download();
  47. }
  48. //停止正在下载中的文件
  49. public void Dispose()
  50. {
  51. m_Cts?.Cancel();
  52. Close();
  53. }
  54. async void Download()
  55. {
  56. m_Cts = new CancellationTokenSource();
  57. CancellationToken token = m_Cts.Token;
  58. m_Result = default(Result);
  59. DownloadedBytes = 0;
  60. IsDone = false;
  61. await Task.Run(() =>
  62. {
  63. try
  64. {
  65. m_Request = (HttpWebRequest)WebRequest.Create(m_Url);
  66. m_Response = (HttpWebResponse)m_Request.GetResponse();
  67. long content = m_Response.ContentLength;
  68. m_Stream = m_Response.GetResponseStream();
  69. m_Stream.ReadTimeout = DEFAULT_DOWNLOAD_TIMEOUT*1000;
  70. m_FileStream = new FileStream(m_File, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, DEFAULT_BUFFER.Length);
  71. int read = 0;
  72. while (!token.IsCancellationRequested &&
  73. (read = m_Stream.Read(DEFAULT_BUFFER, 0, DEFAULT_BUFFER.Length)) > 0)
  74. {
  75. DownloadedBytes += (ulong)read;
  76. m_FileStream.Write(DEFAULT_BUFFER, 0, read);
  77. Thread.Sleep(m_SleepTime);
  78. }
  79. }
  80. catch (WebException ex)
  81. {
  82. m_Result.error = ex.ToString();
  83. }
  84. finally
  85. {
  86. Close();
  87. }
  88. }, token);
  89. try
  90. {
  91. if (!token.IsCancellationRequested)
  92. {
  93. IsDone = true;
  94. completed?.Invoke(m_Result);
  95. }
  96. }
  97. catch (Exception ex)
  98. {
  99. Debug.LogError(ex.ToString());
  100. }
  101. }
  102. void Close()
  103. {
  104. m_FileStream?.Dispose();
  105. m_Stream?.Dispose();
  106. m_Response?.Dispose();
  107. m_Cts?.Dispose();
  108. m_Cts = null;
  109. m_FileStream = null;
  110. m_Stream = null;
  111. m_Response = null;
  112. m_Request = null;
  113. }
  114. }

启动下载调用的代码,这里可以监听下载完成的事件以及错误信息。

  1. if (GUILayout.Button("<size=200>下载 </size>"))
  2. {
  3. string url = "https://xxxxxxx.bundle";
  4. string file = Application.persistentDataPath + "/1.bundle";
  5. float t = Time.time;
  6. downloadHandler = new DownloadHandler(url, file, 1024);//1024表示每秒下载1M,还可以传512或者256让下载速度继续往下降
  7. downloadHandler.StartDownload();
  8. downloadHandler.completed += (info) =>
  9. {
  10. if (info.isHttpError)
  11. {
  12. Debug.LogError(info.error);
  13. }
  14. else
  15. {
  16. finishTime = Time.time - t;
  17. Debug.LogError("fininsh " + finishTime);
  18. }
  19. };
  20. }

下载过程中取消下载

  1. if (GUILayout.Button("<size=200>取消下载 </size>"))
  2. {
  3. downloadHandler?.Dispose();
  4. }

注意如果是下载file://开头的本地文件, 需要在代码中将HttpWebRequest和HttpWebResponse换成FileWebRequest和FileWebResponse其他地方都完全一样。

  1. m_Request = (HttpWebRequest)WebRequest.Create(m_Url);
  2. m_Response = (HttpWebResponse)m_Request.GetResponse();

最后在总结一下资源下载。目前根据我们的经验会将下载分成两部分,一部分是启动下载,另一部分是边玩边下。

先说启动下载,它需要尽可能的快,一般这种下载展示就是一个普通的下载进度条,它并不要求高帧率,需要尽最快速度下载完毕。针对这种下载类型可以直接使用unity的DownloadHandlerFile,但是在面对小文件(几K几十K大小)的时候下载速度是非常慢的,因为针对每个文件需要单独建立http的链接,这些都需要额外开销。反而如果是大文件(百M以上大小),每秒下载好几十M都是可以的。

在针对下载小文件慢的问题上其实是可以增加同时下载的数量的,比如同时下载的资源大小不超过一个阀值就继续开下载队列,目前我项目最大开了30个下载队列,动态根据当前下载文件的小灵活变更数量,尽可能保证下载速度足够快。

其次就是边玩边下了,它和启动下载有个本质区别,边玩边下是不能影响用户游戏体验的,如果用户觉得游戏卡住很可能一开始就流失了。也就是说宁可下载的慢也不能下载太快影响操作体验,所以就有了这篇文章的限速。

另外Unity提供的几个下载的类都在这类,核心都是在C++中完成的。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/387561
推荐阅读
相关标签
  

闽ICP备14008679号