UH3L10——游戏功能—资源更新删除

关于资源对比

我们之前实现的资源对比文件下载并没有执行对比操作就直接覆盖了,因此我们必须要进一步实现本地资源对比消息与远端资源对比消息的比对
由于需要执行比对操作,因此我们不能在下载到远端资源对比消息后就直接覆盖本地的
最好的做法是先比对本地与远端的资源对比文件,然后根据不同去更新AB包,确认全部下载正确了,最后才覆盖

因此,比对资源数据时一定会在内存或者硬盘上存储两份资源对比数据,这里有两种做法:

  1. 保存到临时文件中,待AB包下载完成后,再用该临时文件覆盖本地对比文件
  2. 压根不保存文件,直接通过下载流读取出字节数组数据转存为字符串,待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
/// <summary>
/// 获取下载下来的AB包中的消息
/// </summary>
public void GetRemoteABCompareFileInfo()
{
string info = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_Temp.txt");
//通过分隔符把不同的AB包消息分割开来
string[] strs = info.Split('|');
string[] infos;
for (int i = 0; i < strs.Length; i++)
{
//通过空格将AB包不同的消息分割开来,实例化AB包消息类对象并加入到字典记录下来
infos = strs[i].Split(' ');
remoteInfo.Add(infos[0], new ABInfo(infos[0], infos[1], infos[2]));
}
Debug.Log("远端AB包对比文件,内容获取结束");
}

准备本地和远端数据进行对比

目标:下载远端的资源对比文件,存储为临时文件,解析并读取,读取本地的资源对比文件,解析并读取,准备好对比数据

  • 由于下载使用异步执行,因此检查更新方法CheckUpdate​是异步方法,需要在执行完毕后要执行的回调函数和执行期间输出状态的回调函数

  • Main​的start​方法内

    1. 程序启动后调用ABUpdateManager​的CheckUpdate​方法,传入执行完毕后要执行的回调函数和执行期间输出状态的回调函数
  • ABUpdateManager​的CheckUpdate​方法内

    1. 为了避免上一次报错残留消息,因此所以我们需要清空远端对比消息,本地对比消息,以及待下载列表
    2. 调用ABUpdateManager​的DownLoadABCompareFile​方法,下载远端资源对比文件,传入执行完毕后要执行的回调函数
  • ABUpdateManager​的DownLoadABCompareFile​方法内

    1. 先声明下载是否完毕,最多重试次数以及下载路径的变量,
      下载到本地保存的文件名是ABCompareInfo_Temp.txt​,与本地的ABCompareInfo.txt​区分开

    2. 进入循环下载流程,如果没有下载完毕且最多重试次数大于0时就下载资源对比文件

    3. 创建Task​任务调用下载方法DownLoadFile​,传入下载文件名ABCompareInfo_Temp.txt​(远端还是这个名字)和下载路径

      • ABUpdateManager​的DownLoadFile​内

        1. 尝试执行下载流程,如果下载报错输出下载失败并返回false
        2. 传入的文件名和服务器地址拼接为下载地址,创建FTP连接请求
        3. 设置FTP连接请求通信凭证,代理设置为null​,请求完毕后关闭控制连接,操作命令设置为下载,指定传输二进制类型数据
        4. 通过FTP连接请求获取连接响应,通过连接响应获取下载流,创建本地文件流,从下载流下载数据到本地流,
        5. 下载完毕,关闭流,返回ture
    4. 等待DownLoadFile​执行完成,根据其返回值是否为true​决定是否下载完毕,如果下载完毕就会跳出循环,下载失败就最多重试次数减一

    5. 下载完毕或者连续下载失败导致最多重试次数归0,就结束方法,执行回调函数,传入下载是否完毕

  • ABUpdateManager​的CheckUpdate​方法内下载回调函数

    1. 如果传入false​,则说明下载失败,执行Main​脚本的start​方法传入的执行完毕的回调函数,传入false

    2. 如果传入ture​,则说明远端资源对比文件下载成功,执行输出状态的回调函数,输出对比文件下载结束

    3. 读取远端资源对比文件,执行解析方法GetRemoteABCompareFileInfo​,传入读取出来的字符串和接收存储远端资源信息的字典remoteABInfo
      执行输出状态的回调函数,输出解析远端对比文件

      • ABUpdateManager​的GetRemoteABCompareFileInfo​内

        1. 通过'|'​来分割不同AB包消息,' '​分割同一AB包的不同消息,
        2. 每个AB包消息都有一个ABInfo​对象存储,ABInfo​对象内有文件名,大小,MD5码字段来存储一个AB包的数据
        3. 将所有的ABInfo​对象作为值存入到传入的字典内,以AB包文件名为键,为资源对比作准备
    4. 解析完毕,执行输出状态的回调函数,输出解析远端对比文件完成

    5. 调用ABUpdateManager​的GetLocalABCompareFileInfo​方法,传入执行完毕的回调函数

  • ABUpdateManager​的GetLocalABCompareFileInfo​方法内

    1. 检查可读可写路径是否存在本地资源对比文件,若存在并执行GetLocalABCompareFileInfo​协程方法内,传入路径和传入的回调函数
    2. 不存在则检查可读路径下是否存在本地资源对比文件,若存在并执行GetLocalABCompareFileInfo​协程方法内,传入路径和传入的回调函数
    3. 如果两个路径都不存在,执行回调函数并传入false
  • ABUpdateManager​的GetLocalABCompareFileInfo​协程方法内

    • 根据传入的路径,创建UnityWebRequest​请求,发送请求并挂起协程

    • 获取文件完毕后,检查是否成功,如果失败执行回调函数并传入false

    • 如果成功,执行解析方法GetRemoteABCompareFileInfo​,传入读取出来的字符串和接收存储本地资源信息的字典localABInfo

      • ABUpdateManager​的GetRemoteABCompareFileInfo​内

        1. 通过'|'​来分割不同AB包消息,' '​分割同一AB包的不同消息,
        2. 每个AB包消息都有一个ABInfo​对象存储,ABInfo​对象内有文件名,大小,MD5码字段来存储一个AB包的数据
        3. 将所有的ABInfo​对象作为值存入到传入的字典内,以AB包文件名为键,为资源对比作准备
    • 执行回调函数并传入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;
}
}

