U3D客户端框架之支持断点续传的文件下载器实现方案

这篇具有很好参考价值的文章主要介绍了U3D客户端框架之支持断点续传的文件下载器实现方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、功能设计

文件下载器的作用

        文件下载器是应用程序的基础模块,为应用程序与外部网络交互提供了必要的桥梁。该模块设计初衷是为了热更新过程中,下载CDN站点上的文件资源,所以下载器会验证 要下载的文件是否存在于CDN中。如果存在允许下载器继续工作;如果不存在会跳过本地下载。做这层检测是为了安全性考虑,不允许随意下载网络资源。如果有需求可以跳过这层检测。

什么是断点续传

        下载文件时,不必重头开始下载,而是从上次中断的位置继续下载,这样的功能就叫做断点续传。

断点续传的作用

        在下载文件的过程中,打断文件下载的原因有很多,比如网络不稳定,导致下载,中断如果没有断点续传功能的话,中断之后需要重新开始下载。例如一个文件有100M大小,我下载了99M,马上就要下载完成了,这是突然网络中断导致下载失败了,我重新开始下载的时候发现又需要重新开始下载,这时候是不是会感觉心态崩了,如果有了断点续传功能的话,我下载了99M,即使网络中断,重连之后我的下载依旧是从99M的位置开始下载,这样给用户的体验就很棒了。

断点续传实现思路

1. 在下载文件的时候我们会先创建一个与下载文件对应的以.temp为后缀的临时文件, 下载的文件数据会写入这个临时文件中。
2. 每次开始下载的时候会检查是否存在需下载文件的临时文件,如果存在,便从该文件数据长度的地方开始下载写入。
3. 下载完成后便将临时文件移动到目标下载目录。
 

二、代码设计

下载器在物理结构上切分成了4个文件模块,每个模块各司其职。

文章在讲解的时候会挑选模块内的主要的函数来讲解。

文章格式按照

        模块中文名 类名

                函数:函数中文名 函数名

                        具体作用解释。

具体的函数实现可以去改模块底部完整代码部分,根据函数名搜索该函数即可。函数里也对每一句话添加了注释。如果还是不懂的可以私信我。

下载器回调 DownloadHandler

 + 函数:下载数据回调 OnReceiveDataAction

        OnReceiveDataAction重写了DownloadHandlerScript内的函数。其主要作用就是我们程序内部需要拿到下载的进度数据。

DownloadHandler.cs 完整代码

    public class DownloadHandler : DownloadHandlerScript
    {
        //下载速度限制1024KB
        const int DownLoadKB = 1024;

        //初始化下载句柄,定义每次下载的数据上线为 DownLoadKB KB
        //单位:字节 ,字节 = 1024字节 * DownLoadKB (转换成字节单位)
        public DownloadHandler() : base(new byte[1024 * DownLoadKB])
        {

        }

        //接收到数据的委托
        public BaseAction<byte[], int> OnReceiveDataAction;

        protected override bool ReceiveData(byte[] data, int dataLength)
        {
            if (null == data || dataLength == 0)
                return false;

            OnReceiveDataAction?.Invoke(data, dataLength);
            return true;
        }
        
    }

单文件下载器 DownloadRoutine

- 函数:下载存盘 Save

下载文件中会调用,会检查当前内存缓存是否达到数据落地要求,如果达到,往硬盘里写入一次文件。

- 函数:断点下载 Download(string url, uint beginPos)

断点下载函数,从指定字节处下载这个文件。

- 函数:开始下载 BeginDownload

检查下载目录是否存在,不存在则创建;保存下载文件MD5;拼接真实链接;

- 函数:内部下载 DownloadInner

有相同文件比较MD5是否相同,若不同删除重新下载;定位断点续传文件下载位置;

+ 函数:下载 Download(string url)

从头下载文件

+ 函数:开始下载 BeginDownload

外部调用函数,会把相对url、资源信息、更新、完成回调都传入给该对象

+ 函数:更新 OnUpdate

下载中回调;下载失败重试;下载完成操作;

+ 函数:重置 Reset

关闭WebRequest下载器;接触文件占用;变量重置;

+ 函数:释放 Dispose

对象或程序生命周期结束时调用,内部调用了 Reset还原类内类内成员的状态。

