当我们需要下载热更包时,同样需要中间类来维护。

热更下载的总流程在下面的StartDownloadAB方法内:

  1. 先获取一个下载列表List<Patch>,默认情况下,这个List<Patch>就是m_DownloadList,如果是MD5校验出错的情况下,这个List<Patch>就是需要重新下载资源的列表
  2. 根据下载列表List<Patch>,创建List<DownloadAssetBundle>,因为具体的下载方法我们封装在了DownloadAssetBundle类里

DownloadItem基类

创建DownloadItem基类,因为在游戏中需要下载的东西有很多,不一定是AssetBundle

我们在ResFrame——Frames——ResourceFrm——Download文件夹内新建DownloadItem文件

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

using System;
using System.Collections;
using System.IO;

public abstract class DownloadItem
{
/// <summary>
/// 网络资源URL路径
/// </summary>
protected string m_Url;
/// <summary>
/// 资源下载存放路径,不包含文件名
/// </summary>
protected string m_SavePath;
/// <summary>
/// 文件名,不包含后缀
/// </summary>
protected string m_FileNameWithoutExt;
/// <summary>
/// 文件后缀
/// </summary>
protected string m_FileExt;
/// <summary>
/// 文件名,包含后缀
/// </summary>
protected string m_FileName;
/// <summary>
/// 下载文件全路径,路径+文件名+后缀
/// </summary>
protected string m_SaveFilePath;
/// <summary>
/// 源文件的大小
/// </summary>
protected long m_FileLength;
/// <summary>
/// 当前下载的大小
/// </summary>
protected long m_CurFileLength;
/// <summary>
/// 是否开始下载
/// </summary>
protected bool m_IsStartDownload;

/// <summary>
/// 资源URL
/// </summary>
public string Url => m_Url;
/// <summary>
/// 资源下载存放路径,不包含文件名
/// </summary>
public string SavePath => m_SavePath;
/// <summary>
/// 文件名,不包含后缀
/// </summary>
public string FileNameWithoutExit => m_FileNameWithoutExt;
/// <summary>
/// 文件后缀
/// </summary>
public string FileExt => m_FileExt;
/// <summary>
/// 文件名,包含后缀
/// </summary>
public string FileName => m_FileName;
/// <summary>
/// 下载文件全路径,路径+文件名+后缀
/// </summary>
public string SaveFilePath => m_SaveFilePath;
/// <summary>
/// 源文件的大小
/// </summary>
public long FileLength => m_FileLength;
/// <summary>
/// 当前下载的大小
/// </summary>
public long CurFileLength => m_CurFileLength;
/// <summary>
/// 是否开始下载
/// </summary>
public bool StartDownload => m_IsStartDownload;

public DownloadItem(string url, string savePath)
{
m_Url = url;
m_SavePath = savePath;
m_IsStartDownload = false;
m_FileNameWithoutExt = Path.GetFileNameWithoutExtension(m_Url);
m_FileExt = Path.GetExtension(m_Url);
m_FileName = Path.GetFileName(m_Url);
m_SaveFilePath = Path.Combine(m_SavePath, m_FileName);
}

public virtual IEnumerator Download(Action callback = null)
{
yield return null;
}
/// <summary>
/// 获取下载进度
/// </summary>
/// <returns>进度</returns>
public abstract float GetProcess();
/// <summary>
/// 获取当前已下载的文件大小
/// </summary>
/// <returns>文件大小</returns>
public abstract long GetCurLength();
/// <summary>
/// 获取下载的文件大小
/// </summary>
/// <returns>文件大小</returns>
public abstract long GetFileLength();
/// <summary>
/// 销毁无用的下载中间类
/// </summary>
public abstract void Destroy();
}

DownloadAssetBundle类

我们在ResFrame——Frames——ResourceFrm——Download文件夹内新建DownloadAssetBundle文件

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
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class DownloadAssetBundle : DownloadItem
{
UnityWebRequest m_WebRequest;

public DownloadAssetBundle(string url, string savePath) : base(url, savePath)
{

}

public override IEnumerator Download(Action callback = null)
{
m_WebRequest = UnityWebRequest.Get(m_Url);
m_IsStartDownload = true;
m_WebRequest.timeout = 30;
yield return m_WebRequest.SendWebRequest();
m_IsStartDownload = false;

if(m_WebRequest.result == UnityWebRequest.Result.ConnectionError ||
m_WebRequest.result == UnityWebRequest.Result.ProtocolError ||
m_WebRequest.result == UnityWebRequest.Result.DataProcessingError)
{
Debug.LogError("Download Error" + m_WebRequest.error);
}
else
{
byte[] bytes = m_WebRequest.downloadHandler.data;
FileTool.CreateFile(m_SaveFilePath, bytes);
callback?.Invoke();
}
}

public override void Destroy()
{
if(m_WebRequest != null)
{
m_WebRequest.Dispose();
m_WebRequest = null;
}
}

public override long GetCurLength()
{
if(m_WebRequest != null)
{
return (long)m_WebRequest.downloadedBytes;
}
return 0;
}

public override long GetFileLength()
{
return 0;//下载AssetBundle暂时用不到文件大小的计算,这里设为0
}

public override float GetProcess()
{
if (m_WebRequest != null)
{
return (long)m_WebRequest.downloadProgress;
}
return 0;
}

}

