using System; using System.Collections.Generic; using System.IO; using BestHTTP; using UnityEngine; using UnityEngine.Events; public class AssetUpdateDownloader { public readonly string targetPath; public readonly Uri uri; private FileStream _fileStream; private HTTPRequest _request; public UnityAction onComplete; public AssetUpdateDownloader(string uriPath, string filePath) { uri = new Uri(uriPath); targetPath = filePath; } public float progress { get; private set; } public AssetUpdateManager.AssetUpdateError error { get; private set; } public int finishedSize { get; private set; } public int totalSize { get; private set; } public void Start(float timeOut = 10f) { if (_request == null) { _request = new HTTPRequest(uri, HTTPMethods.Get) { DisableCache = true, Timeout = TimeSpan.FromSeconds(timeOut), Callback = OnGetResponseComplete, OnDownloadProgress = OnDownloadProgress, OnStreamingData = OnStreamingData }; _request.Send(); AssetUpdateDownloaderTick.instance.onUpdate -= Update; AssetUpdateDownloaderTick.instance.onUpdate += Update; } } private void DisposeInternal() { // 解除倒计时绑定 AssetUpdateDownloaderTick.instance.onUpdate -= Update; if (_request != null) { // 取消Dispose可能造成的额外回调; // Dispose接口和其他造成析构的接口应该自行执行onComplete回调; _request.Callback = null; _request.OnDownloadProgress = null; _request.OnStreamingData = null; _request.Dispose(); if (_request.Response != null) try { _request.Response.Dispose(); } catch (Exception e) { Debug.LogError(e); } _request = null; } if (_fileStream != null) try { _fileStream.Dispose(); } catch (Exception e) { Debug.LogError(e); } finally { _fileStream = null; } } public void Dispose() { DisposeInternal(); error = AssetUpdateManager.AssetUpdateError.UserCancel; if (onComplete != null) onComplete(this); } private bool OnStreamingData(HTTPRequest request, HTTPResponse response, byte[] data, int length) { // 注:返回错误仅仅由OnGetResponseComplete判定 if (data != null && data.Length > 0) { // 试图创建文件流 if (_fileStream == null) { EnsureMobileDir(targetPath); try { _fileStream = File.Create(targetPath); } catch (Exception e) { Debug.LogError(e); error = AssetUpdateManager.AssetUpdateError.FileCreateError; } } if (_fileStream != null) try { _fileStream.Write(data, 0, length); finishedSize = (int) _fileStream.Length; } catch (Exception e) { Debug.LogError(e); error = AssetUpdateManager.AssetUpdateError.FileCreateError; } // 如果出现文件错误,就在这里断掉 if (error == AssetUpdateManager.AssetUpdateError.FileCreateError) { DisposeInternal(); if (onComplete != null) onComplete(this); } } return true; } private void OnDownloadProgress(HTTPRequest request, long downloaded, long downloadLength) { progress = downloadLength > 0 ? (float) downloaded / downloadLength : 0f; totalSize = (int) downloadLength; finishedSize = Mathf.Max(finishedSize, (int) downloaded); } private void OnGetResponseComplete(HTTPRequest request, HTTPResponse response) { if (request != _request) { request.Dispose(); if (response != null) try { response.Dispose(); } catch (Exception e) { Debug.LogError(e); } } else { if (response == null) { error = AssetUpdateManager.AssetUpdateError.DownloadError; Debug.LogError(string.Format("Failed to get response from {0}!", uri)); } else if (!response.IsSuccess) { error = AssetUpdateManager.AssetUpdateError.DownloadError; try { Debug.LogError(string.Format("{0}: {1} from {2}!", response.StatusCode, response.Message, uri)); } catch (Exception e) { Debug.LogError(e); } } // 注:下载php服务器的Json文件时,取得的totalSize会为-1,不能用于判定 // 因此暂时处理为使用finishedSize判定 else if (finishedSize <= 0 || totalSize > 0 && finishedSize < totalSize) { error = AssetUpdateManager.AssetUpdateError.DownloadError; Debug.LogError(string.Format("Download incomplete, downloaded {0} / total {1} from {2}!", finishedSize, totalSize, uri)); } if (response != null) try { response.Dispose(); } catch (Exception e) { Debug.LogError(e); } DisposeInternal(); if (onComplete != null) onComplete(this); } } // 注:某些手机平台不支持一次建立多重路径,因此使用这个流程 public static void EnsureMobileDir(string path) { var dir = Path.GetDirectoryName(path); if (!Directory.Exists(dir)) { dir = dir.ToUrl(); var index = dir.LastIndexOf('/'); var segments = new List(); while (index > 0) { segments.Add(dir.Substring(index + 1)); dir = dir.Remove(index); if (Directory.Exists(dir)) break; index = dir.LastIndexOf('/'); } for (var i = segments.Count - 1; i >= 0; i--) { dir = dir.Open(segments[i]); Directory.CreateDirectory(dir); } } } // 某些手机网络环境下,会出现收不到GetResponseComplete,但是下载又没有速度的情况 // 因此使用这个监控来执行主动断开 #region 下载速度监控 // 最低应有速度 private const int minDownloadSpeed = 1024; // 低于最低速度时,超过这个时间将会导致断开 private const int minSpeedTolerance = 15; private float _lastTick; private int _lastBytes; private int _failTime; private void Update() { if (_lastTick > 0f) { if (_lastTick + 1 < Time.realtimeSinceStartup) { _lastTick = Time.realtimeSinceStartup; var delta = finishedSize - _lastBytes; _lastBytes = finishedSize; if (delta < minDownloadSpeed) { _failTime++; if (_failTime >= minSpeedTolerance) ForceDisconnect(); } else { _failTime = 0; } } } else { _lastTick = Time.realtimeSinceStartup; } } private void ForceDisconnect() { DisposeInternal(); error = AssetUpdateManager.AssetUpdateError.DownloadError; if (onComplete != null) onComplete(this); } #endregion }