【Unity】AB包下载

【Unity】AB包下载

1.使用插件打AB包

a.AB包分类

一般地,将预制体作为AB包资源,不仅需要对预制体本身进行归类,还要对其涉及的动画(AnimationClip)、动画状态机(AnimatorController)、以及所用到的骨骼(Avatar)、模型(Mesh)、模型所用的材质(Matreial)、材质所用的图片(Images)、Shader等进行归类。
还有一种是AB包依赖情况,后面接触会在这里补充。
归类的资源对象名字是自定义,这里写XXX.unity3d,是为了在项目中处理。最后打包出来的就是这个名字的AB包,不过从AB包加载出来具体预制体时,就是当前打包时改预制体的原名。例如是存放在XXX.unity3d的AB包中的名字为 Cube 预制体。
在这里插入图片描述
在这里插入图片描述

b.使用官方打包工具 AssetBundles-Browser-master

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
压缩对象的设置及输出文件
在这里插入图片描述
在这里插入图片描述

2.创建AB包对比文件

AB包对比文件的作用:

  1. 判断本地是否存在AB包,有就不需要从服务器下载
  2. 判断本地AB包是否最新,不是则需要从服务器下载

递归查找文件夹中所有文件信息

    /// <summary>/// 获取文件信息/// </summary>/// <param name="target">根目录文件</param>/// <returns>该目录所有文件信息,包括子文件夹中的</returns>private List<FileInfo> GetFoldersInfo(string target){int length = 0;List<FileInfo> fileInfolist = new List<FileInfo>();//获取文件夹下所有文件DirectoryInfo directory = Directory.CreateDirectory(target);FileInfo[] fileInfos = directory.GetFiles();length = fileInfos.Length;for (int i = 0; i < length; i++){fileInfolist.Add(fileInfos[i]);}//获取文件夹下所有文件夹string[] folders = Directory.GetDirectories(target);length = folders.Length;for (int i = 0; i < length; i++){//获取子文件夹中所有文件List<FileInfo> lst = GetFoldersInfo(folders[i]);int len = lst.Count;for (int j = 0; j < len; j++){fileInfolist.Add(lst[j]);}}return fileInfolist;}

获取某个文件的MD5码

    /// <summary>/// 获取MD5码/// </summary>/// <param name="filePath">文件路径</param>/// <returns>该文件的MD5码</returns>private string GetFileMD5(string filePath){//打开文件using (FileStream file = new FileStream(filePath, FileMode.Open)){//使用MD5格式MD5 md5 = new MD5CryptoServiceProvider();//获取文件MD码byte[] data = md5.ComputeHash(file);//关闭文件file.Close();//释放占用资源file.Dispose();StringBuilder sb = new StringBuilder();for (int i = 0; i < data.Length; i++){//将数据转化成小写的16进制sb.Append(data[i].ToString("x2"));}return sb.ToString();}}

创建AB包对比文件信息

    /// <summary>/// 创建AB包对比文件/// </summary>private void CreateABCompareFile(){文件夹信息//DirectoryInfo directory = Directory.CreateDirectory(Application.dataPath + "/ABResource/AB/" + platformStr[platformIdx]);获取文件夹中所有文件//FileInfo[] fileInfos = directory.GetFiles();//获取路径中所有的文件信息 platformStr[platformIdx]为生成AB包的路劲 例如:platformStr[platformIdx] == "Android"List<FileInfo> fileInfos = GetFoldersInfo(Application.dataPath + "/ABResource/AB/" + platformStr[platformIdx]);//声明字符串用来存储AB对比数据string abCompareInfo = string.Empty;int index = -1;string localPath = "";foreach (FileInfo fileInfo in fileInfos){//没有扩展名的就是AB包,这里只是客观的进行判断,具体看打出来的AB包规则//AB包保存的目录也会生成一个文件if (fileInfo.Extension == "" && fileInfo.Name != platformStr[platformIdx]){//存储 AB包名字 字节长度 MD5码  用空格隔开,用"|"隔开每个文件信息//收集相对路劲index = fileInfo.FullName.LastIndexOf(platformStr[platformIdx]);if(index == -1){continue;}localPath = fileInfo.FullName.Substring(index + platformStr[platformIdx].Length + 1);localPath = localPath.Replace("\\", "/");//abCompareInfo += fileInfo.Name + " " + fileInfo.Length + " " + GetFileMD5(fileInfo.FullName);//存储成字符串abCompareInfo += localPath + " " + fileInfo.Length + " " + GetFileMD5(fileInfo.FullName);abCompareInfo += "|";}}//去除最后一个“|”字符abCompareInfo = abCompareInfo.Substring(0, abCompareInfo.Length - 1);//创建 ABCompareInfo.txt 文件作为AB包对比信息文件File.WriteAllText(Application.dataPath + "/ABResource/AB/" + platformStr[platformIdx] + "/ABCompareInfo.txt", abCompareInfo);//刷新Unity资源目录AssetDatabase.Refresh();Debug.Log("[" + platformStr[platformIdx] + "]生成AB包对比文件成功");}
