起因:游戏里面玩家好友都是用关系链头像,也就是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; ////// UnityWebRequest下载接口封装 /// 避免网络卡顿时重复下载,导致协成数量太多 /// public class DownLoadUtil { private static Dictionarym_cacheDownload = new Dictionary ();//下载缓存 private static Dictionary m_taskCallBack = new Dictionary ();//下载回调缓存 private static List m_waitDownloadTask = new List ();//等待下载的列表 private static List m_curDownloadTask = new List ();//当前正在下载的列表 private static int m_maxDownloadNum = 3;//最大可同时下载数量 private static int m_DownloadTimeOut = 5;//下载超时 /// /// 一个url对应一个TaskInfo,里面保存了该url的下载类型DownloadHandler,所有监听该url下载的回调 /// private class TaskInfo { private List> m_callBacks = new List >(); public string Url; public DownloadHandler Handle; public TaskInfo(string url, DownloadHandler handle) { Url = url; Handle = handle; } public void AddCallBack(Action callBack) { if (!m_callBacks.Contains(callBack)) { m_callBacks.Add(callBack); } } public void RemoveCallBack(Action 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 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 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 > m_callBacks = new List >(); public void AddCallBack(Action callBack) { if (!m_callBacks.Contains(callBack)) { m_callBacks.Add(callBack); } } public void RemoveCallBack(Action 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 m_texCallBack = new Dictionary ();//下载回调缓存 //下载贴图 public static void DownloadTexture(string url, Action 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 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 }