//用于存储远端AB包消息的字典,之后和本地进行对比即可完成更新下载相关逻辑
private Dictionary<string, ABInfo> remoteABInfo = new Dictionary<string, ABInfo>();
//用于存储本地AB包消息的字典,之后和远端消息进行对比
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("解析对比文件完成");
//TODO.. 对比它们,进行AB包下载
}
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);
}

// 获取下载下来的AB包中的消息
public void GetRemoteABCompareFileInfo(string info, Dictionary<string, ABInfo> abInfo)
{
//不再读取文件,直接让外部处理好了传进来
//string info = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_Temp.txt");
//通过分隔符把不同的AB包消息分割开来
string[] strs = info.Split('|');
string[] infos;
for (int i = 0; i < strs.Length; i++)
{
//通过空格将AB包不同的消息分割开来,实例化AB包消息类对象并加入到字典记录下来
infos = strs[i].Split(' ');
abInfo.Add(infos[0], new ABInfo(infos[0], infos[1], infos[2]));
}
}

// 本地AB包对比文件加载
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);
}

//本地AB包加载并解析
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)
{
//TODO.. 待修改
}

public bool DownLoadFile(string fileName, string localPath)
{
try
{
//创建一个FTP连接 用于下载
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;
//其他设置:代理设置为null,请求完毕后关闭控制连接,操作命令设置为下载,指定传输二进制类型数据
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;
}
}

