unity UnityWebRequest下載封裝,避免同時開啟太多協成
阿新 • • 發佈:2020-08-06
起因:遊戲裡面玩家好友都是用關係鏈頭像,也就是url頭像,玩家進遊戲需要動態拉取圖片。
之前沒有做下載佇列快取,一個url下載就會開啟一個協成,協成下載等待時間也設定了太長,導致網路延遲高且玩家好友多時,出現開啟協成太多,卡主程序的問題(每個協成都在等待下載回包)。
解決:
1.限制單次的下載等待時間req.timeout = 5;原先是等待30秒。
2.做下載快取,對下載過的url內容做快取。
3.限制同時下載數量,比如最多同時下載三個(也就是最多開啟三個協成),如果當前下載佇列超過3個,把下載任務新增到快取佇列。當前下載任務完成時,從快取佇列取出一個任務執行(如果有的話)。
4.一個url只下載一次,一次下載任務可以對應多個不同回撥(可能會出現多個地方依賴同一個url,或者網路延遲太高導致同一個地方同一個url觸發多次下載)。
原始碼:TaskManager是對協成的封裝,提供在非mono類開啟協成的機制(其實就是綁定了一個DontDestoryOnLoad的mono物件,這個物件永遠是Active狀態)。
// © 2013–2018 Gear Games, LTD. using System; using System.Collections; using System.Collections.Generic; using com.geargames.extensions; using UnityEngine; using UnityEngine.Networking; /// <summary> /// UnityWebRequest下載介面封裝 /// 避免網路卡頓時重複下載,導致協成數量太多 /// </summary> public class DownLoadUtil { private static Dictionary<string, DownCache> m_cacheDownload = new Dictionary<string, DownCache>();//下載快取 private static Dictionary<string, TaskInfo> m_taskCallBack = new Dictionary<string, TaskInfo>();//下載回撥快取 private static List<string> m_waitDownloadTask = new List<string>();//等待下載的列表 private static List<string> m_curDownloadTask = new List<string>();//當前正在下載的列表 private static int m_maxDownloadNum = 3;//最大可同時下載數量 private static int m_DownloadTimeOut = 5;//下載超時 /// <summary> /// 一個url對應一個TaskInfo,裡面儲存了該url的下載型別DownloadHandler,所有監聽該url下載的回撥 /// </summary> private class TaskInfo { private List<Action<DownCache>> m_callBacks = new List<Action<DownCache>>(); public string Url; public DownloadHandler Handle; public TaskInfo(string url, DownloadHandler handle) { Url = url; Handle = handle; } public void AddCallBack(Action<DownCache> callBack) { if (!m_callBacks.Contains(callBack)) { m_callBacks.Add(callBack); } } public void RemoveCallBack(Action<DownCache> callBack) { if (m_callBacks.Contains(callBack)) { m_callBacks.Remove(callBack); } } public void ClearCallBack() { m_callBacks.Clear(); } public int Count() { return m_callBacks.Count; } public void DownloadEnd(DownCache cache) { for (int i = 0; i < m_callBacks.Count; i++) { if (m_callBacks[i] != null) { m_callBacks[i](cache); } } ClearCallBack(); } } public class DownCache { public byte[] data; public string text; public Texture tex; public string url; } //下載 public static void Download(string url, Action<DownCache> callBack, DownloadHandler handle = null) { if (callBack == null) return; DownCache cache; if (m_cacheDownload.TryGetValue(url, out cache)) { callBack(cache); return; } TaskInfo taskInfo = null; if (!m_taskCallBack.TryGetValue(url, out taskInfo)) { taskInfo = new TaskInfo(url, handle); m_taskCallBack.Add(url, taskInfo); } taskInfo.AddCallBack(callBack); //不在當前的下載、等待列表,加入執行佇列 if (!m_waitDownloadTask.Contains(url) && !m_curDownloadTask.Contains(url)) { CastTask(url); } } private static void CastTask(string url) { if (string.IsNullOrEmpty(url)) { if (m_waitDownloadTask.Count == 0) { return;//沒有等待下載的任務 } url = m_waitDownloadTask[0]; } //當前併發下載數大於3,快取 if (m_curDownloadTask.Count > m_maxDownloadNum) { m_waitDownloadTask.Add(url); } else { int taskId = TaskManager.Instance.Create(RealDownload(url)); m_curDownloadTask.Add(url); } } private static IEnumerator RealDownload(string url) { UnityWebRequest req = UnityWebRequest.Get(url); req.timeout = m_DownloadTimeOut; TaskInfo taskInfo = null; if (m_taskCallBack.TryGetValue(url, out taskInfo)) { req.downloadHandler = taskInfo.Handle; } yield return req.SendWebRequest(); if (req.isNetworkError || req.isHttpError) { DownloadEnd(url); yield break; } HandleDownload(url, req.downloadHandler); req.Dispose(); DownloadEnd(url); } //下載錯誤、下載結束都清掉這個url任務 private static void DownloadEnd(string url) { m_taskCallBack.Remove(url); m_curDownloadTask.Remove(url); CastTask(null); } private static void HandleDownload(string url, DownloadHandler handle) { Texture tex = null; if (handle is DownloadHandlerTexture texHandle) { tex = texHandle.texture; if (tex) { tex.name = url; } } DownCache cacheHandle = new DownCache();//快取,req.Dispose會銷燬handle,所以這邊單獨快取 cacheHandle.data = handle.data; cacheHandle.text = handle.text; cacheHandle.tex = tex; cacheHandle.url = url; if(!m_cacheDownload.ContainsKey(url)) m_cacheDownload.AddValueEx(url,cacheHandle); TaskInfo taskInfo = null; if (m_taskCallBack.TryGetValue(url, out taskInfo)) { taskInfo.DownloadEnd(cacheHandle); m_taskCallBack.Remove(url); } Debug.Log("download end : " + url); } //移除某個連結下載 public static void RemoveHandle(string url) { m_taskCallBack.Remove(url); if (m_waitDownloadTask.Contains(url)) m_waitDownloadTask.Remove(url); } //移除單個下載任務 public static void RemoveHandle(string url, Action<DownCache> callBack) { TaskInfo taskInfo = null; if (m_taskCallBack.TryGetValue(url, out taskInfo)) { taskInfo.RemoveCallBack(callBack); if (taskInfo.Count() == 0) { m_taskCallBack.Remove(url); } } } #region 貼圖下載封裝 private class TextureTaskInfo { private List<Action<Texture, string>> m_callBacks = new List<Action<Texture, string>>(); public void AddCallBack(Action<Texture, string> callBack) { if (!m_callBacks.Contains(callBack)) { m_callBacks.Add(callBack); } } public void RemoveCallBack(Action<Texture, string> callBack) { if (m_callBacks.Contains(callBack)) { m_callBacks.Remove(callBack); } } public void ClearCallBack() { m_callBacks.Clear(); } public int Count() { return m_callBacks.Count; } public void DownloadEnd(DownCache cache) { bool isGif = cache.text.StartsWith("GIF"); for (int i = 0; i < m_callBacks.Count; i++) { if (isGif) //gif { m_callBacks[i](null, cache.url); } else { m_callBacks[i](cache.tex, cache.url); } } ClearCallBack(); } } private static Dictionary<string, TextureTaskInfo> m_texCallBack = new Dictionary<string, TextureTaskInfo>();//下載回撥快取 //下載貼圖 public static void DownloadTexture(string url, Action<Texture, string> callBack) { TextureTaskInfo texCallBack = null; if (!m_texCallBack.TryGetValue(url, out texCallBack)) { texCallBack = new TextureTaskInfo(); m_texCallBack.Add(url, texCallBack); } texCallBack.AddCallBack(callBack); Download(url, (cacheHandle) => { TextureTaskInfo finalCallBack = null; if (!m_texCallBack.TryGetValue(cacheHandle.url, out finalCallBack)) { return; } finalCallBack.DownloadEnd(cacheHandle); m_texCallBack.Remove(cacheHandle.url); }, new DownloadHandlerTexture()); } public static void RemoveTexTask(string url, Action<Texture, string> callBack) { TextureTaskInfo callBackList = null; if (m_texCallBack.TryGetValue(url, out callBackList)) { callBackList.RemoveCallBack(callBack); if (callBackList.Count() == 0) { m_texCallBack.Remove(url); } } } public static void RemoveTexTask(string url) { m_texCallBack.Remove(url); } #endregion }