DownloadRoutine.cs 完整代码

    //文件下载器
    public class DownloadRoutine:IDisposable
    {
        //Web请求(存了web的连接)
        private UnityWebRequest m_UnityWebRequest = null;

        //文件流(写入文件使用)
        private FileStream m_FileStream;

        //当前等待写入磁盘的大小(超过阈值才会把这一部分写入文件的尾部)
        private int m_CurrWaitFlushSize = 0;

        //上次写入的大小(上次写入文件流的全量大小)
        private int m_PrevWriteSize = 0;

        //文件总大小
        private ulong m_TotalSize;

        //当前下载的大小(下载了多少了)
        private ulong m_CurrDownloadSize = 0;

        //起始位置
        private uint m_BeginPos = 0;

        //当前下载文件的链接(url)
        private string m_CurrFileUrl;

        //下载到的本地路径
        private string m_DownloadLocalFilePath;

        //下载中的委托(string:url,ulong:下载的大小,float:下载百分比)
        private BaseAction<string, ulong, float> m_OnUpdate;

        //下载完毕回调
        private BaseAction<string, DownloadRoutine> m_OnComplete;

        //当前的资源包信息(*:是信息,不是文件实体;如果不是资源包,是其他的文件怎么办?比如mp4就不能下载了是吗?必须要把资源压到ab包里)
        private AssetBundleInfoEntity m_CurrAssetBundleInfo;

        //当前重试次数
        private int m_CurrRetry = 0;

        //上次重试时间
        private float m_PrevRetryTime = 0;

        //下载句柄(这个也是继承unity的然后自己封装的一层)
        private DownloadHandler m_DownloadHandler;

        /*
         * 功能:保存字节
         * buffer:文件流
         * downloadComplete:是否下载完成
         * bufferCount:文件流的总长度(单位:字节)
         */
        private void Save(byte[] buffer, bool downloadComplete = false, int bufferCount = 0)
        {
            if (null == buffer)
                return;

            //len是文件流的总长度
            int len = buffer.Length;

            //文件流的总长度-上一次写入的大小 = 这次要写入多少?
            int count = len - m_PrevWriteSize;

            //m_FileStream?.Write(buffer,m_PrevWriteSize,count);
            //把偏移写入的方法,换成全量写入了?
            m_FileStream?.Write(buffer, 0, bufferCount);

            m_PrevWriteSize = len;

            m_CurrWaitFlushSize += count;

            //内存中下载的文件大小超过了 FlushSize(2048k)*1024 =多少字节。 到达指定的字节数 || 下载完成 直接把流数据Append到文件尾部 
            if (m_CurrWaitFlushSize >= GameEntry.Download.FlushSize * 1024 || downloadComplete)
            {
                m_CurrWaitFlushSize = 0;

                //内存缓冲区中的数据流,立即写入磁盘
                m_FileStream.Flush();
            }
        }

        //接受到数据后(接受到网络流中的数据后,会回调应用层,然后应用层会毁掉我们自己封装的这个函数)
        //没下载完成
        private void DownloadHandlerReceiveDataCallBack(byte[] buffer, int length)
        {
            Save(buffer, false, length);
        }


        /*
         * 函数功能:下载
         * url:文件链接
         * beginPos:该文件的起始下载位置 单位:字节(断点续传功能使用)
         */
        public void Download(string url, uint beginPos)
        {
            //开启web Request
            m_UnityWebRequest = UnityWebRequest.Get(url);

            //实例化下载器
            m_DownloadHandler = new DownloadHandler();

            //注册下载器回调
            m_DownloadHandler.OnReceiveDataAction += DownloadHandlerReceiveDataCallBack;

            //m_UnityWebRequest内的下载器使用自定义的下载器
            m_UnityWebRequest.downloadHandler = m_DownloadHandler;

            //下载器释放的时候 webRequest 也 跟着释放
            m_UnityWebRequest.disposeDownloadHandlerOnDispose = true;

            //定位下载的位置,从文件的哪部分开始下载(单位:字节)
            string headerValue = string.Format("bytes={0}-", beginPos.ToString());
            m_UnityWebRequest.SetRequestHeader("Range", headerValue);
            
            //发起请求
            m_UnityWebRequest.SendWebRequest();
        }

        public void Download(string url)
        {
            //开启web Request
            m_UnityWebRequest = UnityWebRequest.Get(url);

            //实例化下载器
            m_DownloadHandler = new DownloadHandler();

            //注册下载器回调
            m_DownloadHandler.OnReceiveDataAction += DownloadHandlerReceiveDataCallBack;

            //m_UnityWebRequest内的下载器使用自定义的下载器
            m_UnityWebRequest.downloadHandler = m_DownloadHandler;

            //下载器释放的时候 webRequest 也 跟着释放
            m_UnityWebRequest.disposeDownloadHandlerOnDispose = true;

            //发起请求
            m_UnityWebRequest.SendWebRequest();
        }

        //进行下载
        private void BeginDownload()
        {
            //目录
            string directory = Path.GetDirectoryName(m_DownloadLocalFilePath);
            
            //文件不存在,就创建一个
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            m_FileStream = new FileStream(m_DownloadLocalFilePath,FileMode.Create,FileAccess.Write);

            PlayerPrefs.SetString(m_CurrFileUrl,m_CurrAssetBundleInfo.MD5);

            //开始下载
            string url = string.Format("{0}{1}", GameEntry.Data.SysDataManager.CurrChannelConfig.RealSourceUrl, m_CurrFileUrl);
            Download(url);
        }

        //内部下载
        private void DownloadInner()
        {
            //本地是否有该文件
            if (File.Exists(m_DownloadLocalFilePath))
            {
                //验证md5,如果本地文件的md5和cdn的md5不一致,删除本地文件,重新下载
                if (PlayerPrefs.HasKey(m_CurrFileUrl))
                {
                    //验证
                    if (!PlayerPrefs.GetString(m_CurrFileUrl).
                        Equals(m_CurrAssetBundleInfo.MD5, StringComparison.CurrentCultureIgnoreCase))
                    {
                        //本地文件和cdn md5不一致 删除本地文件
                        File.Delete(m_DownloadLocalFilePath);
                        BeginDownload();
                    }
                    else
                    {
                        //文件一致,打开文件
                        m_FileStream = File.OpenWrite(m_DownloadLocalFilePath);
                        
                        //光标定位到文件的最后
                        m_FileStream.Seek(0,SeekOrigin.End);

                        //开始位置设置成文件的长度
                        m_BeginPos = (uint)m_FileStream.Length;

                        //开始下载,直接成功(只是走了一边下载的流程,其实根本没下载)
                        string url = string.Format("{0}{1}",GameEntry.Data.SysDataManager.CurrChannelConfig.RealSourceUrl,m_CurrFileUrl);
                        Download(url,m_BeginPos);
                    }
                }
            }
            else
            {
                BeginDownload();
            }
        }

        //开始下载
        public void BeginDownload(string url, AssetBundleInfoEntity assetBundleInfoEntity,
                                    BaseAction<string, ulong, float> onUpdate = null, 
                                    BaseAction<string, DownloadRoutine> onComplete = null)
        {
            m_CurrFileUrl = url;

            m_CurrAssetBundleInfo = assetBundleInfoEntity;

            m_OnUpdate = onUpdate;

            m_OnComplete = onComplete;

            m_DownloadLocalFilePath = string.Format("{0}/{1}",GameEntry.Resource.LocalFilePath,m_CurrFileUrl);

            //如果本地有这个文件,先删除
            if (File.Exists(m_DownloadLocalFilePath))
            {
                File.Delete(m_DownloadLocalFilePath);
            }

            m_DownloadLocalFilePath = m_DownloadLocalFilePath + ".temp";

            //如果通过这个函数调DownloadInnder,本地一定不会有这个文件了啊
            DownloadInner();
        }

        public void OnUpdate()
        {
            if (null == m_UnityWebRequest)
                return;

            //如果进行重试了,判断重试间隔
            if (m_CurrRetry > 0 && Time.time < m_PrevRetryTime + GameEntry.Download.RetryInterval)
                return;

            //大小=0,获取web中的内容大小数据
            if (m_TotalSize == 0)
                ulong.TryParse(m_UnityWebRequest.GetResponseHeader("Content-Length"),out m_TotalSize);

            //下载没完成
            if (!m_UnityWebRequest.isDone)
            {
                //使用 unityWebRequest里面的下载字节大小
                if (m_CurrDownloadSize < m_UnityWebRequest.downloadedBytes)
                {
                    m_CurrDownloadSize = m_UnityWebRequest.downloadedBytes;

                    //通知更新
                    m_OnUpdate?.Invoke(m_CurrFileUrl,m_CurrDownloadSize,m_CurrDownloadSize/(float)m_TotalSize);
                }
                return;
            }

            //网络错误 || http 请求错误
            if (m_UnityWebRequest.isNetworkError || m_UnityWebRequest.isHttpError)
            {
                ++m_CurrRetry;
                m_PrevRetryTime = Time.time;

                //大于了重试次数
                if (m_CurrRetry > GameEntry.Download.Retry)
                {
                    Reset();
                    GameEntry.Log(LogCategory.Resource, "下载完毕url=>{0} 失败 当前重试次数{1}", m_UnityWebRequest.url, m_CurrRetry);

                    //尝试重新下载
                    DownloadInner();
                    return;
                }
                GameEntry.Log(LogCategory.Resource, "下载完毕url=>{0} error=>{1}", m_UnityWebRequest.url, m_UnityWebRequest.error);
                Reset();
            }
            else
            {
                m_CurrDownloadSize = m_UnityWebRequest.downloadedBytes;

                //最后再更新一次
                m_OnUpdate?.Invoke(m_CurrFileUrl,m_CurrDownloadSize, m_CurrDownloadSize/(float)m_TotalSize);

                GameEntry.Log(LogCategory.Resource,"下载完毕url=>{0}",m_UnityWebRequest.url);
                
                Reset();

                //好像File里面没有Rename操作,把a/b/c/xxx.ab.temp 移动到a/b/c/xxx.ab
                //这好像是个改名的骚操作啊!
                File.Move(m_DownloadLocalFilePath, m_DownloadLocalFilePath.Replace(".temp",""));

                m_DownloadLocalFilePath = null;

                if (PlayerPrefs.HasKey(m_CurrFileUrl))
                {
                    PlayerPrefs.DeleteKey(m_CurrFileUrl);
                }
                
                //这个文件 写入本地版本文件信息
                GameEntry.Resource.ResManager.SaveVersion(m_CurrAssetBundleInfo);

                //回调OnComplete函数
                m_OnComplete?.Invoke(m_CurrFileUrl,this);
            }
        }

        //重置对象信息(我认为每个可以被回收的类,都应该有Reset)
        public void Reset()
        {
            if (null != m_UnityWebRequest)
            {
                //中断连接|下载
                m_UnityWebRequest.Abort();

                //释放 m_UnityWebRequest 内部资源
                m_UnityWebRequest.Dispose();

                m_UnityWebRequest = null;
            }

            if (null != m_DownloadHandler)
            {
                //反注册回调
                m_DownloadHandler.OnReceiveDataAction -= DownloadHandlerReceiveDataCallBack;
                m_DownloadHandler = null;
            }

            if (null != m_FileStream)
            {
                //关闭文件句柄(解除对该文件的占用,在操作系统里更改这个文件的状态)
                m_FileStream.Close();

                //清理 m_FileStream 内部的资源
                m_FileStream.Dispose();
                m_FileStream = null;
            }

            m_PrevWriteSize = 0;
            m_TotalSize = 0;
            m_CurrDownloadSize = 0;
            m_CurrWaitFlushSize = 0;
        }

        public void Dispose()
        {
            Reset();
        }
    }