调用CreateABCompareFile()后,就会在对应的文件目录下生成AB包对比文件,接着将对比文件和AB包文件上传到服务器。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.从服务器下载文件并保存本地

    //服务器路劲private static string serverPath = "http://127.0.0.1:8800/Game/";/// <summary>/// 下载文件并保存本地/// </summary>/// <param name="fileName">下载的文件路径(不包括服务器路径 包括后缀)</param>/// <param name="localPath">保存路径(包括文件名和后缀)</param>/// <param name="callback">结束回调</param>/// <returns>下载文件携程</returns>private IEnumerator DownLoadFile(string filePath, string localPath, UnityAction<bool> callback = null){//下载的文件路劲 serverPath 为服务器所存放AB包的目录 这里使用的是本地服务器 例如:serverPath == "http://127.0.0.1:8800/Game/"string requestPath =
#if UNITY_EDITORserverPath + "PC/" + filePath;
#elseserverPath + "Android/" + filePath;
#endifUnityWebRequest unityWebRequest = UnityWebRequest.Get(requestPath);yield return unityWebRequest.SendWebRequest();//没有出现错误并且下载完毕if(!unityWebRequest.isHttpError && !unityWebRequest.isNetworkError && unityWebRequest.isDone){//将文件保存到本地using (FileStream fileStream = File.Create(localPath)){fileStream.Write(unityWebRequest.downloadHandler.data, 0, unityWebRequest.downloadHandler.data.Length);}//回调成功Debug.Log("下载" + filePath + "成功");if (callback != null){callback(true);}}else{//回调失败Debug.Log("下载" + filePath + "失败");if (callback != null){callback(false);}}}

4.下载AB包对比文件

    //服务器AB包对比文件名 以及 本地AB包对比文件名private static string remoteABCompareName = "ABCompareInfo.txt";/// <summary>/// 下载AB包对比文件/// </summary>/// <param name="callback">结束回调</param>private void DownLoadABCompareFile(UnityAction<bool> callback = null){//开启下载 remoteABCompareName 文件携程 remoteABCompareName为AB包对比文件名 例如:remoteABCompareName == "ABCompareInfo.txt"//下载完成后会保存到 数据持久化路径Application.persistentDataPathStartCoroutine(DownLoadFile(remoteABCompareName, Application.persistentDataPath + "/" + remoteABCompareName, isDown =>{if (isDown){//这里代表已经下载并保存到本地完成if (callback != null){callback(true);}}else{if (callback != null){callback(false);}}}));}
