UH3L7——下载AB包

前置知识点

  • FTP下载相关(Unity网络开发基础中)
  • C#异步函数
  • C#Task任务类

目标

在下载完资源对比文件并解析了其中的消息后,根据资源对比文件下载AB包(全部下载,目前不需要对比本地资源来确认哪些AB包文件需要下载)

实现流程

  • 由于下载使用异步执行,因此下载AB包方法DownLoadABFile​是异步方法,需要传入所有AB包下载完毕后要执行的回调函数

  • Main​脚本的start​方法内传入的下载回调函数内,下载完资源对比文件并解析后

    1. 解析完下载好的资源对比文件后,执行ABUpdateManager​的DownLoadABFile​方法,
      传入下载好所有AB包要执行的回调函数,和下载好一个AB包后要执行的回调函数
    2. 输出AB相关函数执行完毕
  • ABUpdateManager​的DownLoadABFile​方法内

    1. 遍历解析资源对比文件得到的ABInfo​字典remoteInfo​,将所有的AB包文件名字放入到待下载的列表downLoadList​内

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

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

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

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

      • ​​ABUpdateManager​的DownLoadFile​内

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

      • Main​脚本的start​方法内传入的下载好一个AB包后的回调函数

        1. 根据传入的下载成功次数,本次下载一共要下载的AB包数量,输出下载进度
    7. 将下载好的AB包文件名添加到下载完毕的AB包文件名列表,回到第5步,开始下一个文件的下载流程

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

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

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

  • Main​脚本的start​方法内传入的下载好所有AB包的回调函数

    1. 如果传入false​,则说明下载失败,输出失败信息,如果传入ture​,则说明下载成功,输出成功消息
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Main : MonoBehaviour
{
void Start()
{
print(Application.persistentDataPath);
ABUpdateManager.Instance.DownLoadABCompareFile((isOver) =>
//下载资源对比文件完毕后要执行的函数
{
if (isOver)
{
ABUpdateManager.Instance.GetRemoteABCompareFileInfo();
ABUpdateManager.Instance.DownLoadABFile((isOver) =>
//下载所有AB包文件完毕后要执行的函数
{
if (isOver)
{
print("所有AB包下载结束,继续处理逻辑");
}
else
{
print("下载失败,网络出现问题了");
}
}, (nowNum, maxNum) =>
//下载好一个AB包后要执行的函数,传入当前下载完毕数和
{
Debug.Log($"下载进度:{nowNum}/{maxNum}");
});
print("AB相关函数执行完毕");
}
else
{
print("下载失败,网络出现问题了");
}
});
}
}
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
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;

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> remoteInfo = new Dictionary<string, ABInfo>();
//这个是待下载的AB包的列表文件,存储AB包的名字
private List<string> downLoadList = new List<string>();

private void OnDestroy()
{
instance = null;
}

public async void DownLoadABCompareFile(UnityAction<bool> overCallBack)
{
//从资源服务器下载资源对比文件
bool isOver = false;
int reDownloadMaxNum = 5;
string path = Application.persistentDataPath + "/ABCompareInfo.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 = File.ReadAllText(Application.persistentDataPath + "/ABCompareInfo.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包对比文件,内容获取结束");
}

public async void DownLoadABFile(UnityAction<bool> overCallBack, UnityAction<int, int> updatePro)
{
//遍历字典的键,根据AB名,下载AB包到本地
foreach (string str in remoteInfo.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.102/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包消息类
private 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;
}
}
}