13.下载更新删除资源

13.资源更新-资源更新删除-下载更新删除资源


13.1 知识点

更新资源思路

  • 遍历远端 AB 包字典
  • 发现本地 AB 包字典没有对应 AB 包就直接下载
  • 发现本地 AB 包字典有对应 AB 包但是 MD5 码不同就更新
  • 遍历远端 AB 包字典时边遍历边移除本地 AB 包字典可能存在的 AB 包,这样本地 AB 包字典剩下的就是要删除的 AB 包

注释下载远程 AssetBundle 文件的函数中直接把所有远端 AB 包都下载的逻辑,外部更新委托改成字符串作为参数

/// <summary>
/// 下载远程AssetBundle文件的函数,异步执行。
/// </summary>
/// <param name="overCallBack">下载完成回调,参数为是否全部下载成功。</param>
/// <param name="updatePro">更新下载进度回调,参数为已下载资源数量和总资源数量。</param>
public async void DownLoadABFile(UnityAction<bool> overCallBack, UnityAction<string> updatePro)
{
    //// 1. 遍历字典的键,根据文件名将AssetBundle包添加到待下载列表中
    //foreach (string name in remoteAssetBundleInfoDictionary.Keys)
    //{
    //    // 直接将文件名放入待下载列表中
    //    assetBundleDownLoadList.Add(name);
    //}

    // 本地存储路径,由于多线程不能访问Unity相关内容,因此在外部声明
    string localPath = Application.persistentDataPath + "/";

    // 是否下载成功的标志
    bool isOver = false;

    // 下载成功的文件名列表,用于移除下载成功的内容
    List<string> tempList = new List<string>();

    // 重新下载的最大次数
    int reDownLoadMaxNum = 5;

    // 下载成功的资源数量
    int downLoadOverNum = 0;

    // 需要下载的总资源数量
    int downLoadMaxNum = assetBundleDownLoadList.Count;

    // while循环的目的是进行n次重新下载,避免网络异常时下载失败
    while (assetBundleDownLoadList.Count > 0 && reDownLoadMaxNum > 0)
    {
        for (int i = 0; i < assetBundleDownLoadList.Count; i++)
        {
            // 通过Task.Run在异步线程中执行下载操作
            isOver = false;
            await Task.Run(() =>
            {
                isOver = DownLoadFile(assetBundleDownLoadList[i], localPath + assetBundleDownLoadList[i]);
            });

            if (isOver)
            {
                // 2. 更新下载进度,通知外部已下载资源数量和总资源数量
                updatePro(++downLoadOverNum + "/" + downLoadMaxNum);
                tempList.Add(assetBundleDownLoadList[i]); // 下载成功记录下来
            }
        }

        // 将下载成功的文件名从待下载列表中移除
        for (int i = 0; i < tempList.Count; i++)
            assetBundleDownLoadList.Remove(tempList[i]);

        // 递减重新下载的次数
        --reDownLoadMaxNum;
    }

    // 所有内容都下载完毕,通过回调告知外部是否下载完成
    overCallBack(assetBundleDownLoadList.Count == 0);
}

在检查更新函数前先清空容器,写 AB 包对比下载更新删除的逻辑

