【Unity】热更新流程详解

源工程来自Xlua从开发到热更全流程Demo_xlua打包教程-CSDN博客

目录

第一步 打AB包,更新版本文件信息

第二步 将AB包和版本文件上传服务器

第三步 客户端版本检查

Main -> DownloadMgr

DownloadMgr -> ABDownload

ABDownload -> DownloadMgr

DownloadMgr -> ABDownload

ABDownload -> AssetbundleDownloadRoutine

ABDownload -> Main


第一步 打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));
}

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值