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;

/// 
/// UnityWebRequest下载接口封装
/// 避免网络卡顿时重复下载,导致协成数量太多
/// 
public class DownLoadUtil 
{
    private static Dictionary m_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
}

  

你可能感兴趣的:(unity UnityWebRequest下载封装,避免同时开启太多协成)