/// <summary>
/// 外部调用的检查AB包更新的方法
/// </summary>
/// <param name="overCallBack">检查完的回调</param>
/// <param name="updateInfoCallBack">更新信息的委托</param>
public void CheckUpdate(UnityAction<bool> overCallBack, UnityAction<string> updateInfoCallBack)
{
    //为了避免由于上一次报错 而残留信息 所以我们清空它
    remoteAssetBundleInfoDictionary.Clear();
    localAssetBundleInfoDictionary.Clear();
    assetBundleDownLoadList.Clear();

    //1.加载远端资源对比文件
    DownLoadABCompareFile((isOver) =>
    {
        updateInfoCallBack("开始更新资源");

        if (isOver)
        {
            updateInfoCallBack("远端对比文件下载结束");

            string remoteInfo = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_TMP.txt");
            updateInfoCallBack("得到远端对比文件信息并开始解析到字典");

            GetABCompareFileInfoToDictionary(remoteInfo, remoteAssetBundleInfoDictionary);
            updateInfoCallBack("解析远端对比文件完成");

            //2.加载本地资源对比文件
            GetLocalABCompareFileInfo((isOver) => {
                if (isOver)
                {
                    updateInfoCallBack("解析本地对比文件完成");
                    //3.对比他们 然后进行AB包下载
                    updateInfoCallBack("开始对比");
                    foreach (string abName in remoteAssetBundleInfoDictionary.Keys)
                    {
                        //1.判断 哪些资源时新的 然后记录 之后用于下载
                        //这由于本地对比信息中没有叫这个名字的AB包 所以我们记录下载它
                        if (!localAssetBundleInfoDictionary.ContainsKey(abName))
                            assetBundleDownLoadList.Add(abName);

                        //发现本地有同名AB包 然后继续处理
                        else
                        {
                            //2.判断 哪些资源是需要更新的 然后记录 之后用于下载
                            //对比md5码 判断是否需要更新
                            if (localAssetBundleInfoDictionary[abName].md5 != remoteAssetBundleInfoDictionary[abName].md5)
                                assetBundleDownLoadList.Add(abName);
                            //如果md5码相等 证明是同一个资源 不需要更新

                            //3.判断 哪些资源需要删除
                            //每次检测完一个名字的AB包 就移除本地的信息 那么本地剩下来的信息 就是远端没有的内容
                            //我们就可以把他们删除了
                            localAssetBundleInfoDictionary.Remove(abName);
                        }
                    }

                    updateInfoCallBack("对比完成");
                    updateInfoCallBack("删除无用的AB包文件");
                    //上面对比完了 那么我们就先删除没用的内容 再下载AB包
                    //删除无用的AB包
                    foreach (string abName in localAssetBundleInfoDictionary.Keys)
                    {
                        //如果可读写文件夹中有内容 我们就删除它 
                        //默认资源中的 信息 我们没办法删除
                        if (File.Exists(Application.persistentDataPath + "/" + abName))
                            File.Delete(Application.persistentDataPath + "/" + abName);
                    }

                    updateInfoCallBack("下载和更新AB包文件");
                    //下载待更新列表中的所有AB包
                    //下载
                    DownLoadABFile((isOver) =>
                    {
                        if (isOver)
                        {
                            //下载完所有AB包文件后
                            //把本地的AB包对比文件 更新为最新
                            //把之前读取出来的 远端对比文件信息 存储到 本地 
                            updateInfoCallBack("更新本地AB包对比文件为最新");
                            File.WriteAllText(Application.persistentDataPath + "/ABCompareInfo.txt", remoteInfo);
                        }
                        overCallBack(isOver);
                    }, updateInfoCallBack);
                }
                else
                    overCallBack(false);
            });
        }
        else
        {
            overCallBack(false);
        }
    });
}

进行测试,尝试删除,修改,重写上传本地包看看会不会生效

print(Application.persistentDataPath);
AssetBundleUpdateManager.Instance.CheckUpdate((isOver) =>
{
    print("检查更新完成");
}, (infoString) =>
{
    print(infoString);
});

13.2 知识点代码

AssetBundleUpdateManager

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;
using static ABUpdateMgr;

//AB包更新管理器
public class AssetBundleUpdateManager : BaseSingletonInMonoBehaviour<AssetBundleUpdateManager>
{
    //用于存储远端AB包信息的字典 之后 和本地进行对比即可完成 更新 下载相关逻辑
    private Dictionary<string, AssetBundleInfo> remoteAssetBundleInfoDictionary = new Dictionary<string, AssetBundleInfo>();

    //用于存储本地AB包信息的字典 主要用于和远端信息对比
    private Dictionary<string, AssetBundleInfo> localAssetBundleInfoDictionary = new Dictionary<string, AssetBundleInfo>();

    //这个是待下载的AB包列表文件 存储AB包的名字
    private List<string> assetBundleDownLoadList = new List<string>();

