源工程来自Xlua从开发到热更全流程Demo_xlua打包教程-CSDN博客
目录
ABDownload -> AssetbundleDownloadRoutine
第一步 打AB包,更新版本文件信息
寻找对应平台文件夹下的版本文件信息,如果存在,则删除
遍历Assetbundles文件夹中目标平台文件下所有文件的绝对路径(从盘符开始)
再截取出从Assetbundles开始的相对路径
对绝对路径进行MD5哈希取值,对其大小根据 file.length / 1024f 进行取值
最后单个条目格式化为 “相对路径 MD5值 文件大小”
public void 生成版本文件()
{
outPath = Application.dataPath + "/../AssetBundles/" + EnumConvertToString(builPlatform); //版本文件所在路径
StringBuilder sb = new StringBuilder(); //版本文件要构建的内容
//检查是否已存在版本文件
string strVersionFilePath = outPath + "/VersionFile.txt";
if (File.Exists(strVersionFilePath))
{
File.Delete(strVersionFilePath);
}
DirectoryInfo theFolder = new DirectoryInfo(outPath);//拿到文件夹
FileInfo[] arrFiles = theFolder.GetFiles("*", SearchOption.AllDirectories);//拿到文件夹下所有文件
foreach (var item in arrFiles)
{
FileInfo file = item;
string fullName = file.FullName;//绝对路径
string name = fullName.Substring(fullName.IndexOf("AssetBundles"));//相对路径
//取MD5值
string md5 = Mima.GetMD5HashFromFile(fullName);
if (md5 == null)
{
Debug.LogError("有文件没有拿到MD5:" + item.Name);
continue;
}
string size = Mathf.Ceil(file.Length / 1024f).ToString();//文件大小
string strLine = string.Format("{0} {1} {2}", name, md5, size); //每个文件的版本信息
sb.AppendLine(strLine);//写入版本文件要构建的内容中,按行写入
}
//创建对应的文本文件
IOUtil.CreatTextFile(strVersionFilePath, sb.ToString());
}
第二步 将AB包和版本文件上传服务器
目前是使用本地模拟来模拟远程下载AB包,使用的是HFS简易HTTP服务器
可以很方便的将本地文件夹上传到虚拟服务器
记录IP地址和服务器路径
第三步 客户端版本检查
Main -> DownloadMgr
首先,利用宏来初始化不同平台客户端记录的服务器AB包路径(Build)和对应平台(BuildTarget)
然后在Main中先调用DownloadMgr单例的InitCheckVersion方法,传入版本文件路径和自身的回调函数SucessCheck
private void Start()
{
#if UNITY_EDITOR || UNITY_STANDLONE_WIN
build = "http://192.168.2.4/AssetBundles/StandaloneWindows";
buildTarget = "StandaloneWindows";//
#elif UNITY_ANDROID
build = "http://192.168.2.4/AssetBundles/Android";
buildTarget = "Android"; //给模拟器访问
#elif UNITY_IPHONE
buildTarget = "IOS";
#endif
//版本检查
DownLoadMgr.GetInstance().InitCheckVersion(build + "/VersionFile.txt", SucessCheck);
}
DownloadMgr -> ABDownload
在DownloadMgr单例中,又将参数转发给了ABDownload单例的InitServerVersion方法,传入的参数有版本文件路径,Main注册的回调和DownloadMgr注册的回调
public void InitCheckVersion(string versionPathURL, UnityAction finishCheck)
{
Debug.Log("运行平台为:" + Application.platform);
string strVersionPath = DownLoadUrl + "/" + "VersionFile.txt";//版本文件路径
Debug.Log("版本文件路径" + strVersionPath);
if (strVersionPath != versionPathURL)
{
Debug.Log("版本文件路径配置错误,可能无法根据版本文件下载资源,请修改");
Debug.LogError(versionPathURL);
ABDownLoad.Instance.InitServerVersion(versionPathURL, OnInitVersionCallBack, finishCheck);
}
else
{
ABDownLoad.Instance.InitServerVersion(strVersionPath, OnInitVersionCallBack, finishCheck);//将委托传进去
}
}
ABDownload -> DownloadMgr
ABDownload单例为懒加载,因此一单调用了其单例的Init方法后,就会执行它的Start方法,其会根据传入的版本文件路径进行版本文件的下载,下载完成后的字符串通过回调送回DownloadMgr
private void Start()
{
Debug.Log("读取版本文件ABDownLoad");
StartCoroutine(DownLoadVersion(m_VersionUrl));
}
private IEnumerator DownLoadVersion(string url)
{
UnityWebRequest webRequest = UnityWebRequest.Get(url);
yield return webRequest;//finish 等待资源下载
webRequest.SendWebRequest();
if (webRequest.isNetworkError || webRequest.isHttpError) //如果出错
{
Debug.Log(webRequest.error); //输出 错误信息
}
else
{
while (!webRequest.isDone) //只要下载没有完成,一直执行此循环
{
Debug.Log("请求版本信息" + webRequest.downloadProgress);
yield return 0;
}
if (webRequest.isDone) //如果下载完成了
{
print("版本文件下载完成:");
}
}
if (webRequest != null && webRequest.error == null)
{
string content = webRequest.downloadHandler.text;
Debug.Log("读取版本文件内容:" + content);
if (m_OnInitVersion != null)
{
m_OnInitVersion(DownLoadMgr.GetInstance().PackDownloadData(content));//调用委托 将下载列表内容解析 然后传给委托执行,执行下载
}
}
else
{
Debug.LogError("下载失败" + webRequest.error);
}
}
DownloadMgr -> ABDownload
在DownloadMgr中对字符串进行拆分,得到服务器的版本文件条目,然后去查找本地版本文件
如果本地没有版本文件,则将服务器的文件条目全部加入需要下载列表
如果本地有版本文件,首先遍历服务器文件条目,查找本地是否有对应条目,没有,则需要下载,有,如果MD5值不同,也需要下载
在需要下载条目明晰以后,再调用ABDownload的DownLoadFiles进行实际下载,传入的是记录的需要下载的条目List
private void OnInitVersionCallBack(List<DownLoadDataEnety> serverDownLoadData)
{
m_LocalVersionPath = LocalFilePath + "VersionFile.txt";
Debug.Log("版本文件地址: " + m_LocalVersionPath);
//todo 检测本地文件,生成版本文件,对比删除过时
if (File.Exists(m_LocalVersionPath))
{
Debug.Log("本地存在版本文件,对比服务器版本文件信息");
//如果本地文件存在版本文件 则和服务器进行对比
Dictionary<string, string> serveDic = PackDownLoadDataDic(serverDownLoadData);//服务器的版本文件字典
//本地数据
string clientContent = File.ReadAllText(m_LocalVersionPath);//读本地版本文件内容;
Dictionary<string, string> clientDic = PackDownLoadDataDic(clientContent);//传入内容进行分割,赋值字典
m_LocalDadaList = PackDownloadData(clientContent);
isLocalVersion = true;
//遍历服务器文件
foreach (var serverData in serverDownLoadData)
{
//如果服务器有文件本地不存在,直接下载
if (!clientDic.ContainsKey(serverData.FullName))
{
Debug.Log("服务器有本地文件没有的文件" + serverData.FullName);
m_NeedDownLoadDataList.Add(serverData);
}
if (clientDic.ContainsKey(serverData.FullName))
{
//同名文件比较MD5 ,不同则更新文件
if (serverData.MD5 != clientDic[serverData.FullName])//文件名一致,查找本地版本文件字典的MD5 是否与服务器版本文件的MD5一致
{
Debug.Log("同名文件MD5不同");
m_NeedDownLoadDataList.Add(serverData);
m_NeedDownLoadDataList.Add(serverData);
}
}
}
}
else
{
Debug.Log("本地不存在版本文件,全部下载");
for (int i = 0; i < serverDownLoadData.Count; i++)
{
m_NeedDownLoadDataList.Add(serverDownLoadData[i]);
}
}
//进行下载
ABDownLoad.Instance.DownLoadFiles(m_NeedDownLoadDataList);
}
ABDownload -> AssetbundleDownloadRoutine
ABDownload中有AssetbundleDownloadRoutine,可创建多个下载协程同时下载,首先创建这些下载协程对象,再将所需下载列表按顺序循环分发给协程直到分发完毕(这些下载协程各自维护一份自己的下载条目表
在AssetbundleDownloadRoutine中,首先会拼出下载的资源的服务器地址,并创建本地存放地址,接着会向服务器请求文件,并将得到的byte数组写入创建好的文件。完成一个下载流程后循环触发,直到全部下载完毕。
public void DownLoadFiles(List<DownLoadDataEnety> downloadList)
{
Debug.Log("开始下载");
TotalSize = 0;
totalCount = 0;
for (int i = 0; i < m_Routine.Length; i++)
{
if (m_Routine[i] == null)
{
Debug.Log("创建下载器");
m_Routine[i] = gameObject.AddComponent<AssetBundleDownloadRoutine>();
}
}
//循环给下载器分配任务,循环结束则任务分配完毕
for (int i = 0; i < downloadList.Count; i++)
{
Debug.Log("为下载器分配下载任务");
m_RoutineIndex = m_RoutineIndex % m_Routine.Length;//0-4 取余给每个下载器分别赋值下载任务
//分配文件
m_Routine[m_RoutineIndex].AddDownload(downloadList[i]);
m_RoutineIndex++;
TotalSize += downloadList[i].Size;
}
TotalCount = downloadList.Count;
Debug.LogError(string.Format("需要下载的总数量:{0} \n 需要下载的总大小:{1}", TotalCount, TotalSize));
//开始下载
for (int i = 0; i < m_Routine.Length; i++)
{
if (m_Routine[i] == null)
{
continue;
}
m_Routine[i].StarDownload();
}
}
ABDownload -> Main
全部下载完毕时,ABDownload的Update检测到满足条件,会触发Main函数的SucessCheck回调
if (totalCompleteCount == TotalCount)
{
m_IsDownLoadOver = true;
Debug.Log("下载完成");
//finish 判断下载完成 finishCheck ,进度条完成即下载完成
finishCheck();
}
在Main中,先加载主包和主包依赖,初始化这两个重要变量,再初始化主包的Assetbundle路径,方便余包加载,最后是通过LuaEnv的require ‘main’,来通过自定义的Loader来请求AB包中的main.lua,完成lua侧的热更新
private void SucessCheck()
{
Debug.Log("完成版本检查");
//AB主包的路径
string ABmainPath = Application.persistentDataPath + "/AssetBundles/" + buildTarget + "/" + buildTarget + ".assetbundle";
Debug.Log("AB主包加载路径:" + ABmainPath);
ABManager.Instance.LoadMainAB(ABmainPath);//加载AB主包
//ABManager.Instance.LoadResAsync<GameObject>("cube.assetbundle", "Cube", successTwo);//Cube测试
ABManager.Instance.pathUrl = Application.persistentDataPath + "/AssetBundles/" + buildTarget + "/";//主包所在目录,提供给其余包加载
//执行lua脚本(如果有热更新,替换脚本)
LuaEnv.DoString("require 'main'");//所有lua脚本通过main执行,不进行混合开发
MyBagLua.Instance.InitBagData();//初始化背包数据
isOverCheck = true;
}
private byte[] MyLoader(ref string filePath)
{
string path = Application.persistentDataPath + "/AssetBundles/" + buildTarget + "/Xlua/" + filePath + ".lua.txt";
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(path));
}