至此,已经完成AB包对比文件的下载。将 [ http://127.0.0.1:8800/Game/ABCompareInfo.txt ](本地服务器的AB包对比文件)下载下来,并保存为[ Application.persistentDataPath + "/" + ABCompareInfo.txt ]。

5.失败后多次下载的优化

用户由于网络不稳定,会出现下载失败的情况,因此设计自动重新下载的次数,让用户在规定次数中,无需手动进行下载。
    //从服务器下载下来后,临时保存的对比文件路径名private static string localABCompareName = "ABCompareInfo_TMP.txt";//是否成功下载AB包对比文件private bool isSuccDownLoadABCompare = false;//重新下载AB包对比文件次数private int reDownLoadCompareMaxNums = 5;//当前下载AB包对比文件次数private int downLoadCompareNums = 0;/// <summary>/// 下载AB包对比文件/// </summary>/// <param name="callback">结束回调</param>private void DownLoadABCompareFile(UnityAction<bool> callback = null){//没有成功下载,并且下载次数小于重新下载最大次数if (!isSuccDownLoadABCompare && downLoadCompareNums < reDownLoadCompareMaxNums){downLoadCompareNums++;//开启下载 remoteABCompareName 文件携程 remoteABCompareName为AB包对比文件名 例如:localABCompareName == remoteABCompareName == "ABCompareInfo.txt"//下载完成后会保存到 数据持久化路径Application.persistentDataPathStartCoroutine(DownLoadFile(remoteABCompareName, Application.persistentDataPath + "/" + localABCompareName, isDown =>{if (isDown){//这里代表已经下载并保存到本地完成if (callback != null){callback(true);}isSuccDownLoadABCompare = true;}else{Debug.Log("下载" + remoteABCompareName + "失败,重试[" + downLoadCompareNums + "]次");//下载方法回调失败isSuccDownLoadABCompare = false;if (downLoadCompareNums < reDownLoadCompareMaxNums){//还可以重新下载DownLoadABCompareFile(callback);}else{//重新下载次数用尽 回调失败if (callback != null){callback(false);}}}}));}}

6.收集AB包对比文件的信息

    private class ABInfo{public string name;public long size;public string md5;public ABInfo(string name, string size, string md5){this.name = name;this.size = long.Parse(size);this.md5 = md5;}}private Dictionary<string, ABInfo> remoteABInfo = new Dictionary<string, ABInfo>();/// <summary>/// AB包对比文件信息收集/// </summary>/// <param name="callback">收集结束回调</param>private void GetABCompareFileInfo(UnityAction<bool> callback){if(!File.Exists(Application.persistentDataPath + "/" + remoteABCompareName)){//路劲不存在的情况下if (callback != null){//回调收集信息失败callback(false);return;}}//读取下载的AB包对比文件路径string info = File.ReadAllText(Application.persistentDataPath + "/" + remoteABCompareName);//将数据信息拆分成每个AB包信息string[] strs = info.Split('|');string[] infos = null;for (int i = 0; i < strs.Length; i++){//将每个AB包信息拆分成独立数据infos = strs[i].Split(' ');//使用 remoteABInfo 字典和 ABInfo 类存储AB包信息remoteABInfo.Add(infos[0], new ABInfo(infos[0], infos[1], infos[2]));}if(callback != null){//回调收集成功callback(true);}}

7.下载AB包

    //AB包待下载List集合  用于存储AB包的相对路径名private List<string> downLoadList = new List<string>();/// <summary>/// 对比信息后 下载AB包/// </summary>/// <param name="callback">结束回调</param>/// <returns></returns>private IEnumerator DownLoadABFile(UnityAction<bool> callback = null){        string localPath = Application.persistentDataPath + "/";//临时List集合:用于记录成功下载的AB文件名List<string> tempList = new List<string>();//最大允许重新下载次数int reDownLoadABFileMaxNums = 5;//下载次数int downLoadABFileNums = 0;//下载成功个数int downLoadOverNums = 0;//待下载总数int downLoadOverMaxNums = downLoadList.Count;//待下载总数大于0 并且 下载次数在允许重新在次数中while (downLoadList.Count > 0 && downLoadABFileNums < reDownLoadABFileMaxNums){//下载次数增加downLoadABFileNums++;//下载集合中开始下载for (int i = 0; i < downLoadList.Count; i++){//在本地创建目录供AB包保存string[] relativePaths = downLoadList[i].Split('/');string tmpDir = localPath;for(int j = 0; j < relativePaths.Length - 1; j++){tmpDir = tmpDir + relativePaths[j];if (!Directory.Exists(tmpDir)){Directory.CreateDirectory(tmpDir);}tmpDir = tmpDir + "/";}//开启下载文件协程  downLoadList[i] 文件名     localPath + downLoadList[i] 保存地址    下载结束回调yield return StartCoroutine(DownLoadFile(downLoadList[i], localPath + downLoadList[i], isDown =>{if(isDown){//下载成功 记录下载的文件tempList.Add(downLoadList[i]);downLoadOverNums++;}}));Debug.Log("下载AB包进度:" + downLoadOverNums + "/" + downLoadOverMaxNums);}//将下载成功的文件名从待下载列表删除for (int i = 0; i < tempList.Count; i++){downLoadList.Remove(tempList[i]);}//清空记录的文件名List集合tempList.Clear();//如果待下载集合中还有文件没下载继续循环}if(downLoadList.Count == 0){//待下载集合没有内容,下载所有的AB包成功if (callback != null){//回调成功callback(true);}}else{//下载失败if (callback != null){//回调失败callback(false);}}}
至此,已经可以根据AB包对比文件中的AB包信息下载AB包。将 remoteABInfo 中的key添加到downLoadList中,就能够完成AB包对比文件中所包含的AB包的下载。

添加到 DownLoadABFile 协程中,写在用到 downLoadList 的代码 之前。

    foreach (string name in remoteABInfo.Keys){downLoadList.Add(name);}

在这里插入图片描述

8.添加待下载AB包路径名的逻辑及代码优化

待下载List集合需添加AB包路径名进行下载的逻辑:

  1. 客户端未包含任何AB包,需要下载读取到的对比文件中所有的AB包。
  2. 客户端包含部分AB包,需要下载本地对比文件中没有保存的AB包。
  3. 客户端包含部分旧的AB包,需要对比本地对比文件和服务器对比文件,从而下载最新的AB包。

根据项目不同情况,将部分AB包 及 这部分AB包的信息文件(AB包对比文件)存放在 streamingAssetsPath 中,跟随包一起导出。或者不将任何AB包跟随包一起导出。因此,从服务器下载AB包对比文件后,如果包中原本存在AB包对比文件,则需要对比两个文件,从而确定要进行下载或者更新的AB包,从而添加到待下载队列进行下载,之后记得更新本地AB包对比文件。

a.优化AB包对比文件信息,达到重用目的

    //服务器对比文件信息private Dictionary<string, ABInfo> remoteABInfo = new Dictionary<string, ABInfo>();//本地对比文件信息private Dictionary<string, ABInfo> localABInfo = new Dictionary<string, ABInfo>();/// <summary>/// AB包对比文件信息收集(更新)/// </summary>/// <param name="filePath">对比文件路径</param>/// <param name="dic">存放信息的对应字典</param>/// <param name="callback">收集结束回调</param>private void GetABCompareFileInfo(string filePath, Dictionary<string, ABInfo> dic, UnityAction<bool> callback){if (!File.Exists(filePath)){//路劲不存在的情况下if (callback != null){//回调收集信息失败callback(false);return;}}//读取下载的AB包对比文件路径string info = File.ReadAllText(filePath);//将数据信息拆分成每个AB包信息string[] strs = info.Split('|');string[] infos = null;for (int i = 0; i < strs.Length; i++){//将每个AB包信息拆分成独立数据infos = strs[i].Split(' ');//使用字典和 ABInfo 类存储AB包信息dic.Add(infos[0], new ABInfo(infos[0], infos[1], infos[2]));}if (callback != null){//回调收集成功callback(true);}}

b.判断并收集本地AB包对比文件信息

    /// <summary>/// 根据本地最新AB对比文件 收集信息/// </summary>/// <param name="callback">收集结束回调</param>private void GetLocalABCompareFileInfo(UnityAction<bool> callback){if (File.Exists(Application.persistentDataPath + "/" + remoteABCompareName)){//Application.persistentDataPath + "/" + remoteABCompareName 指的是之后更新后的本地AB包对比文件GetABCompareFileInfo(Application.persistentDataPath + "/" + remoteABCompareName, localABInfo, callback);}else if(File.Exists(Application.streamingAssetsPath + "/" + remoteABCompareName)){//Application.streamingAssetsPath + "/" + remoteABCompareName 指的是初始跟随包一起导出的AB包对比文件GetABCompareFileInfo(Application.streamingAssetsPath + "/" + remoteABCompareName, localABInfo, callback);}else{//没有AB包对比文件//没有AB包对比文件代表没有AB包,所以本地AB包信息应当清空localABInfo.Clear();//默认成功收集本地AB包信息callback(true);}}

c.两AB包对比文件进行对比的逻辑

从服务器下载下来的AB包对比文件(localABCompareName)与本地存在的AB包对比文件(remoteABCompareName)进行对比。

    /// <summary>/// 检查是否需要更新资源/// </summary>/// <param name="overCallback">结束回调</param>public void CheckUpdate(UnityAction<bool> overCallback){//清空服务器对比文件信息及本地对比文件信息的字典 及 待下载List集合remoteABInfo.Clear();localABInfo.Clear();downLoadList.Clear();//从服务器下载AB包对比文件 服务器AB包对比文件名字:remoteABCompareName  保存到本地后的AB包对比文件名字:localABCompareNameDownLoadABCompareFile(isOver =>{if (isOver){//下载成功//收集从服务器下载下来的AB包对比文件信息 并 保存到 remoteABInfo 字典中GetABCompareFileInfo(Application.persistentDataPath + "/" + localABCompareName, remoteABInfo, overCallback);//服务器AB包对比文件信息收集成功//获取本地AB对比文件 并 收集对比文件信息 保存到localABInfo中GetLocalABCompareFileInfo(isCollect =>{if (isCollect){//本地AB包对比文件信息收集成功//获取本地AB包对比文件的成功回调foreach (string abName in remoteABInfo.Keys){if (!localABInfo.ContainsKey(abName)){//将本地没有的AB包路径名添加到待下载List集合中downLoadList.Add(abName);}else{//本地有的AB包则需要对比两者MD5码是否一样if (localABInfo[abName].md5 != remoteABInfo[abName].md5){//MD5码不一样,代表本地AB包不是最新的,需要下载,因此也添加到待下载List集合中downLoadList.Add(abName);}//检查后删除与服务器一样的资源信息localABInfo.Remove(abName);}}foreach (string abName in localABInfo.Keys){//本地多出来的无用AB包需要进行删除if (File.Exists(Application.persistentDataPath + "/" + abName)){File.Delete(Application.persistentDataPath + "/" + abName);}}//两AB包对比文件对比结束 开始下载AB包StartCoroutine(DownLoadABFile(isFinish =>{if (isFinish){//完成后 更新本地AB包对比文件string remoteInfo = File.ReadAllText(Application.persistentDataPath + "/" + localABCompareName);File.WriteAllText(Application.persistentDataPath + "/" + remoteABCompareName, remoteInfo);}else{Debug.Log("下载AB包失败");}//下载AB包结束 回调结束overCallback(isFinish);}));}else{//收集本地AB包对比信息失败 回调失败overCallback(false);}});}else{//下载服务器AB包对比文件失败 回调失败overCallback(false);}});}
至此,整个流程基本结束。下载AB包对比文件,并与存在本地的AB包对比文件进行对比,之后下载最新和缺失的AB包。

9.优化资源更新判断逻辑,加入自定义消息回调

/// <summary>
/// 下载回调类型
/// </summary>
public enum DownCallbackType
{None,Message,Progress
}/// <summary>/// 检查是否需要更新资源/// </summary>/// <param name="overCallback">结束回调</param>public void CheckUpdate(UnityAction<bool> overCallback, UnityAction<DownCallbackType, string> updateCallback){//清空服务器对比文件信息及本地对比文件信息的字典 及 待下载List集合remoteABInfo.Clear();localABInfo.Clear();downLoadList.Clear();updateCallback(DownCallbackType.Message, "下载的本地路径|" + Application.persistentDataPath);//从服务器下载AB包对比文件 服务器AB包对比文件名字:remoteABCompareName  保存到本地后的AB包对比文件名字:localABCompareNameDownLoadABCompareFile(isOver =>{if(isOver){//下载成功updateCallback(DownCallbackType.Message, "下载服务器对比文件成功");//收集从服务器下载下来的AB包对比文件信息 并 保存到 remoteABInfo 字典中GetABCompareFileInfo(Application.persistentDataPath + "/" + localABCompareName, remoteABInfo, overCallback);updateCallback(DownCallbackType.Message, "服务器AB包对比文件信息收集成功");//服务器AB包对比文件信息收集成功//获取本地AB对比文件 并 收集对比文件信息 保存到localABInfo中GetLocalABCompareFileInfo(isCollect =>{if (isCollect){//本地AB包对比文件信息收集成功//获取本地AB包对比文件的成功回调updateCallback(DownCallbackType.Message, "本地AB包对比文件信息收集成功");foreach (string abName in remoteABInfo.Keys){if(!localABInfo.ContainsKey(abName)){//将本地没有的AB包路径名添加到待下载List集合中downLoadList.Add(abName);}else{//本地有的AB包则需要对比两者MD5码是否一样if (localABInfo[abName].md5 != remoteABInfo[abName].md5){//MD5码不一样,代表本地AB包不是最新的,需要下载,因此也添加到待下载List集合中downLoadList.Add(abName);}//检查后删除与服务器一样的资源信息localABInfo.Remove(abName);}}foreach(string abName in localABInfo.Keys){//本地多出来的无用AB包需要进行删除if (File.Exists(Application.persistentDataPath + "/" + abName)){File.Delete(Application.persistentDataPath + "/" + abName);}}updateCallback(DownCallbackType.Message, "两AB包对比文件对比结束 开始下载AB包");//两AB包对比文件对比结束 开始下载AB包StartCoroutine(DownLoadABFile(isFinish =>{if(isFinish){//完成后 更新本地AB包对比文件string remoteInfo = File.ReadAllText(Application.persistentDataPath + "/" + localABCompareName);File.WriteAllText(Application.persistentDataPath + "/" + remoteABCompareName, remoteInfo);updateCallback(DownCallbackType.None, "AB包下载结束");}else{updateCallback(DownCallbackType.None, "AB包下载失败");}//下载AB包结束 回调结束overCallback(isFinish);}, updateCallback));}else{updateCallback(DownCallbackType.Message, "收集本地AB包对比信息失败");//收集本地AB包对比信息失败 回调失败overCallback(false);}});}else{updateCallback(DownCallbackType.Message, "下载服务器AB包对比文件失败");//下载服务器AB包对比文件失败 回调失败overCallback(false);}});}/// <summary>/// 对比信息后 下载AB包/// </summary>/// <param name="callback">结束回调</param>/// <param name="updateCallback">消息回调</param>/// <returns></returns>private IEnumerator DownLoadABFile(UnityAction<bool> callback = null, UnityAction<DownCallbackType, string> updateCallback = null){//foreach (string name in remoteABInfo.Keys)//{//    downLoadList.Add(name);//}string localPath = Application.persistentDataPath + "/";//临时List集合:用于记录成功下载的AB文件名List<string> tempList = new List<string>();//最大允许重新下载次数int reDownLoadABFileMaxNums = 5;//下载次数int downLoadABFileNums = 0;//下载成功个数int downLoadOverNums = 0;//待下载总数int downLoadOverMaxNums = downLoadList.Count;//待下载总数大于0 并且 下载次数在允许重新在次数中while (downLoadList.Count > 0 && downLoadABFileNums < reDownLoadABFileMaxNums){//下载次数增加downLoadABFileNums++;//下载集合中开始下载for (int i = 0; i < downLoadList.Count; i++){//在本地创建目录供AB包保存string[] relativePaths = downLoadList[i].Split('/');string tmpDir = localPath;for(int j = 0; j < relativePaths.Length - 1; j++){tmpDir = tmpDir + relativePaths[j];if (!Directory.Exists(tmpDir)){Directory.CreateDirectory(tmpDir);}tmpDir = tmpDir + "/";}//开启下载文件协程  downLoadList[i] 文件名     localPath + downLoadList[i] 保存地址    下载结束回调yield return StartCoroutine(DownLoadFile(downLoadList[i], localPath + downLoadList[i], isDown =>{if(isDown){//下载成功 记录下载的文件tempList.Add(downLoadList[i]);downLoadOverNums++;}}));//Debug.Log("下载AB包进度:" + downLoadOverNums + "/" + downLoadOverMaxNums);updateCallback(DownCallbackType.Progress, downLoadOverNums + "/" + downLoadOverMaxNums);}//将下载成功的文件名从待下载列表删除for (int i = 0; i < tempList.Count; i++){downLoadList.Remove(tempList[i]);}//清空记录的文件名List集合tempList.Clear();//如果待下载集合中还有文件没下载继续循环}if(downLoadList.Count == 0){//待下载集合没有内容,下载所有的AB包成功if (callback != null){//回调成功callback(true);}}else{//下载失败if (callback != null){//回调失败callback(false);}}}

10.在外部代码中调用的方法

CheckUpdate(isOver =>
{if (isOver){Debug.Log("下载结束, 跳转场景");}else{Debug.Log("失败,弹出重新游戏弹框");}
}, (callbackType, message) =>
{switch (callbackType){case DownCallbackType.Message:Debug.Log(message);break;case DownCallbackType.Progress:Debug.Log("AB包下载进度:" + message);break;}
});

因为作者精力有限,文章中难免出现一些错漏,敬请广大专家和网友批评、指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/639154.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《A++ 敏捷开发》- 5 量化管理从个人开始

我&#xff1a;你们管理层和客户都比较关心项目的进度&#xff0c;项目是否能按时完成&#xff1f;请问你们过去的项目如何&#xff1f; 开发&#xff1a;我们现在就是走敏捷开发&#xff0c;两周一个迭代。每次迭代前&#xff0c;我们聚一起开会&#xff0c;把所有用户故事按优…

Dubbo 3.2版本分析Provider启动时操作

Dubbo 3.2版本分析Provider启动时操作 前言例子分析onStarting 模块doStart 模块 小结 前言 上一篇文章&#xff0c;我们分析了 Dubbo 3.2 版本在 Provider 启动前的操作流程&#xff0c;这次我们具体分析具体它的启动过程&#xff0c;揭开它的神秘面纱。 例子 这里我们还是…

【ZYNQ入门】第八篇、基于Lwip构建TCP服务器

目录 第一部分、基础知识 1、小白入门必看文章 2、什么是Lwip&#xff1f; 3、什么是TCP/IP协议&#xff1f; 4、MAC地址、IP地址、子网掩码、网关 4.1、MAC地址 4.2、IP地址 4.3、子网掩码 4.4、网关 第二部分、硬件搭建 第三部分、软件代码 1、SDK工程的建立 2、…

数据结构与算法-二叉树-从中序与后序遍历序列构造二叉树

从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder …

云盘后端分析

1.验证码 用的是外面找的 2.发送邮箱验证码 配置邮箱的授权码 我们在发送邮箱的时候&#xff0c;需要把那个值传到数据库中&#xff0c;数据库中有它的状态&#xff0c;我们需要根据状态判断它是注册还是找回密码 我们在发送邮箱之前&#xff0c;先从session里面得到我们验证…

Rocky Linux 8.9 安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

某度网盘提取下载链接JS逆向分析(一)

本次目标网址如下&#xff0c;使用base64解码后获得 aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMUZsaDBPeGpZamZJTFVZWUQzTm9fVnc 链接提取码为&#xff1a;ly12 本次逆向分析分为上下两篇文章说明&#xff0c;一为讲解如何从原链接通过逆向拿到下载链接&#xff0c;二为逆向登录拿到co…

flink结合Yarn进行部署

1. 什么是Yarn模式部署Flink 独立&#xff08;Standalone&#xff09;模式由 Flink 自身提供资源&#xff0c;无需其他框架&#xff0c;这种方式降低了和其他第三方资源框架的耦合性&#xff0c;独立性非常强。但我们知道&#xff0c;Flink 是大数据计算框架&#xff0c;不是资…

C++学习笔记——指针

1&#xff0c;指针的基本概念 指针的作用&#xff1a;可以通过指针间接访问内存 内存的编号是从0开始记录的&#xff0c;一般用十六进制数字表示可以利用指针变量保存地址 上图中的p就是a变量的指针&#xff0c;也可以记作*a 2&#xff0c;指针变量的定义和使用 指针变量定…

Linux操作系统——理解文件系统

预备知识 到目前为止&#xff0c;我们所学习到的关于文件的操作&#xff0c;全部都是基于文件被打开&#xff0c;被访问&#xff0c;访问期间比较重要的有重定向&#xff0c;缓冲区&#xff0c;一切皆文件&#xff0c;当我们访问完毕的时候需要将文件关闭&#xff0c;关闭时那…

3.RHCSA脚本配置及通过node2改密码

运行脚本发现node2不成功 脚本破解 选第二个 Ctrl x 换行 破解成功后做node2的改密码题 回到redhat, 发现检测程序检测密码题成功,得了8分.

DBA技术栈MongoDB: 数据增改删除

该博文主要介绍mongoDB对文档数据的增加、更新、删除操作。 1.插入数据 以下案例演示了插入单个文档、多个文档、指定_id、指定多个索引以及插入大量文档的情况。在实际使用中&#xff0c;根据需求选择适合的插入方式。 案例1&#xff1a;插入单个文档 db.visitor.insert({…

【蓝桥杯冲冲冲】动态规划初步[USACO2006 OPEN] 县集市

蓝桥杯备赛 | 洛谷做题打卡day13 文章目录 蓝桥杯备赛 | 洛谷做题打卡day13题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示样例说明数据规模与约定 思路&#xff1a;方程&#xff1a; 题解代码我的一些话 [USACO2006 OPEN] 县集市 The County Fair 题目描述 每年…

Threejs实现立体3D园区解决方案及代码

一、实现方案 单独贴代码可能容易混乱&#xff0c;所以这里只讲实现思路&#xff0c;代码放在最后汇总了下。 想要实现一个简单的工业园区、主要包含的内容是一个大楼、左右两片停车位、四条道路以及多个可在道路上随机移动的车辆、遇到停车位时随机选择是否要停车&#xff0…

解决ssh登录Permission denied, please try again

现象截图如下&#xff1a; 确定root的密码是正确的&#xff0c;最后的原因找到了&#xff0c;是远程的服务器&#xff0c;禁用了root账户可以被远程访问的权限。开启操作如下&#xff1a; 1.编辑配置文件 vi /etc/ssh/sshd_config 2.文件中找到PermitRootLogin #PermitRoo…

seaborn可视化示例详解

目录 1、散点图 2、散点图回归线 3、折线图 4、频数柱状图 5、分组散点图 6、箱型图 7、数值分布柱状图 8、频数分布图 9、联合分布图 10、数值分布柱状图 11、相关系数热力图 划重点 少走10年弯路 Seaborn是一个基于Python的数据可视化库&#xff0c;Seaborn提供了许多用…

链表|数据结构|C语言深入学习

什么是链表 离散&#xff0c;就是“分离的、散开的” 链表是什么样子的&#xff1a; 有限个节点离散分配 彼此间通过指针相连 除了首尾节点&#xff0c;每个节点都只有一个前驱节点和一个后继节点 首节点没有前驱结点&#xff0c;尾节点没有后继节点 基本概念术语&#xf…

1.使用分布式文件系统Minio管理文件

分布式文件系统DFS分类 文件系统 文件系统是操作系统用于组织管理存储设备(磁盘)或分区上文件信息的方法和数据结构,负责对文件存储设备空间进行组织和分配,并对存入文件进行保护和检索 文件系统是负责管理和存储文件的系统软件&#xff0c;操作系统通过文件系统提供的接口去…

Docker五部曲之五:通过Docker和GitHub Action搭建个人CICD项目

文章目录 项目介绍Dockerfile解析compose.yml解析Nginx反向代理到容器以及SSL证书设置MySQL的准备工作Spring和环境变量的交互 GitHub Action解析项目测试结语 项目介绍 该项目是一个入门CICD-Demo&#xff0c;它由以下几部分组成&#xff1a; Dockerfile&#xff1a;用于构建…

「 典型安全漏洞系列 」05.XML外部实体注入XXE详解

1. XXE简介 XXE&#xff08;XML external entity injection&#xff0c;XML外部实体注入&#xff09;是一种web安全漏洞&#xff0c;允许攻击者干扰应用程序对XML数据的处理。它通常允许攻击者查看应用程序服务器文件系统上的文件&#xff0c;并与应用程序本身可以访问的任何后…