    /// <summary>
    /// 外部调用的检查AB包更新的方法
    /// </summary>
    /// <param name="overCallBack">检查完的回调</param>
    /// <param name="updateInfoCallBack">更新信息的委托</param>
    public void CheckUpdate(UnityAction<bool> overCallBack, UnityAction<string> updateInfoCallBack)
    {
        //为了避免由于上一次报错 而残留信息 所以我们清空它
        remoteAssetBundleInfoDictionary.Clear();
        localAssetBundleInfoDictionary.Clear();
        assetBundleDownLoadList.Clear();

        //1.加载远端资源对比文件
        DownLoadABCompareFile((isOver) =>
        {
            updateInfoCallBack("开始更新资源");

            if (isOver)
            {
                updateInfoCallBack("远端对比文件下载结束");

                string remoteInfo = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_TMP.txt");
                updateInfoCallBack("得到远端对比文件信息并开始解析到字典");

                GetABCompareFileInfoToDictionary(remoteInfo, remoteAssetBundleInfoDictionary);
                updateInfoCallBack("解析远端对比文件完成");

                //2.加载本地资源对比文件
                GetLocalABCompareFileInfo((isOver) => {
                    if (isOver)
                    {
                        updateInfoCallBack("解析本地对比文件完成");
                        //3.对比他们 然后进行AB包下载
                        updateInfoCallBack("开始对比");
                        foreach (string abName in remoteAssetBundleInfoDictionary.Keys)
                        {
                            //1.判断 哪些资源时新的 然后记录 之后用于下载
                            //这由于本地对比信息中没有叫这个名字的AB包 所以我们记录下载它
                            if (!localAssetBundleInfoDictionary.ContainsKey(abName))
                                assetBundleDownLoadList.Add(abName);

                            //发现本地有同名AB包 然后继续处理
                            else
                            {
                                //2.判断 哪些资源是需要更新的 然后记录 之后用于下载
                                //对比md5码 判断是否需要更新
                                if (localAssetBundleInfoDictionary[abName].md5 != remoteAssetBundleInfoDictionary[abName].md5)
                                    assetBundleDownLoadList.Add(abName);
                                //如果md5码相等 证明是同一个资源 不需要更新

                                //3.判断 哪些资源需要删除
                                //每次检测完一个名字的AB包 就移除本地的信息 那么本地剩下来的信息 就是远端没有的内容
                                //我们就可以把他们删除了
                                localAssetBundleInfoDictionary.Remove(abName);
                            }
                        }

                        updateInfoCallBack("对比完成");
                        updateInfoCallBack("删除无用的AB包文件");
                        //上面对比完了 那么我们就先删除没用的内容 再下载AB包
                        //删除无用的AB包
                        foreach (string abName in localAssetBundleInfoDictionary.Keys)
                        {
                            //如果可读写文件夹中有内容 我们就删除它 
                            //默认资源中的 信息 我们没办法删除
                            if (File.Exists(Application.persistentDataPath + "/" + abName))
                                File.Delete(Application.persistentDataPath + "/" + abName);
                        }

                        updateInfoCallBack("下载和更新AB包文件");
                        //下载待更新列表中的所有AB包
                        //下载
                        DownLoadABFile((isOver) =>
                        {
                            if (isOver)
                            {
                                //下载完所有AB包文件后
                                //把本地的AB包对比文件 更新为最新
                                //把之前读取出来的 远端对比文件信息 存储到 本地 
                                updateInfoCallBack("更新本地AB包对比文件为最新");
                                File.WriteAllText(Application.persistentDataPath + "/ABCompareInfo.txt", remoteInfo);
                            }
                            overCallBack(isOver);
                        }, updateInfoCallBack);
                    }
                    else
                        overCallBack(false);
                });
            }
            else
            {
                overCallBack(false);
            }
        });
    }