修改HotPatchManager

首先给HotPatchManager添加几个全局变量

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
/// <summary>
/// 服务器上资源名对应的Dic,用于下载后MD5校验
/// </summary>
private Dictionary<string,string> m_DownloadMD5Dic = new Dictionary<string,string>();
/// <summary>
/// 服务器列表获取错误回调
/// </summary>
public event Action ServerInfoError;

/// <summary>
/// 文件下载出错回调
/// </summary>
public event Action<string> ItemDownloadError;
/// <summary>
/// 下载完成回调
/// </summary>
public event Action LoadOver;
/// <summary>
/// 已经下载的Patch列表
/// </summary>
private List<Patch> m_AlreadyDownList = new List<Patch>();
/// <summary>
/// 是否已经开始下载
/// </summary>
private bool m_StartDownload = false;
/// <summary>
/// 是否已经开始下载
/// </summary>
public bool IsStartDownload => m_StartDownload;
/// <summary>
/// 尝试重新下载次数
/// </summary>
private int m_TryDownCount = 0;
/// <summary>
/// 最多尝试下载次数
/// </summary>
private const int MAXDOWNLOADCOUNT = 4;

StartDownloadAB

添加StartDownloadAB方法,注意这是一个公共方法,需要在外部调用。

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
  /// <summary>
/// 开始下载AB包
/// </summary>
/// <param name="callBack">回调</param>
public IEnumerator StartDownloadAB(Action callBack,List<Patch> allPatch = null)
{
m_AlreadyDownList.Clear();
m_StartDownload = true;
if(allPatch == null)
{
allPatch = m_DownloadList;
}

if(!Directory.Exists(m_DownLoadPath))
{
Directory.CreateDirectory(m_DownLoadPath);
}

List<DownloadAssetBundle> downloadAssetBundles = new List<DownloadAssetBundle>();

foreach (Patch patch in allPatch)
{
downloadAssetBundles.Add(new DownloadAssetBundle(patch.Url, m_DownLoadPath));
}

foreach (DownloadAssetBundle download in downloadAssetBundles)
{
yield return m_Mono.StartCoroutine(download.Download());
Patch patch = FindPatchByName(download.FileName);
if (patch != null)
{
m_AlreadyDownList.Add(patch);
}
download.Destroy();
}

//MD5码校验
//如果检验没通过,自动重新下载没通过的文件
//记录下载计数,达到一定次数后,进行反馈
VerifyMD5(downloadAssetBundles, callBack);
}
/// <summary>
/// 根据名字查找对象的热更Patch
/// </summary>
/// <param name="name">Patch名</param>
Patch FindPatchByName(string name)
{
Patch patch = null;
m_DownloadDic.TryGetValue(name, out patch);
return patch;
}

修改ComputeDownloadAddDownloadList方法,主要用来添加m_DownloadMD5Dic

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
void ComputeDownload()
{
m_DownloadList.Clear();
m_DownloadDic.Clear();
m_DownloadMD5Dic.Clear();//+++
//...
}
void AddDownloadList(Patch patch)
{
//...
if (File.Exists(filePath))
{
string md5 = MD5Manager.Instance.BuildFileMD5(filePath);
if (patch.MD5 != md5)
{
m_DownloadList.Add(patch);
m_DownloadDic.Add(patch.Name, patch);
m_DownloadMD5Dic.Add(patch.Name,patch.MD5);//+++
}
}
else
{
m_DownloadList.Add(patch);
m_DownloadDic.Add(patch.Name, patch);
m_DownloadMD5Dic.Add(patch.Name, patch.MD5);//+++
}
}

修改CheckVersion方法,主要用来将m_TryDownCount归零

1
2
3
4
5
public void CheckVersion(Action<bool> hotCallback = null)
{
m_TryDownCount = 0;
//...
}

VerifyMD5

当所有的热更包下载完成之后,执行VerifyMD5,来判断下载的文件是否正确。

它算是一种递归方法,因为VerifyMD5StartDownloadAB内部被调用,又反过来调用了StartDownloadAB