多文件下载器 DownloadMultiRoutine

        多文件下载器内部也是调用了单文件下载器,本质上是对单文件下载器的封装实现。解决单文件下载器在下载依赖文件或资源包时不好管理问题,所以分出来了一个多文件下载器。

- 函数:下载中回调 OnDownloadMultiUpdate

统计下载数据,执行下载中回调

- 函数:下载完成回调 OnDownloadMultiComplete

检测继续下载;执行下载完成回调

+ 函数:更新 OnUpdate

迭代执行DownloadRoutine内的OnUpdate

+ 函数:开始下载多文件 BeginDownloadMulti 

下载数据记录;分配DownloadRoutine下载;

+ 函数:释放对象 Dispose 

释放对象内部状态 

DownloadMultiRoutine.cs 完整代码

//多文件下载器
    public class DownloadMultiRoutine : IDisposable
    {
        //下载器链接
        private LinkedList<DownloadRoutine> m_ListDownloadRoutine;

        //需要下载的文件链表
        private LinkedList<string> m_ListNeedDownload;

        //多个文件下载中的回调函数
        private BaseAction<int, int, ulong, ulong> m_OnDownloadMultiUpdate;

        //多个文件下载完成的回调
        private BaseAction<DownloadMultiRoutine> m_OnDownloadMultiComplete;

        //多文件下载,需要下载的文件数量
        private int m_DownloadMultiNeedCount = 0;

        //多文件 当前下载的数量
        private int m_DownloadMultiCurrCount = 0;

        //多文件 下载总共大小(单位:字节)
        private ulong m_DownloadMultiTotalSize = 0;

        //多文件 当前下载大小(单位:字节)
        private ulong m_DownloadMultiCurrSize = 0;

        //多文件 每个文件当前下载的大小(单位:字节)
        private Dictionary<string, ulong> m_dicDownloadMultiCurrSize;

        public DownloadMultiRoutine()
        {
            m_ListDownloadRoutine = new LinkedList<DownloadRoutine>();
            m_ListNeedDownload = new LinkedList<string>();
            m_dicDownloadMultiCurrSize = new Dictionary<string, ulong>();
        }

        public void OnUpdate()
        {
            LinkedListNode<DownloadRoutine> iter = m_ListDownloadRoutine.First;
            for (; iter != null;)
            {
                iter.Value.OnUpdate();
                iter = iter.Next;
            }
        }


        #region 下载多个文件
        //多文件 下载中回调
        private void OnDownloadMultiUpdate(string url, ulong currDownloadedSize, float progress)
        {
            //缓存当前文件下载的大小
            m_dicDownloadMultiCurrSize[url] = currDownloadedSize;

            ulong currSize = 0;
            IEnumerator<KeyValuePair<string, ulong>> iter = m_dicDownloadMultiCurrSize.GetEnumerator();
            for (; iter.MoveNext();)
            {
                currSize += iter.Current.Value;
            }

            //算出当前下载的大小,保存
            m_DownloadMultiCurrSize = currSize;

            //安全保护
            if (m_DownloadMultiCurrSize > m_DownloadMultiTotalSize)
            {
                m_DownloadMultiCurrSize = m_DownloadMultiTotalSize;
            }

            //回调,通知当前的下载进度
            m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
                                            m_DownloadMultiCurrSize, m_DownloadMultiTotalSize);
        }

        //单个文件下载完毕回调
        private void OnDownloadMultiComplete(string fileUrl, DownloadRoutine routine)
        {
            //检查需要下载链表中 是否还有数据,如果有继续下载
            if (m_ListNeedDownload.Count > 0)
            {
                //让下载器继续工作,拿到头部数据
                string url = m_ListNeedDownload.First.Value;
                m_ListNeedDownload.RemoveFirst();

                AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
                routine.BeginDownload(url,entity,OnDownloadMultiUpdate, OnDownloadMultiComplete);
            }
            else
            {
                m_ListDownloadRoutine.Remove(routine);
                GameEntry.Pool.EnqueueClassObject(routine);
            }

            //当前已下载数量+1
            m_DownloadMultiCurrCount++;

            m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
                                            m_DownloadMultiCurrSize,m_DownloadMultiTotalSize);


            //全部下载完成
            if (m_DownloadMultiCurrCount == m_DownloadMultiNeedCount)
            {
                //结束的时候 直接把当前下载的大小设置为总大小
                m_DownloadMultiCurrSize = m_DownloadMultiTotalSize;

                m_OnDownloadMultiUpdate?.Invoke(m_DownloadMultiCurrCount, m_DownloadMultiNeedCount,
                                                m_DownloadMultiCurrSize, m_DownloadMultiTotalSize);

                m_OnDownloadMultiComplete?.Invoke(this);
            }
        }
            

        public void BeginDownloadMulti(LinkedList<string> lstUrl,
            BaseAction<int, int, ulong, ulong> onDownloadMultiUpdate = null,
            BaseAction<DownloadMultiRoutine> onDownloadComplete = null)
        {
            m_OnDownloadMultiUpdate = onDownloadMultiUpdate;
            m_OnDownloadMultiComplete = onDownloadComplete;

            //需要下载的文件列表&字典 清空
            m_ListNeedDownload.Clear();
            m_dicDownloadMultiCurrSize.Clear();

            //下载器记录的下载数量和当前下载数量重置
            m_DownloadMultiNeedCount = 0;
            m_DownloadMultiCurrCount = 0;

            //下载器记录的下载大小数据重置
            m_DownloadMultiTotalSize = 0;
            m_DownloadMultiCurrSize = 0;

            //1.把需要下载的加入下载队列
            for (LinkedListNode<string> iter = lstUrl.First; iter != null; iter = iter.Next)
            {
                string url = iter.Value;

                //多文件下载器加了限制,只有CDN上的资源才能下载,否则不允许下载
                AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
                if (entity != null)
                {
                    //?这里等于不就行了 +=个毛啊?
                    m_DownloadMultiTotalSize += entity.Size;
                    m_DownloadMultiNeedCount++;
                    m_ListNeedDownload.AddLast(url);
                    m_dicDownloadMultiCurrSize[url] = 0;
                }
                else
                {
                    GameEntry.LogError("CDN站点无此资源=>" + url);
                }
            }

            //下载器数量,最大同时下载数(平衡下载速度和下载数量,之间做权衡)
            int routineCount = Math.Min(GameEntry.Download.DownloadRoutineCount, m_ListNeedDownload.Count) ;
            for (int i=0;i<routineCount;++i)
            {
                //类对象池取一个对象
                DownloadRoutine routine = GameEntry.Pool.DequeueClassObject<DownloadRoutine>();

                //取头部的url,开始下载
                string url = m_ListNeedDownload.First.Value;
                m_ListNeedDownload.RemoveFirst();

                AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
                routine.BeginDownload(url,entity, OnDownloadMultiUpdate, OnDownloadMultiComplete);
                m_ListDownloadRoutine.AddLast(routine);
            }
        }
        #endregion

        public void Dispose()
        {
            LinkedListNode<DownloadRoutine> iter=m_ListDownloadRoutine.First;
            for (; iter != null;)
            {
                iter.Value.Dispose();
                iter = iter.Next;
            }

            m_ListDownloadRoutine.Clear();
            m_ListNeedDownload.Clear();
            m_dicDownloadMultiCurrSize.Clear();
        }
    }

