1. 程式人生 > 實用技巧 >unity UnityWebRequest下載封裝,避免同時開啟太多協成

unity UnityWebRequest下載封裝,避免同時開啟太多協成

起因:遊戲裡面玩家好友都是用關係鏈頭像,也就是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
}