UH3L10——游戏功能—资源更新删除
关于资源对比
我们之前实现的资源对比文件下载并没有执行对比操作就直接覆盖了,因此我们必须要进一步实现本地资源对比消息与远端资源对比消息的比对
由于需要执行比对操作,因此我们不能在下载到远端资源对比消息后就直接覆盖本地的
最好的做法是先比对本地与远端的资源对比文件,然后根据不同去更新AB包,确认全部下载正确了,最后才覆盖
因此,比对资源数据时一定会在内存或者硬盘上存储两份资源对比数据,这里有两种做法:
保存到临时文件中,待AB包下载完成后,再用该临时文件覆盖本地对比文件
压根不保存文件,直接通过下载流读取出字节数组数据转存为字符串,待AB包下载完毕后再保存为本地资源对比文件
根据我们之前实现的逻辑来看,第一种做法明显对逻辑要求的改动更少,因此采用第一种方法
改动下载资源对比文件的逻辑
首先,我们不能下载后直接覆盖本地原有的资源对比文件,因此额外创建一个新的临时文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public async void DownLoadABCompareFile (UnityAction<bool > overCallBack ){ bool isOver = false ; int reDownloadMaxNum = 5 ; string path = Application.persistentDataPath + "/ABCompareInfo_Temp.txt" ; while (!isOver && reDownloadMaxNum > 0 ) { await Task.Run(() => { isOver = DownLoadFile("ABCompareInfo.txt" , path); }); --reDownloadMaxNum; } overCallBack?.Invoke(isOver); }
同时,下载完毕后解析资源对比文件也需要转为先去解析临时文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void GetRemoteABCompareFileInfo (){ string info = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_Temp.txt" ); string [] strs = info.Split('|' ); string [] infos; for (int i = 0 ; i < strs.Length; i++) { infos = strs[i].Split(' ' ); remoteInfo.Add(infos[0 ], new ABInfo(infos[0 ], infos[1 ], infos[2 ])); } Debug.Log("远端AB包对比文件,内容获取结束" ); }
准备本地和远端数据进行对比
目标:下载远端的资源对比文件,存储为临时文件,解析并读取,读取本地的资源对比文件,解析并读取,准备好对比数据
由于下载使用异步执行,因此检查更新方法CheckUpdate
是异步方法,需要在执行完毕后要执行的回调函数和执行期间输出状态的回调函数
Main
的start
方法内
程序启动后调用ABUpdateManager
的CheckUpdate
方法,传入执行完毕后要执行的回调函数和执行期间输出状态的回调函数
ABUpdateManager
的CheckUpdate
方法内
为了避免上一次报错残留消息,因此所以我们需要清空远端对比消息,本地对比消息,以及待下载列表
调用ABUpdateManager
的DownLoadABCompareFile
方法,下载远端资源对比文件,传入执行完毕后要执行的回调函数
ABUpdateManager
的DownLoadABCompareFile
方法内
先声明下载是否完毕,最多重试次数以及下载路径的变量,
下载到本地保存的文件名是ABCompareInfo_Temp.txt
,与本地的ABCompareInfo.txt
区分开
进入循环下载流程,如果没有下载完毕且最多重试次数大于0时就下载资源对比文件
创建Task
任务调用下载方法DownLoadFile
,传入下载文件名ABCompareInfo_Temp.txt
(远端还是这个名字)和下载路径
等待DownLoadFile
执行完成,根据其返回值是否为true
决定是否下载完毕,如果下载完毕就会跳出循环,下载失败就最多重试次数减一
下载完毕或者连续下载失败导致最多重试次数归0,就结束方法,执行回调函数,传入下载是否完毕
ABUpdateManager
的CheckUpdate
方法内下载回调函数
如果传入false
,则说明下载失败,执行Main
脚本的start
方法传入的执行完毕的回调函数,传入false
如果传入ture
,则说明远端资源对比文件下载成功,执行输出状态的回调函数,输出对比文件下载结束
读取远端资源对比文件,执行解析方法GetRemoteABCompareFileInfo
,传入读取出来的字符串和接收存储远端资源信息的字典remoteABInfo
执行输出状态的回调函数,输出解析远端对比文件
解析完毕,执行输出状态的回调函数,输出解析远端对比文件完成
调用ABUpdateManager
的GetLocalABCompareFileInfo
方法,传入执行完毕的回调函数
ABUpdateManager
的GetLocalABCompareFileInfo
方法内
检查可读可写路径是否存在本地资源对比文件,若存在并执行GetLocalABCompareFileInfo
协程方法内,传入路径和传入的回调函数
不存在则检查可读路径下是否存在本地资源对比文件,若存在并执行GetLocalABCompareFileInfo
协程方法内,传入路径和传入的回调函数
如果两个路径都不存在,执行回调函数并传入false
ABUpdateManager
的GetLocalABCompareFileInfo
协程方法内
根据传入的路径,创建UnityWebRequest
请求,发送请求并挂起协程
获取文件完毕后,检查是否成功,如果失败执行回调函数并传入false
如果成功,执行解析方法GetRemoteABCompareFileInfo
,传入读取出来的字符串和接收存储本地资源信息的字典localABInfo
执行回调函数并传入ture
ABUpdateManager
的CheckUpdate
方法内获取本地资源对比文件回调函数
如果失败,执行Main
脚本的start
方法传入的执行完毕的回调函数,传入false
如果成功,执行输出状态的回调函数,输出解析对比文件完成
,至此本地和远端数据准备完毕,准备进行对比(下一节)
Main
的start
方法内CheckUpdate
执行完毕回调函数
如果下载成功,输出检测更新结束
如果下载失败,输出网络出错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using UnityEngine;public class Main : MonoBehaviour { void Start () { ABUpdateManager.Instance.CheckUpdate((isOver) => { if (isOver) { print("检测更新结束" ); } else { print("网络出错" ); } }, (str) => { print(str); }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 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 ABUpdateManager : MonoBehaviour { private static ABUpdateManager instance; public static ABUpdateManager Instance { get { if (instance == null ) { GameObject obj = new GameObject("ABUpdateManager" ); instance = obj.AddComponent<ABUpdateManager>(); } return instance; } } private Dictionary<string , ABInfo> remoteABInfo = new Dictionary<string , ABInfo>(); private Dictionary<string , ABInfo> localABInfo = new Dictionary<string , ABInfo>(); private void OnDestroy () { instance = null ; } public void CheckUpdate (UnityAction<bool > overCallBack, UnityAction<string > updateInfoCallBack ) { DownLoadABCompareFile((isOver) => { updateInfoCallBack("开始更新资源" ); if (isOver) { updateInfoCallBack("对比文件下载结束" ); string remoteInfo = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_Temp.txt" ); updateInfoCallBack("解析远端对比文件" ); GetRemoteABCompareFileInfo(remoteInfo, remoteABInfo); updateInfoCallBack("解析远端对比文件完成" ); GetLocalABCompareFileInfo((isOver) => { if (isOver) { updateInfoCallBack("解析对比文件完成" ); } else overCallBack(false ); }); } else { overCallBack(false ); } }); } public async void DownLoadABCompareFile (UnityAction<bool > overCallBack ) { bool isOver = false ; int reDownloadMaxNum = 5 ; string path = Application.persistentDataPath + "/ABCompareInfo_Temp.txt" ; while (!isOver && reDownloadMaxNum > 0 ) { await Task.Run(() => { isOver = DownLoadFile("ABCompareInfo.txt" , path); }); --reDownloadMaxNum; } overCallBack?.Invoke(isOver); } public void GetRemoteABCompareFileInfo (string info, Dictionary<string , ABInfo> abInfo ) { string [] strs = info.Split('|' ); string [] infos; for (int i = 0 ; i < strs.Length; i++) { infos = strs[i].Split(' ' ); abInfo.Add(infos[0 ], new ABInfo(infos[0 ], infos[1 ], infos[2 ])); } } 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(false ); } private IEnumerator GetLocalABCompareFileInfo (string filePath, UnityAction<bool > overCallBack ) { UnityWebRequest request = UnityWebRequest.Get(filePath); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { GetRemoteABCompareFileInfo(request.downloadHandler.text, localABInfo); overCallBack(true ); } else overCallBack(false ); } public async void DownLoadABFile (UnityAction<bool > overCallBack, UnityAction<int , int > updatePro ) { } public bool DownLoadFile (string fileName, string localPath ) { try { FtpWebRequest request = FtpWebRequest.Create(new Uri("ftp://192.168.1.105/AB/PC/" + fileName)) as FtpWebRequest; NetworkCredential credential = new NetworkCredential("MrTang" , "MrTang123" ); request.Credentials = credential; request.Proxy = null ; request.KeepAlive = false ; request.Method = WebRequestMethods.Ftp.DownloadFile; request.UseBinary = true ; FtpWebResponse response = request.GetResponse() as FtpWebResponse; Stream downLoadStream = response.GetResponseStream(); using (FileStream file = File.Create(localPath)) { byte [] bytes = new byte [2048 ]; int contentLength = downLoadStream.Read(bytes, 0 , bytes.Length); while (contentLength > 0 ) { file.Write(bytes, 0 , contentLength); contentLength = downLoadStream.Read(bytes, 0 , bytes.Length); } file.Close(); downLoadStream.Close(); } return true ; } catch (Exception e) { Debug.Log($"{fileName} 下载失败:{e.Message} " ); return false ; } } public 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; } } }
资源更新删除
目标:在获取了远端和本地资源对比消息后,我们需要去对比,检查哪些AB包是需要下载与更新的,哪些AB包是需要移除的(不移除默认资源)
ABUpdateManager
的CheckUpdate
方法内获取本地资源对比文件回调函数(后半段)
在完成了远端和本地资源对比消息的获取后,调用执行输出状态的回调函数,输出开始对比
遍历远端资源对比消息字典的所有的键,也就是远端AB包的文件名
如果本地资源对比消息字典不存在远端某个AB包文件名,说明需要下载,将该AB包文件名添加到待下载列表内
如果本地资源对比消息字典存在远端某个AB包文件名,则将其从本地资源对比消息字典移除出去,并对比远端与本地的两个文件的MD5码
如果本地资源的MD5码与远端资源的MD5码对不上,说明需要更新,将该AB包文件名添加到待下载列表内
对比完成后,调用执行输出状态的回调函数,输出对比完成
,然后输出删除无用的AB包文件
在遍历完了远端资源对比消息字典后,在本地资源对比消息字典中未被移除的消息就是需要移除的AB包消息,将对应的AB包删除掉
执行输出状态的回调函数,输出下载与更新AB包文件
,执行DownloadABFile
方法,传入下载完毕后的回调函数,以及外部传入的输出状态的回调函数
ABUpdateManager
的DownLoadABFile
方法内
声明变量:下载路径,本轮下载是否成功,下载完毕的AB包文件名列表,最多重试下载次数,下载成功次数,本次下载一共要下载的AB包数量
进入循环下载流程,如果待下载列表内还有内容,且还有重试下载次数,就执行下载流程
遍历待下载的列表downLoadList
,根据AB包名字下载文件名
先将本轮下载是否成功赋值为false
,创建Task
任务调用下载方法DownLoadFile
,传入文件名和下载路径
等待执行DownLoadFile
结束,检查下载是否成功,如果成功就执行外部传入的Main
脚本的start
方法传入的输出状态的回调函数
将下载好的AB包文件名添加到下载完毕的AB包文件名列表,回到第5步,开始下一个文件的下载流程
等待所有AB包文件都执行过一遍下载流程了,遍历下载完毕的AB包文件名列表,将下载完毕的AB包从待下载列表downLoadList
移除出去
最多重试下载次数减一,如果待下载列表已被清空,或者重试下载次数归0,则跳出下载循环
执行传入的下载好所有AB包要执行的回调函数,将待下载列表是否已被清空传入进去
ABUpdateManager
的CheckUpdate
方法内下载AB包完毕的回调函数内
如果下载成功,执行输出状态的回调函数,输出更新本地AB包对比文件为最新
将之前下载的远端资源对比文件字符串写入到本地资源对比文件内,使其成为最新的本地资源对比文件
Main
的start
方法内CheckUpdate
执行完毕回调函数
如果下载成功,输出检测更新结束
如果下载失败,输出网络出错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using UnityEngine;public class Main : MonoBehaviour { void Start () { ABUpdateManager.Instance.CheckUpdate((isOver) => { if (isOver) { print("检测更新结束" ); } else { print("网络出错" ); } }, (str) => { print(str); }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 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 ABUpdateManager : MonoBehaviour { private static ABUpdateManager instance; public static ABUpdateManager Instance { get { if (instance == null ) { GameObject obj = new GameObject("ABUpdateManager" ); instance = obj.AddComponent<ABUpdateManager>(); } return instance; } } private Dictionary<string , ABInfo> remoteABInfo = new Dictionary<string , ABInfo>(); private Dictionary<string , ABInfo> localABInfo = new Dictionary<string , ABInfo>(); private List<string > downLoadList = new List<string >(); private void OnDestroy () { instance = null ; } public void CheckUpdate (UnityAction<bool > overCallBack, UnityAction<string > updateInfoCallBack ) { remoteABInfo.Clear(); localABInfo.Clear(); downLoadList.Clear(); DownLoadABCompareFile((isOver) => { updateInfoCallBack("开始更新资源" ); if (isOver) { updateInfoCallBack("对比文件下载结束" ); string remoteInfo = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_Temp.txt" ); updateInfoCallBack("解析远端对比文件" ); GetRemoteABCompareFileInfo(remoteInfo, remoteABInfo); updateInfoCallBack("解析远端对比文件完成" ); GetLocalABCompareFileInfo((isOver) => { if (isOver) { updateInfoCallBack("解析对比文件完成" ); updateInfoCallBack("开始对比" ); 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包文件" ); foreach (string abName in localABInfo.Keys) { if (File.Exists(Application.persistentDataPath + "/" + abName)) File.Delete(Application.persistentDataPath + "/" + abName); } updateInfoCallBack("下载和更新AB包文件" ); DownLoadABFile((isOver) => { if (isOver) { updateInfoCallBack("更新本地AB包对比文件为最新" ); File.WriteAllText(Application.persistentDataPath + "/ABCompareInfo.txt" , remoteInfo); } overCallBack(isOver); }, updateInfoCallBack); } else overCallBack(false ); }); } else { overCallBack(false ); } }); } public async void DownLoadABCompareFile (UnityAction<bool > overCallBack ) { bool isOver = false ; int reDownloadMaxNum = 5 ; string path = Application.persistentDataPath + "/ABCompareInfo_Temp.txt" ; while (!isOver && reDownloadMaxNum > 0 ) { await Task.Run(() => { isOver = DownLoadFile("ABCompareInfo.txt" , path); }); --reDownloadMaxNum; } overCallBack?.Invoke(isOver); } public void GetRemoteABCompareFileInfo (string info, Dictionary<string , ABInfo> abInfo ) { string [] strs = info.Split('|' ); string [] infos; for (int i = 0 ; i < strs.Length; i++) { infos = strs[i].Split(' ' ); abInfo.Add(infos[0 ], new ABInfo(infos[0 ], infos[1 ], infos[2 ])); } } 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(false ); } private IEnumerator GetLocalABCompareFileInfo (string filePath, UnityAction<bool > overCallBack ) { UnityWebRequest request = UnityWebRequest.Get(filePath); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { GetRemoteABCompareFileInfo(request.downloadHandler.text, localABInfo); overCallBack(true ); } else overCallBack(false ); } public async void DownLoadABFile (UnityAction<bool > overCallBack, UnityAction<string > updatePro ) { string localPath = Application.persistentDataPath + "/" ; bool isOver = false ; List<string > tempList = new List<string >(); int redownLoadMaxNum = 5 ; int downLoadOverNum = 0 ; int downLoadMaxNum = 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 + "/" + downLoadMaxNum); tempList.Add(downLoadList[i]); } } for (int i = 0 ; i < tempList.Count; i++) downLoadList.Remove(tempList[i]); --redownLoadMaxNum; } overCallBack?.Invoke(downLoadList.Count == 0 ); } public bool DownLoadFile (string fileName, string localPath ) { try { FtpWebRequest request = FtpWebRequest.Create(new Uri("ftp://192.168.1.101/AB/PC/" + fileName)) as FtpWebRequest; NetworkCredential credential = new NetworkCredential("MrTang" , "MrTang123" ); request.Credentials = credential; request.Proxy = null ; request.KeepAlive = false ; request.Method = WebRequestMethods.Ftp.DownloadFile; request.UseBinary = true ; FtpWebResponse response = request.GetResponse() as FtpWebResponse; Stream downLoadStream = response.GetResponseStream(); using (FileStream file = File.Create(localPath)) { byte [] bytes = new byte [2048 ]; int contentLength = downLoadStream.Read(bytes, 0 , bytes.Length); while (contentLength > 0 ) { file.Write(bytes, 0 , contentLength); contentLength = downLoadStream.Read(bytes, 0 , bytes.Length); } file.Close(); downLoadStream.Close(); } return true ; } catch (Exception e) { Debug.Log($"{fileName} 下载失败:{e.Message} " ); return false ; } } public 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; } } }