下载管理器 DownloadManager

        下载管理器中对,配置了下载数据的落地大小、重试次数、间隔等。管理器中存放了单下载器与多下载器的链表。

+ 函数:管理器初始化 Init

 初始化配置

+ 函数:下载单个文件 BeginDownloadSingle 

分配下载器,下载单个文件;下载完成后移除下载器;执行完成回调;

+ 函数:下载文件列表 BeginDownloadMulti

分配下载器,下载多个文件;下载完成后移除下载器;执行完成回调;

+ 函数:更新 OnUpdate

执行单文件下载器和多文件下载器的OnUpdate

+ 函数:清理对象状态 Dispose

调用单文件下载器和多文件下载器的Dispose文章来源地址https://www.toymoban.com/news/detail-730080.html

DownloadManager.cs 完整代码

 //下载管理器
    public class DownloadManager : ManagerBase, IDisposable
    {
        //写入磁盘的缓存大小(单位:K 数据到达多少才写入磁盘)
        public int FlushSize
        {
            get;
            private set;
        }

        //每个多文件下载器中的下载器最大数量
        public int DownloadRoutineCount
        {
            get;
            private set;
        }

        //连接失败后 重试次数
        public int Retry
        {
            get;
            private set;
        }

        //重试间隔
        public int RetryInterval
        {
            get;
            private set;
        }

        //单文件下载器链表
        private LinkedList<DownloadRoutine> m_lstDownloadSingleRoutine;

        //多文件下载器连边
        private LinkedList<DownloadMultiRoutine> m_lstDownloadMultiRoutine;

        public DownloadManager()
        {
            m_lstDownloadSingleRoutine = new LinkedList<DownloadRoutine>();
            m_lstDownloadMultiRoutine = new LinkedList<DownloadMultiRoutine>();
        }

        public override void Init()
        {
            //TODO:这里应该读取配置的
            Retry = 5;
            RetryInterval = 60;
            DownloadRoutineCount = 5;
            FlushSize = 2048;//2Mb
        }

        #region BeginDownloadSingle 下载单一文件
        /// <summary>
        //  下载单个文件
        /// </summary>
        /// <param name="url">文件链接</param>
        /// <param name="onUpdate">下载中的更新回调</param>
        /// <param name="onComplete">下载完成的回调</param>
        public void BeginDownloadSingle(string url, BaseAction<string, ulong, float> onUpdate = null, BaseAction<string> onComplete = null)
        {
            AssetBundleInfoEntity entity = GameEntry.Resource.ResManager.GetCDNAssetBundleInfo(url);
            if (null == entity)
            {
                GameEntry.LogError("资源包无效=>" + url);
                return;
            }

            DownloadRoutine routine = GameEntry.Pool.DequeueClassObject<DownloadRoutine>();
            routine.BeginDownload(url, entity, onUpdate, onComplete: (string fileUrl, DownloadRoutine r) =>
                {
                    //移除,归还到对象池
                    m_lstDownloadSingleRoutine.Remove(r);
                    GameEntry.Pool.EnqueueClassObject(r);

                    onComplete?.Invoke(fileUrl);
                });

            m_lstDownloadSingleRoutine.AddLast(routine);
        }
        #endregion

        #region BeginDownloadMulti 下载多个文件
        /// <summary>
        /// 下载多个文件
        /// </summary>
        /// <param name="lstUrl">url链表</param>
        /// <param name="onUpdate">下载中更新回调</param>
        /// <param name="onComplete">下载完成回调</param>
        public void BeginDownloadMulti(LinkedList<string> lstUrl, BaseAction<int, int, ulong, ulong> onUpdate, BaseAction onComplete = null)
        {
            //从对象池里取一个多文件下载器
            DownloadMultiRoutine multiRoutine = GameEntry.Pool.DequeueClassObject<DownloadMultiRoutine>();

            multiRoutine.BeginDownloadMulti(lstUrl, onUpdate, onDownloadComplete: (DownloadMultiRoutine r) =>
              {
                  m_lstDownloadMultiRoutine.Remove(r);
                  GameEntry.Pool.EnqueueClassObject(r);
                  onComplete?.Invoke();
              });
        }
        #endregion

        //更新
        public void OnUpdate()
        {
            //遍历更新单文件下载器
            LinkedListNode<DownloadRoutine> iterSingleRoutine = m_lstDownloadSingleRoutine.First;
            for (; iterSingleRoutine != null;)
            {
                iterSingleRoutine.Value.OnUpdate();
                iterSingleRoutine = iterSingleRoutine.Next;
            }
            
            //循环更新多文件下载器
            LinkedListNode<DownloadMultiRoutine> iterMultiRoutine = m_lstDownloadMultiRoutine.First;
            for (; iterMultiRoutine != null;)
            {
                iterMultiRoutine.Value.OnUpdate();
                iterMultiRoutine = iterMultiRoutine.Next;
            }
        }


        public void Dispose()
        {
            //清空单任务下载器链表
            LinkedListNode<DownloadRoutine> iter = m_lstDownloadSingleRoutine.First;
            for (; iter != null;)
            {
                iter.Value.Dispose();
                iter = iter.Next;
            }
            m_lstDownloadSingleRoutine.Clear();

            LinkedListNode<DownloadMultiRoutine> iterMultiRoutine = m_lstDownloadMultiRoutine.First;
            for (; iterMultiRoutine != null;)
            {
                iterMultiRoutine.Value.Dispose();
                iterMultiRoutine = iterMultiRoutine.Next;
            }

            //清空多任务下载器链表
            m_lstDownloadMultiRoutine.Clear();
        }
    }

