起因:游戲里面玩家好友都是用關系鏈頭像,也就是url頭像,玩家進游戲需要動態拉取圖片。
之前沒有做下載隊列緩存,一個url下載就會開啟一個協成,協成下載等待時間也設置了太長,導致網絡延遲高且玩家好友多時,出現開啟協成太多,卡主進程的問題(每個協成都在等待下載回包)。
解決:
1.限制單次的下載等待時間req.timeout = 5;原先是等待30秒。
2.做下載緩存,對下載過的url內容做緩存。
3.限制同時下載數量,比如最多同時下載三個(也就是最多開啟三個協成),如果當前下載隊列超過3個,把下載任務添加到緩存隊列。當前下載任務完成時,從緩存隊列取出一個任務執行(如果有的話)。
4.一個url只下載一次,一次下載任務可以對應多個不同回調(可能會出現多個地方依賴同一個url,或者網絡延遲太高導致同一個地方同一個url觸發多次下載)。
源碼:TaskManager是對協成的封裝,提供在非mono類開啟協成的機制(其實就是綁定了一個DontDestoryOnLoad的mono對象,這個對象永遠是Active狀態)。
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];
m_waitDownloadTask.RemoveAt(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
}