//AB包消息类
public class ABInfo
{
public string name; //AB包名字
public long size; //AB包大小
public string md5; //AB包MD5码

public ABInfo(string name, string size, string md5)
{
this.name = name;
this.size = long.Parse(size);
this.md5 = md5;
}
}
}

资源更新删除

目标:在获取了远端和本地资源对比消息后,我们需要去对比,检查哪些AB包是需要下载与更新的,哪些AB包是需要移除的(不移除默认资源)

  • ABUpdateManager​的CheckUpdate​方法内获取本地资源对比文件回调函数(后半段)

    1. 在完成了远端和本地资源对比消息的获取后,调用执行输出状态的回调函数,输出开始对比
    2. 遍历远端资源对比消息字典的所有的键,也就是远端AB包的文件名
    3. 如果本地资源对比消息字典不存在远端某个AB包文件名,说明需要下载,将该AB包文件名添加到待下载列表内
    4. 如果本地资源对比消息字典存在远端某个AB包文件名,则将其从本地资源对比消息字典移除出去,并对比远端与本地的两个文件的MD5码
    5. 如果本地资源的MD5码与远端资源的MD5码对不上,说明需要更新,将该AB包文件名添加到待下载列表内
    6. 对比完成后,调用执行输出状态的回调函数,输出对比完成​,然后输出删除无用的AB包文件
    7. 在遍历完了远端资源对比消息字典后,在本地资源对比消息字典中未被移除的消息就是需要移除的AB包消息,将对应的AB包删除掉
    8. 执行输出状态的回调函数,输出下载与更新AB包文件​,执行DownloadABFile​方法,传入下载完毕后的回调函数,以及外部传入的输出状态的回调函数
  • ABUpdateManager​的DownLoadABFile​方法内

    1. 声明变量:下载路径,本轮下载是否成功,下载完毕的AB包文件名列表,最多重试下载次数,下载成功次数,本次下载一共要下载的AB包数量

    2. 进入循环下载流程,如果待下载列表内还有内容,且还有重试下载次数,就执行下载流程

    3. 遍历待下载的列表downLoadList​,根据AB包名字下载文件名

    4. 先将本轮下载是否成功赋值为false​,创建Task​任务调用下载方法DownLoadFile​,传入文件名和下载路径

      • ABUpdateManager​的DownLoadFile​内

        1. 尝试执行下载流程,如果下载报错输出下载失败并返回false
        2. 传入的文件名和服务器地址拼接为下载地址,创建FTP连接请求
        3. 设置FTP连接请求通信凭证,代理设置为null​,请求完毕后关闭控制连接,操作命令设置为下载,指定传输二进制类型数据
        4. 通过FTP连接请求获取连接响应,通过连接响应获取下载流,创建本地文件流,从下载流下载数据到本地流,
        5. 下载完毕,关闭流,返回ture​,根据其返回值是否为true​决定是否下载完毕
    5. 等待执行DownLoadFile​结束,检查下载是否成功,如果成功就执行外部传入的Main​脚本的start​方法传入的输出状态的回调函数

      • Main​脚本的start​方法内的输出状态的回调函数

        1. 输出传入的内容,在这里就是输出下载进度
    6. 将下载好的AB包文件名添加到下载完毕的AB包文件名列表,回到第5步,开始下一个文件的下载流程

    7. 等待所有AB包文件都执行过一遍下载流程了,遍历下载完毕的AB包文件名列表,将下载完毕的AB包从待下载列表downLoadList​移除出去

    8. 最多重试下载次数减一,如果待下载列表已被清空,或者重试下载次数归0,则跳出下载循环

    9. 执行传入的下载好所有AB包要执行的回调函数,将待下载列表是否已被清空传入进去

  • ABUpdateManager​的CheckUpdate​方法内下载AB包完毕的回调函数内

    1. 如果下载成功,执行输出状态的回调函数,输出更新本地AB包对比文件为最新
    2. 将之前下载的远端资源对比文件字符串写入到本地资源对比文件内,使其成为最新的本地资源对比文件
  • 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;
}
}