到了这里,关于U3D客户端框架之支持断点续传的文件下载器实现方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • U3D面试汇总!!!

    ① 微软开发的一种 OOP 编程语言。专门用于.NET应用而开发。 ② 面向对象的 三大特征 (封装,继承,多态)。除了三大特征以外,C#还添加了 事件和委托 ,增强了编程的灵活性。 ③ 简单,安全 。C#不再使用指针,而且不允许直接读取内存等不安全操作。提供了相比C/C++,

    2023年04月08日
    浏览(61)
  • U3D热更新技术

    @作者 : SYFStrive @博客首页 : HomePage 📌: 个人社区(欢迎大佬们加入) 👉: 社区链接🔗 🤷‍♀️: 创作不易转发需经作者同意😈 💃: 程序员每天坚持锻炼💪 👉 U3D热更新技术 (🔥) 🕐:开发者将测试好的代码,发布到应用商店的审核平台,平台方会进行稳定性及性

    2024年02月02日
    浏览(56)
  • U3D通过按钮点击实现场景切换

    1.新建UI,选择button选项,新建button;   3.新建一个空对象,挂载一个scenechange c#脚本; 4.编写脚本,1头文件using UnityEngine.SceneMangement                    2public void change() {                     scenemanager.loadscene (1)  }//括号中的数字为第2步中场景后面的数字          

    2024年02月07日
    浏览(47)
  • 【U3D引擎】没有切换中文选项&切换中文模式?

    第一步,查看是否有勾选简体中文模块 第二步,拉到底部,勾选简体中文,点击继续 第三步,勾选已阅读同意后点击安装 第四步,等待下载安装完成 过程中会自动安装VS软件 如有下载失败可重新下载 注:如果重复提示错误也没有没有关系,直接略过就好, 第五步,随便打

    2024年02月06日
    浏览(73)
  • 【Unity入门】1.创建第一个u3d项目

            大家好,我是Lampard~~      欢迎来到Unity入门系列博客     Halo大家好久不见,最近半年比较懒惰,一直都比较少更新(不过摆烂确实挺开心哈哈哈哈哈)。最近项目要转3D,引擎要从以前的cocos转向unity,关注我的小伙伴可能知道,我以前开发的一直是cocos2d的内容,

    2024年03月15日
    浏览(54)
  • AVProVideo☀️一、一款U3D视频播放插件介绍

    🎊 商务合作:https://skode.cn/file/businesscard/wechat.jpg 🎥 本文由 星河造梦坊公司官方 原创! 🏅 如果你有技术问题或项目开发,都可以加上方的联系方式,和我聊一聊你的故事🧡 前段时间看到有人问: 橙哥,AVProVideo支持8K全景视频嘛? 看来,好多人对这款插件的支持的功能

    2024年02月07日
    浏览(51)
  • 云备份客户端——客户端整体设计框架以及实用类工具实现

    客户端要实现的功能和服务端相比相对简单,客户端要实现的功能是 自动对指定文件中的文件进行备份,也就是定时对指定文件进行扫描,根据文件信息判断文件,符合要求(新文件或者被修改过的文件)进行上传 因此我们客户端大概需要实现下面三个模块 数据管理模块:

    2024年02月09日
    浏览(55)
  • 使用U3D、pico开发VR(二)——添加手柄摇杆控制移动

    1System: 2Move Speed:注意速度过小会导致看起来没有移动 3RightHandMoveAction(right loco move):个人采用右手柄实现移动 1System: 2Turn Speed:注意旋转速度过小会导致看起来没有移动 3RightHandMoveAction:个人采用右手柄实现旋转 至此,初步的移动功能就实现了

    2024年02月08日
    浏览(67)
  • unity客户端开源框架

    链接:https://github.com/yomunsam/TinaX/tree/master TinaX 主要实现了以下功能: Lua 语言支持 出于普遍的热更新需求,TinaX原生提供了基于 Tencent/xlua 的Lua语言运行环境,并为主要功能提供了Lua层面的API支持。 如果不需要Lua环境的话,也可以在项目中将Lua相关功能完全关闭,不会影响包

    2024年01月16日
    浏览(75)
  • SocketTools 11在所有HTTP客户端组件支持

    在所有HTTP客户端组件中添加了对HTTP/2.0协议的支持。 更新了TLS 1.2(及更高版本)和SSH 2.0的安全选项,以使用Microsoft Windows 11和Windows Server 2022中提供的密码套件。较旧、安全性较低的密码套件已被弃用,在建立连接时将不会使用。回退选项可用于使用TLS 1.0连接到旧版服务器。

    2024年02月05日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包