using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public class ABUpdateMgr : MonoBehaviour
{
    //AB包下载管理器脚本
    private static ABUpdateMgr instance;
    public static ABUpdateMgr Instance
    {
        get
        {
            if (instance == null)
            {
                GameObject obj = new GameObject("ABUpdateMgr");//为空则创建单例,自动添加到场景中
                instance = obj.AddComponent<ABUpdateMgr>();
            }
            return instance;
        }
    }
    //存储远端AB包信息字典,后续与本地对比
    private Dictionary<string, ABInfo> remoteABInfo = new Dictionary<string, ABInfo>();
    //用于存储本地AB包信息字典
    private Dictionary<string, ABInfo> localABInfo = new Dictionary<string, ABInfo>();
    //待下载的AB包文件名
    private List<string> downLoadList = new List<string>();
    public void CheckUpdate(UnityAction<bool> overCallBack, UnityAction<string> updateInfoCallBack)
    {
        //加载远端对比文件
        DownLoadABCompareFile((isOver) =>
        {
            updateInfoCallBack("开始更新资源");
            if (isOver)
            {
                updateInfoCallBack("对比文件下载完成");
                string remoteInfo = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_TMP.txt");
                updateInfoCallBack("解析远端对比文件");
                GetRemoteABCompareFileInfo(remoteInfo, remoteABInfo);
                updateInfoCallBack("解析远端对比文件完成");
                //加载本地对比文件
                GetLocalABCompareFileInfo((isOver) =>
                {
                    if (isOver)
                    {
                        updateInfoCallBack("解析本地对比文件完成");
                        updateInfoCallBack("开始对比");
                        //对比进行AB包下载
                        foreach (string abName in remoteABInfo.Keys)
                        {
                            if (!localABInfo.ContainsKey(abName))
                            {
                                //新资源下载
                                downLoadList.Add(abName);
                            }
                            else
                            {
                                //资源替换
                                if (localABInfo[abName].md5 != remoteABInfo[abName].md5)
                                    downLoadList.Add(abName);
                                //本地移除
                                localABInfo.Remove(abName);
                            }
                        }
                        updateInfoCallBack("对比完成");
                        updateInfoCallBack("删除无用AB包文件");
                        //对比结束 删除没用内容 再下载AB包
                        foreach (string abName in localABInfo.Keys)
                        {
                            //如果可读写文件夹中有内容则删除
                            if (File.Exists(Application.persistentDataPath + "/" + abName))
                                File.Delete(Application.persistentDataPath + "/" + abName);
                        }
                        updateInfoCallBack("下载更新AB包文件");
                        //下载待更新列表中的所有AB包
                        DownLoadABFile((isOver) =>
                        {
                            if (isOver)
                            {
                                //把之前读取的远端对比文件信息存储到本地
                                updateInfoCallBack("更新本地AB包对比文件完成");
                                File.WriteAllText(Application.persistentDataPath + "/ABCompareInfo.txt", remoteInfo);
                                //更新本地AB包对比文件
                            }
                            overCallBack(isOver);
                        }, updateInfoCallBack);
                    }
                    else
                        overCallBack(false);
                });
            }
            else
            {
                overCallBack(false);
            }
        });
    }
    /// <summary>
    /// 下载AB包对比文件
    /// </summary>
    /// <param name="overCallback"></param>
    public async void DownLoadABCompareFile(UnityAction<bool> overCallback)
    {
        //从资源服务器下载资源对比文件
        //www UnityWebRequest 适合http服务器,更简单
        //Debug.Log(Application.persistentDataPath);
        bool isOver = false;
        int reDownLoadMaxNum = 5;
        //本地存储路径
        string localPath = Application.persistentDataPath + "/";
        while (!isOver && reDownLoadMaxNum > 0)
        {
            await Task.Run(() =>
            {
                isOver = DownLoadFile("ABCompareInfo.txt", localPath + "ABCompareInfo_TMP.txt");
            });
            --reDownLoadMaxNum;
        }
        //告诉外部是否成功
        overCallback?.Invoke(isOver);//传空不执行
    }
    /// <summary>
    /// 获取下载下来的AB包信息
    /// </summary>
    public void GetRemoteABCompareFileInfo(string info, Dictionary<string, ABInfo> ABInfo)
    {
        //根据资源对比文件的字符串进行拆分,TMP临时文件
        // string info = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_TMP.txt");
        string[] strs = info.Split('|');
        string[] infos = null;
        for (int i = 0; i < strs.Length; i++)
        {
            //名字 大小 MD5码
            infos = strs[i].Split(' ', ' ');
            //存储每一个远端AB包信息,后面对比
            ABInfo.Add(infos[0], new ABInfo(infos[0], infos[1], infos[2]));
        }
        //Debug.Log("AB包对比文件内容获取结束");
    }
    /// <summary>
    /// 本地AB包对比文件加载和解析
    /// </summary>
    public void GetLocalABCompareFileInfo(UnityAction<bool> overCallBack)
    {
        //如果可读可写文件夹中有对比文件
        if (File.Exists(Application.persistentDataPath + "/ABCompareInfo.txt"))
        {
            StartCoroutine(GetLocalABCompareFileInfo(Application.persistentDataPath + "/ABCompareInfo.txt", overCallBack));
        }
        //可读可写文件没有对比文件时,加载默认资源(第一次启动)
        else if (File.Exists(Application.streamingAssetsPath + "/ABCompareInfo.txt"))
        {
            StartCoroutine(GetLocalABCompareFileInfo(Application.streamingAssetsPath + "/ABCompareInfo.txt", overCallBack));
        }
        else
        {
            overCallBack(true);
        }
        //如果两个文件都没有,表示第一次启动且没有默认资源
    }
    /// <summary>
    /// 加载本地信息存入字典
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    private IEnumerator GetLocalABCompareFileInfo(string filePath, UnityAction<bool> overCallBack)
    {
        //加载本地文件
        UnityWebRequest req = UnityWebRequest.Get(filePath);
        yield return req.SendWebRequest();
        //加载成功
        if (req.result == UnityWebRequest.Result.Success)
        {
            GetRemoteABCompareFileInfo(req.downloadHandler.text, localABInfo);
            //Debug.Log(req.downloadHandler.text);
            overCallBack(true);
        }
        else
        {
            overCallBack(false);
        }
    }
    public async void DownLoadABFile(UnityAction<bool> overCallBack, UnityAction<string> updatePro)
    {
        //遍历字典的键 根据文件名下载AB包
        /* foreach (string name in remoteABInfo.Keys)
         {
             //直接放入待下载列表
             //-----------------与对比文件对比排除不变文件
             downLoadList.Add(name);
         }*/
        //本地存储路径
        string localPath = Application.persistentDataPath + "/";
        //是否下载成功
        bool isOver;
        //下载成功列表
        List<string> _list = new List<string>();
        //重新下载最大次数
        int reDownLoadMaxNum = 5;
        //下载成功资源数
        int downLoadOverNum = 0;
        //该次需下载资源数
        int dowmLoadMaxNum = downLoadList.Count;
        //循环目的 进行多次循环下载,避免网络异常下载失败
        while (downLoadList.Count > 0 && reDownLoadMaxNum > 0)
        {
            for (int i = 0; i < downLoadList.Count; i++)
            {
                isOver = false;
                await Task.Run(() =>
                {
                    isOver = DownLoadFile(downLoadList[i], localPath + downLoadList[i]);
                });
                if (isOver)
                {
                    //进度
                    updatePro(++downLoadOverNum + "/" + dowmLoadMaxNum);
                    //Debug.Log("下载进度:" + (++downLoadOverNum) + "/" + downLoadList.Count);
                    _list.Add(downLoadList[i]);//下载成功则记录
                }
            }
            //把下载的文件名从待下载列表移除
            for (int i = 0; i < _list.Count; i++)
            {
                downLoadList.Remove(_list[i]);
            }
            --reDownLoadMaxNum;
        }

        //所有文件下载完成,委托告诉是否外部下载完成     
        overCallBack(downLoadList.Count == 0);
    }
    private bool DownLoadFile(string flieName, string localPath)
    {
        try
        {
            //创建Ftp链接下载
            FtpWebRequest req = FtpWebRequest.Create(new Uri("ftp://192.168.1.103/AB/PC/" + flieName)) as FtpWebRequest;
            //设置通信凭证下载(如果有匿名账号可不设置凭证)
            NetworkCredential n = new NetworkCredential("Box", "jj13579");//账号密码
            req.Credentials = n;
            //设置代理为null,请求完毕后关闭控制连接,操作命令-上传,指定传输类型-2进制
            req.Proxy = null;
            req.KeepAlive = false;
            //操作命令,下载
            req.Method = WebRequestMethods.Ftp.DownloadFile;
            req.UseBinary = true;
            //下载文件 创建流对象
            FtpWebResponse res = req.GetResponse() as FtpWebResponse;
            Stream downLoadStream = res.GetResponseStream();
            //读取文件信息,写入流对象
            using (FileStream file = File.Create(localPath))
            {
                //2kb下载
                byte[] bytes = new byte[2048];
                int contentLength = downLoadStream.Read(bytes, 0, bytes.Length);//读到内容长度
                                                                                //循环下载文件数据
                while (contentLength != 0)
                {
                    //写入到本地文件流中
                    file.Write(bytes, 0, contentLength);
                    //写完再读,为0为止
                    contentLength = downLoadStream.Read(bytes, 0, bytes.Length);
                }
                //循环完毕 下载结束
                file.Close();
                downLoadStream.Close();
            }
            return true;
        }
        catch (Exception ex)
        {
            Debug.Log(flieName + "下载失败:" + ex);
            return false;
        }
    }
    private void OnDestroy()
    {
        instance = null;
    }
    public class ABInfo//AB包信息
    {
        public string name;//名字
        public long size;//大小
        public string md5;//MD5码
        public ABInfo(string name, string size, string md5)//构造函数
        {
            this.name = name;
            this.size = long.Parse(size);
            this.md5 = md5;
        }
    }
}

使用

ABUpdateMgr.Instance.CheckUpdate((isOver) =>
        {
            if (isOver)
                Debug.Log("检测更新完成");
            else
                Debug.Log("检测更新失败,请检测网络");
        }, (str) =>
        {
            Debug.Log(str);
        });