//用于存储远端AB包消息的字典,之后和本地进行对比即可完成更新下载相关逻辑
private Dictionary<string, ABInfo> remoteABInfo = new Dictionary<string, ABInfo>();
//用于存储本地AB包消息的字典,之后和远端消息进行对比
private Dictionary<string, ABInfo> localABInfo = new Dictionary<string, ABInfo>();
//这个是待下载的AB包的列表文件,存储AB包的名字
private List<string> downLoadList = new List<string>();

private void OnDestroy()
{
instance = null;
}

/// <summary>
/// 用于检查资源热更新的函数
/// </summary>
/// <param name="overCallBack"></param>
/// <param name="updateInfoCallBack"></param>
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("解析对比文件完成");
//对比它们,进行AB包下载
updateInfoCallBack("开始对比");
foreach (string abName in remoteABInfo.Keys)
{
//判断,哪些资源是新的,然后记录,之后用于下载
if (!localABInfo.ContainsKey(abName))
//由于本地对比消息中没有叫这个名字的AB包,所以我们下载记录它
downLoadList.Add(abName);
else
{
//判断,哪些资源需要更新的,然后记录,之后用于下载
//如果本地MD5与远端MD5对不上,说明需要更新
if (localABInfo[abName].md5 != remoteABInfo[abName].md5)
downLoadList.Add(abName);
//判断,哪些资源需要删除, 每次检测完一个名字的AB包,就移除本地的信息,
//最后循环完毕,本地剩下的消息就是远端不再存在的内容,我们就可以剩余消息删除对应的AB包
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包文件");
DownLoadABFile((isOver) =>
{
if (isOver)
{
//在下载完所有的AB包文件后,把本地的AB包对比文件,更新为最新
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);
}

/// <summary>
/// 获取下载下来的AB包中的消息
/// </summary>
public void GetRemoteABCompareFileInfo(string info, Dictionary<string, ABInfo> abInfo)
{
//不再读取文件,直接让外部处理好了传进来
//string info = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo_Temp.txt");
//通过分隔符把不同的AB包消息分割开来
string[] strs = info.Split('|');
string[] infos;
for (int i = 0; i < strs.Length; i++)
{
//通过空格将AB包不同的消息分割开来,实例化AB包消息类对象并加入到字典记录下来
infos = strs[i].Split(' ');
abInfo.Add(infos[0], new ABInfo(infos[0], infos[1], infos[2]));
}
}

/// <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(false);
}

//本地AB包加载并解析
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)
{
//我们不再由DownLoadABFile来执行添加待下载列表操作,而是转为外部执行,这里只根据待下载列表下载内容
////遍历字典的键,根据AB名,下载AB包到本地
//foreach (string str in remoteABInfo.Keys)
//{
// //直接放入,待下载列表内
// downLoadList.Add(str);
//}
//本地存储的路径,供下载线程读取
string localPath = Application.persistentDataPath + "/";
//本轮下载是否成功
bool isOver = false;
//临时列表,用来记录哪些内容下载完了,在遍历完待下载列表后根据该列表移除已下载文件名
List<string> tempList = new List<string>();
int redownLoadMaxNum = 5; //最多重试下载次数
int downLoadOverNum = 0; //下载成功次数
int downLoadMaxNum = downLoadList.Count; //本次下载一共要下载AB包的数量
//如果待下载列表内还有内容,且还有重试下载次数
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
{
//创建一个FTP连接 用于下载
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;
//其他设置:代理设置为null,请求完毕后关闭控制连接,操作命令设置为下载,指定传输二进制类型数据
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;
}
}

//AB包消息类
public class ABInfo
{
public string name; //AB包名字
public long size; //AB包大小
public string md5; //AB包MD5码

public ABInfo(string name, string size, string md5)
{
this.name = name;
this.size = long.Parse(size);
this.md5 = md5;
}
}
}