    /// <summary>
    /// 下载服务器中的文件
    /// </summary>
    /// <param name="fileName">文件名</param>
    /// <param name="localPath">本地路径</param>
    /// <returns></returns>
    private bool DownLoadFile(string fileName, string localPath)
    {
        try
        {
            //1.创建一个FTP连接 用于下载
            FtpWebRequest ftpWebRequest = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/AB/PC/" + fileName)) as FtpWebRequest;

            //2.设置一个通信凭证 这样才能下载(如果有匿名账号 可以不设置凭证 但是实际开发中 建议 还是不要设置匿名账号)
            NetworkCredential networkCredential = new NetworkCredential("MrTao", "MrTao");
            ftpWebRequest.Credentials = networkCredential;

            //3.其它设置
            //  设置代理为null
            ftpWebRequest.Proxy = null;
            //  请求完毕后 是否关闭控制连接
            ftpWebRequest.KeepAlive = false;
            //  操作命令-下载
            ftpWebRequest.Method = WebRequestMethods.Ftp.DownloadFile;
            //  指定传输的类型 2进制
            ftpWebRequest.UseBinary = true;

            //4.下载文件
            //  ftp的流对象
            FtpWebResponse ftpWebResponse = ftpWebRequest.GetResponse() as FtpWebResponse;
            Stream downLoadStream = ftpWebResponse.GetResponseStream();
            using (FileStream fileStream = File.Create(localPath))
            {
                //一点一点的下载内容
                byte[] bytes = new byte[2048];

                //返回值 代表读取了多少个字节
                int contentLength = downLoadStream.Read(bytes, 0, bytes.Length);

                //循环下载数据
                while (contentLength != 0)
                {
                    //写入到本地文件流中
                    fileStream.Write(bytes, 0, contentLength);
                    //写完再读
                    contentLength = downLoadStream.Read(bytes, 0, bytes.Length);
                }

                //循环完毕后 证明下载结束
                fileStream.Close();
                downLoadStream.Close();

                print(fileName + "下载成功");
                return true;
            }
        }
        catch (Exception ex)
        {
            print(fileName + "下载失败" + ex.Message);
            return false;
        }

    }

    /// <summary>
    /// 异步下载远端资源对比文件的函数,提供回调通知下载完成状态
    /// </summary>
    /// <param name="overCallBack">下载完成后的回调 通常会调用获取下载下来的AB包中的信息函数</param>
    public async void DownLoadABCompareFile(UnityAction<bool> overCallBack)
    {
        // 打印本地持久化数据路径
        print(Application.persistentDataPath);

        // 初始化下载是否成功的标志
        bool isOver = false;

        // 设置重新下载的最大次数
        int reDownLoadMaxNum = 5;

        // 本地存储路径,由于多线程不能访问Unity主线程的Application,所以在外面声明
        string localPath = Application.persistentDataPath;

        // 循环下载,直到下载成功或达到最大重新下载次数
        while (!isOver && reDownLoadMaxNum > 0)
        {
            // 使用异步任务Task.Run在后台线程中执行下载操作
            await Task.Run(() =>
            {
                // 执行下载文件的操作,将下载结果存储在isOver中
                isOver = DownLoadFile("ABCompareInfo.txt", localPath + "/ABCompareInfo_TMP.txt");
            });

            // 递减重新下载的次数
            --reDownLoadMaxNum;
        }

        // 通过回调通知外部下载是否完成
        overCallBack?.Invoke(isOver);
    }

    /// <summary>
    /// 获取AB包对比文件中的信息并解析到对应的字典中 可能是远程字典也可能是本地字典
    /// </summary>
    /// <param name="info"></param>
    /// <param name="assetBundleInfoDictionary"></param>
    public void GetABCompareFileInfoToDictionary(string info, Dictionary<string, AssetBundleInfo> assetBundleInfoDictionary)
    {
        //这就不去读取文件了 直接让外部读好了 传进来
        // 读取资源对比文件的内容
        //string info = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_TMP.txt");

        // 使用竖线分割字符串,将每个AB包的信息分离
        string[] strs = info.Split('|');
        string[] infos = null;

        // 遍历每个分割后的AB包信息
        for (int i = 0; i < strs.Length; i++)
        {
            // 使用空格分割每个AB包的详细信息
            infos = strs[i].Split(' ');

            // 记录每一个远端AB包的信息,之后用于对比
            assetBundleInfoDictionary.Add(infos[0], new AssetBundleInfo(infos[0], infos[1], infos[2]));
        }
    }

