BundleDownloaderComponent学习笔记
请大家关注我的微博:@NormanLin_BadPixel坏像素
这应该就是热更新的包下载相关的内容了。我们知道,每次我们加载热更新的资源,需要判断本地和服务器的版本,如果本地的版本过低,则需要从服务端重新下载。
我们来看一下热更新资源版本信息的结构吧。
VersionConfig
public int Version;
public long TotalSize;
public List<FileVersionInfo> FileVersionInfos = new List<FileVersionInfo>();
我们看到,储存了第几个版本的信息,储存了资源的全部大小,还储存了各个资源的文件信息,FileVersionInfo。
public class FileVersionInfo
{
public string File;
public string MD5;
public long Size;
}
也就是说,我们每次更新版本的时候不会更新全部的资源,而是可以根据FileVersionInfo的MD5决定是否需要更新指定资源。
具体是怎么实现的呢?我们继续往下看代码。
我们先通过UnityWebRequestAsync获取服务器上热更资源的版本,再对比本地的版本信息。如果本地没有版本(第一次运行),则所有的资源都需要更新。否则,先用简单的文件读取File.ReadAllText() 来获取本地的版本信息。(以上的版本信息都是获取到字符串用Json反序列化成VersionConfig对象的)
// 先删除服务器端没有的ab
foreach (FileVersionInfo fileVersionInfo in localVersionConfig.FileVersionInfos)
{
if (this.VersionConfig.FileInfoDict.ContainsKey(fileVersionInfo.File))
{
continue;
}
string abPath = Path.Combine(PathHelper.AppHotfixResPath, fileVersionInfo.File);
File.Delete(abPath);
}
// 再下载
foreach (FileVersionInfo fileVersionInfo in this.VersionConfig.FileVersionInfos)
{
FileVersionInfo localVersionInfo;
if (localVersionConfig.FileInfoDict.TryGetValue(fileVersionInfo.File, out localVersionInfo))
{
if (fileVersionInfo.MD5 == localVersionInfo.MD5)
{
continue;
}
}
if(fileVersionInfo.File == "Version.txt")
{
continue;
}
this.bundles.Enqueue(fileVersionInfo.File);
this.TotalSize += fileVersionInfo.Size;
}
作者注释的很详细了,先在本地删除服务器端没有的ab,再下载。这里的下载其实是登记需要下载的名单。然后我们转到WaitAsync
private Task<bool> WaitAsync()
{
if (this.bundles.Count == 0 && this.downloadingBundle == "")
{
return Task.FromResult(true);
}
this.Tcs = new TaskCompletionSource<bool>();
UpdateAsync();
return this.Tcs.Task;
}
经过上一步对比本地与服务器的版本,我们已经知道哪些资源是需要重新下载的。如果全部已经下载完了,则异步返回true。如果还有需要下载的,则开始异步下载。UpdateAsync,并返回his.Tcs.Task。我们知道,我们可以在下载完成的时候主动去结束这个Task。
UpdateAsync代码很长,我也全部帖出来了,我觉得应该很重要。我就在代码上面直接加注释帮助大家理解好了。
private async void UpdateAsync()
{
while (true)
{
//如果所有的资源都下载完成了,则跳出循环
if (this.bundles.Count == 0)
{
break;
}
//取出第一个需要重新下载的资源名,保存在downloadingBundle
this.downloadingBundle = this.bundles.Dequeue();
while (true)
{
//尝试去下载指定的资源
try
{
//创建一个UnityWebRequestAsync
using (this.downloadingRequest = ComponentFactory.Create<UnityWebRequestAsync>())
{
//开始异步下载指定资源
await this.downloadingRequest.DownloadAsync(GlobalConfigComponent.Instance.GlobalProto.GetUrl() + "StreamingAssets/" + this.downloadingBundle);
//在资源下载完成后,获取资源的数据。
byte[] data = this.downloadingRequest.Request.downloadHandler.data;
//获取资源完整的路径名
string path = Path.Combine(PathHelper.AppHotfixResPath, this.downloadingBundle);
//如果发现资源所在的文件夹在本地没有,则先在本地创建文件夹
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
//把资源数据保存到本地。FileMode.Create 指示操作系统应创建新文件,如果文件已经存在,它将被覆盖
using (FileStream fs = new FileStream(path, FileMode.Create))
{
fs.Write(data, 0, data.Length);
}
}
}
catch(Exception e)
{
Log.Error($"download bundle error: {this.downloadingBundle}\n{e}");
continue;
}
break;
}
//记录已经下载完的资源包,初始化一些内容,并开始下一个循环下载下一个资源包
this.downloadedBundles.Add(this.downloadingBundle);
this.downloadingBundle = "";
this.downloadingRequest = null;
}
//待所有资源下载完成后,更新本地的"Version.txt"为服务器的版本
using (FileStream fs = new FileStream(Path.Combine(PathHelper.AppHotfixResPath, "Version.txt"), FileMode.Create))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.Write(MongoHelper.ToJson(this.VersionConfig));
}
//当所有步骤完成后,我们手动结束this.Tcs,并返回true。
this.Tcs?.SetResult(true);
}
这样,一套完整的热更资源系统就完成了。
另外,作者还好心的提供了一个获取下载进度的方法public int Progress,这里就不讲了,大家自己看看就能明白了。