所以我们声明了m_TryDownCountMAXDOWNLOADCOUNT来限制递归次数。

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
void VerifyMD5(List<DownloadAssetBundle> downLoadAssets, Action callBack)
{
List<Patch> downloadList = new List<Patch>();
foreach (DownloadAssetBundle download in downLoadAssets)
{
string md5 = "";
if (m_DownloadMD5Dic.TryGetValue(download.FileName, out md5))
{
if(MD5Manager.Instance.BuildFileMD5(download.SaveFilePath) != md5)
{
Debug.Log(string.Format("此文件{0}MD5校验失败,即将重新下载",download.FileName));
Patch patch = FindPatchByName(download.FileName);
if (patch != null)
{
downloadList.Add(patch);
}
}
}
}

if (downloadList.Count > 0)
{
if(m_TryDownCount > MAXDOWNLOADCOUNT)
{
string allName = "";
m_StartDownload = false;
foreach (Patch patch in downloadList)
{
allName += patch.Name + ";";
}
Debug.LogError("资源下载后MD5校验失败,请检查资源:" + allName);
ItemDownloadError?.Invoke(allName);
}
else
{
m_TryDownCount++;
m_DownloadMD5Dic.Clear();
foreach (Patch patch in downloadList)
{
m_DownloadMD5Dic.Add(patch.Name, patch.MD5);
}
//自动重新下载校验失败的文件
m_Mono.StartCoroutine(StartDownloadAB(callBack,downloadList));
}

}
else
{
m_DownloadMD5Dic.Clear();
callBack?.Invoke();
m_StartDownload = false;
LoadOver?.Invoke();
}
}

获取下载总进度

我们可以通过m_AlreadyDownList中获取到刚下载完的资源包的大小。

通过在HotPatchManager里面添加m_CurDownload变量,可以获取到当前正在下载的资源包的下载信息。

我们之前在HotPatchManager里面声明了一个

1
2
3
4
/// <summary>
/// 当前正在下载的资源
/// </summary>
private DownloadAssetBundle m_CurDownload = null;

StartDownloadAB方法里指定m_CurDownload

1
2
3
4
5
6
7
8
9
10
11
12
13
    public IEnumerator StartDownloadAB(Action callBack,List<Patch> allPatch = null)
{
//...

foreach (DownloadAssetBundle download in downloadAssetBundles)
{
m_CurDownload = download;//+++
yield return m_Mono.StartCoroutine(download.Download());
//...
}
//...
}

最后在HotPatchManager里面添加GetProgress方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// <summary>
/// 获取总下载进度
/// </summary>
/// <returns>进度</returns>
public float GetProgress()
{
float alreadySize = m_AlreadyDownList.Sum(x => x.Size);
float curAlreadySize = 0;
if(m_CurDownload != null)
{
Patch patch = FindPatchByName(m_CurDownload.FileName);
if (patch != null && !m_AlreadyDownList.Contains(patch))
{
curAlreadySize = m_CurDownload.GetProcess() * patch.Size;
}
}
return (alreadySize + curAlreadySize) / LoadSumSize;
}

获取已经下载大小

HotPatchManager里面添加GetLoadSize方法,获取下载总大小,可以用来计算下载速度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   /// <summary>
/// 获取已经下载总大小
/// </summary>
/// <returns>总大小</returns>

public float GetLoadSize()
{
float alreadySize = m_AlreadyDownList.Sum(x => x.Size);
float curAlreadySize = 0;
if (m_CurDownload != null)
{
Patch patch = FindPatchByName(m_CurDownload.FileName);
if (patch != null && !m_AlreadyDownList.Contains(patch))
{
curAlreadySize = m_CurDownload.GetProcess() * patch.Size;
}
}
return alreadySize + curAlreadySize;
}

简化GetProgress方法

1
2
3
4
public float GetProgress()
{
return GetLoadSize() / LoadSumSize;
}

有了下载总进度和已经下载的大小,我们就可以计算下载的速度了(计算出来的是有误差的,并非实际速度)

资源加载路径替换

热更包下载完成后,需要在AssetBundleManager中指定好路径

首先在HotPatchManager里面添加ComputeABPath方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <summary>
/// 计算热更AB包路径
/// </summary>
/// <param name="name">ab包名</param>
/// <returns>ab包全路径</returns>
public string ComputeABPath(string name)
{
Patch patch = null;
if (m_HotFixDic.TryGetValue(name, out patch))
{
return m_DownLoadPath + "/" + name;
}
return string.Empty;
}

这时我们的m_HotFixDic就派上用场了,它用来确认加载的包是否是目前能用的ab包,防止非法加载AB包

然后再在AssetBundleManager里面的LoadAssetBundle方法内添加路径的替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    /// <summary>
/// 加载AssetBundle并维护AssetBundleItem中间类
/// </summary>
private AssetBundle LoadAssetBundle(string abName)
{
AssetBundleItem abItem = null;
uint abCrc = CRC32.GetCRC32(abName);
if(!m_AssetBundleItemDic.TryGetValue(abCrc, out abItem))
{
AssetBundle assetBundle = null;
string hotABPath = HotPatchManager.Instance.ComputeABPath(abName);//+++
string fullPath = string.IsNullOrEmpty(hotABPath)? ABLoadPath + abName : hotABPath;//+++

#if UNITY_STANDALONE || UNITY_EDITOR
if(File.Exists(fullPath))//在安卓和WebGL平台,fullPath是一个URL,这个判断是false
{
assetBundle = AssetBundle.LoadFromFile(fullPath);
}
#endif
//...
}
//...
}