    /// <summary>
    /// 本地AB包对比文件加载 并解析信息到本地字典
    /// </summary>
    public void GetLocalABCompareFileInfo(UnityAction<bool> overCallBack)
    {
        //Application.persistentDataPath;
        //如果可读可写文件夹中 存在对比文件 说明之前我们已经下载更新过了
        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>
    /// 协同程序 本地AB包对比文件加载 并解析信息到本地字典
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    private IEnumerator GetLocalABCOmpareFileInfo(string filePath, UnityAction<bool> overCallBack)
    {
        //通过 UnityWebRequest 去加载本地文件
        UnityWebRequest unityWebRequest = UnityWebRequest.Get(filePath);

        yield return unityWebRequest.SendWebRequest();

        //获取文件成功 继续往下执行
        if (unityWebRequest.result == UnityWebRequest.Result.Success)
        {
            GetABCompareFileInfoToDictionary(unityWebRequest.downloadHandler.text, localAssetBundleInfoDictionary);
            overCallBack(true);
        }
        else
        {
            overCallBack(false);
        }

    }

    /// <summary>
    /// 下载远程AssetBundle文件的函数,异步执行。
    /// </summary>
    /// <param name="overCallBack">下载完成回调,参数为是否全部下载成功。</param>
    /// <param name="updatePro">更新下载进度回调,参数为已下载资源数量和总资源数量。</param>
    public async void DownLoadABFile(UnityAction<bool> overCallBack, UnityAction<string> updatePro)
    {
        //// 1. 遍历字典的键,根据文件名将AssetBundle包添加到待下载列表中
        //foreach (string name in remoteAssetBundleInfoDictionary.Keys)
        //{
        //    // 直接将文件名放入待下载列表中
        //    assetBundleDownLoadList.Add(name);
        //}

        // 本地存储路径,由于多线程不能访问Unity相关内容,因此在外部声明
        string localPath = Application.persistentDataPath + "/";

        // 是否下载成功的标志
        bool isOver = false;

        // 下载成功的文件名列表,用于移除下载成功的内容
        List<string> tempList = new List<string>();

        // 重新下载的最大次数
        int reDownLoadMaxNum = 5;

        // 下载成功的资源数量
        int downLoadOverNum = 0;

        // 需要下载的总资源数量
        int downLoadMaxNum = assetBundleDownLoadList.Count;

        // while循环的目的是进行n次重新下载,避免网络异常时下载失败
        while (assetBundleDownLoadList.Count > 0 && reDownLoadMaxNum > 0)
        {
            for (int i = 0; i < assetBundleDownLoadList.Count; i++)
            {
                // 通过Task.Run在异步线程中执行下载操作
                isOver = false;
                await Task.Run(() =>
                {
                    isOver = DownLoadFile(assetBundleDownLoadList[i], localPath + assetBundleDownLoadList[i]);
                });

                if (isOver)
                {
                    // 2. 更新下载进度,通知外部已下载资源数量和总资源数量
                    updatePro(++downLoadOverNum + "/" + downLoadMaxNum);
                    tempList.Add(assetBundleDownLoadList[i]); // 下载成功记录下来
                }
            }

            // 将下载成功的文件名从待下载列表中移除
            for (int i = 0; i < tempList.Count; i++)
                assetBundleDownLoadList.Remove(tempList[i]);

            // 递减重新下载的次数
            --reDownLoadMaxNum;
        }

        // 所有内容都下载完毕,通过回调告知外部是否下载完成
        overCallBack(assetBundleDownLoadList.Count == 0);
    }

}

Lesson13_资源更新_资源更新删除_下载更新删除资源

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson13_资源更新_资源更新删除_下载更新删除资源 : MonoBehaviour
{
    void Start()
    {
        print(Application.persistentDataPath);
        AssetBundleUpdateManager.Instance.CheckUpdate((isOver) =>
        {
            print("检查更新完成");
        }, (infoString) =>
        {
            print(infoString);
        });
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