热跟新——资源更新

开发过程中常把需要更新的资源文件放到云服务器中,每次游戏运行时候去比对版本文件信息,定量更新本地文件和增量下载远程文件。

版本文件生成代码:

private void OnCreateVersionTextCallBack()
{
    string path = Application.dataPath + "/../AssetBundles/" + arrBuildTarget[buildTargetIndex];

    if (!Directory.Exists(path))
    {
        Directory.CreateDirectory(path);
    }
    string strVersionTextPath = path + "/VersionFule.txt";//版本文件路径
    //如果版本文件存在 则删除
    IOUtil.DeleteFile(strVersionTextPath);

    StringBuilder sbContent = new StringBuilder();

    DirectoryInfo dirctory = new DirectoryInfo(path);
    //拿到文件夹下所有文件
    FileInfo[] arrFiles = dirctory.GetFiles("*", SearchOption.AllDirectories);
    for (int i = 0; i < arrFiles.Length; i++)
    {
        FileInfo file = arrFiles[i];
        string fullName = file.FullName;//全名 包括路径扩展名
        //相对路径
        string name = fullName.Substring(fullName.IndexOf(arrBuildTarget[buildTargetIndex]) + arrBuildTarget[buildTargetIndex].Length + 1);
        
        string md5 = EncryptUtil.GetFileMD5(fullName);//文件的MD5
        if (md5 == null) continue;

        string size = Math.Ceiling(file.Length / 1024f).ToString();//文件大小

        bool isFirstData = false;//是否初始数据
        bool isBreak = false;
        for (int j = 0; j < m_lst.Count; j++)
        {
            foreach (string xmlPath in m_lst[j].PathList)
            {
                string tempPath = xmlPath;
                if (xmlPath.IndexOf(".") != -1)
                {
                    tempPath = xmlPath.Substring(0, xmlPath.IndexOf("."));
                }
                if (name.IndexOf(tempPath, StringComparison.CurrentCultureIgnoreCase) != -1)
                {
                    isFirstData = m_lst[j].IsFirstData;
                    isBreak = true;
                    break;
                }
            }
            if (isBreak) break;
        }
        if (name.IndexOf("DataTable") != -1 || name.IndexOf("Windows") != -1)
        {
            isFirstData = true;
        }
        string strLine = string.Format("{0} {1} {2} {3}", name, md5, size, isFirstData ? 1 : 0);
        sbContent.AppendLine(strLine);
    }

    IOUtil.CreateTextFile(strVersionTextPath, sbContent.ToString());
    Debug.Log("生成版本文件");
}

运行代码后生成VersionFule.txt 版本信息文件——文件名(文件相对下载路径),文件MD5值,文件大小,是否初始文件。

为了模拟云服务器下载过程,可以在本地安装HFS服务器模拟一下下载过程

HFS服务器
把StreamingAsset中文件解压到PersistentDataPath文件夹下(权限高,可读可写):

/// <summary>
/// 读取streamingAssetsPath资源目录的版本文件
/// </summary>
/// <param name="fileUrl"></param>
/// <param name="onReadStreamingAssetOver"></param>
/// <returns></returns>
private IEnumerator ReadStreamingAssetVersionFile(string fileUrl, Action<string> onReadStreamingAssetOver)
{
   UISceneInitCtrl.Instance.SetProgress("正在准备进行资源初始化", 0);
   using (WWW www = new WWW(fileUrl))
   {
       yield return www;
       if (www.error == null)
       {
           if (onReadStreamingAssetOver != null)
           {
               onReadStreamingAssetOver(Encoding.UTF8.GetString(www.bytes));
           }
       }
       else
       {
           onReadStreamingAssetOver("");
       }
   }
}

//读取StreamingAsset文件回调
private void OnReadStreamingAssetOver(string obj)
{
     GlobalInit.Instance.StartCoroutine(InitStreamingAssetList(obj));
}

///循环解压StreamingAsset文件到persistentDataPath 文件夹下
private IEnumerator InitStreamingAssetList(string conent)
{
    if (string.IsNullOrEmpty(conent))
    {
        InitCheckVersion();
        yield break;
    }
    string[] arr = conent.Split('\n');
    //循环解压
    for (int i = 0; i < arr.Length; i++)
    {
        string[] arrInfo = arr[i].Split(' ');

        string fileUrl = arrInfo[0];//短路径

        yield return GlobalInit.Instance.StartCoroutine(AsserLoadToLocal(m_StreamingAssetsPath + fileUrl, LocalFilePath + fileUrl));

        float value = (i + 1) / (float)arr.Length;
        UISceneInitCtrl.Instance.SetProgress(string.Format("初始化资源不消耗流量 {0}/{1}", i + 1, arr.Length), value);
    }
    //解压版本文件
    yield return GlobalInit.Instance.StartCoroutine(AsserLoadToLocal(m_StreamingAssetsPath + m_VersionFuleName, LocalFilePath + m_VersionFuleName));
    InitCheckVersion();
}

封装一下下载文件实体类:

public class DownloadDataEntity
{
    /// <summary>
    /// 资源名称
    /// </summary>
    public string FullName;
    /// <summary>
    /// MD5
    /// </summary>
    public string MD5;
    /// <summary>
    /// 资源大小(k)
    /// </summary>
    public int Size;
    /// <summary>
    /// 是否初始资源
    /// </summary>
    public bool IsFirstData;
}

WWW获取服务器版本文件数据,比对本地版本文件数据,将需要下载的数据列表传给文件下载中心。

private void OnInitVersionCallBack(List<DownloadDataEntity> serverDownloadData)
{
    if (serverDownloadData == null)
    {
        //直接读取本地文件
        Debug.LogError("没有网络,直接加载本地文件");

        UISceneInitCtrl.Instance.SetProgress("资源更新完毕", 1);
        if (OnInitComplete != null)
        {
            OnInitComplete(2);
        }
    }
    else
    {
        //需要下载的数据列表
        m_ServerDataList = serverDownloadData;
        if (File.Exists(m_LoaclVersionPath))
        {
            //与服务器对比
            //服务器数据

            //服务器数据VersionFule中的 fullname 和 MD5
            Dictionary<string, string> serverDic = PackDownloadDataDic(serverDownloadData);

            //本地VersionFule数据
            string content = IOUtil.GetFileText(m_LoaclVersionPath);

            // 本地VersionFule中的 fullname 和 MD5 
            Dictionary<string, string> clientDic = PackDownloadDataDic(content);
            //本地 VersionFule 转化成 List<DownloadDataEntity> 
            m_LocalDataList = PackDownloadData(content);

            //1.新加的初始资源
            for (int i = 0; i < serverDownloadData.Count; i++)
            {
                if (serverDownloadData[i].IsFirstData && !clientDic.ContainsKey(serverDownloadData[i].FullName))
                {
                    Debug.Log("download   " + serverDownloadData[i].FullName);
                    m_NeddDownloadDataList.Add(serverDownloadData[i]);
                }
            }
            //2.对比已经下载过的 但是有更新的资源
            foreach (var item in clientDic)
            {
                //如果md5不一样
                if (serverDic.ContainsKey(item.Key) && serverDic[item.Key] != item.Value)
                {
                    DownloadDataEntity entity = GetDownloadData(item.Key, serverDownloadData);
                    if (entity != null)
                    {
                        Debug.Log("download   " + entity.FullName);
                        m_NeddDownloadDataList.Add(entity);
                    }
                }
            }
        }
        else
        {
            //如果本地没有版本文件,初始资源需要下载的文件
            for (int i = 0; i < serverDownloadData.Count; i++)
            {
                if (serverDownloadData[i].IsFirstData)
                {
                    Debug.Log("download   " + serverDownloadData[i].FullName);
                    m_NeddDownloadDataList.Add(serverDownloadData[i]);
                }
            }
        }


        if (m_NeddDownloadDataList.Count == 0)
        {
            Debug.LogError("不需要网络数据更新");

            UISceneInitCtrl.Instance.SetProgress("资源更新完毕", 1);
            if (OnInitComplete != null)
            {
                OnInitComplete(0);
            }

            SaveLocalData();

            return;
        }

        //进行下载 拿到下载列表 
        AssetBundleDownload.Instance.DownloadFiles(m_NeddDownloadDataList);
        SaveLocalData();
    }
}

封装一个文件异步主下载器:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

/// <summary>
/// 主下载器 检查版本  分配下载器任务
/// </summary>
public class AssetBundleDownload : SingletonMono<AssetBundleDownload>
{
    private string m_VersionUrl;
    private Action<List<DownloadDataEntity>> m_OnInityVersion;
    /// <summary>
    /// 下载器的数组
    /// </summary>
    private AssetBundleDownloadRoutine[] m_Routines = new AssetBundleDownloadRoutine[DownloadMgr.DownloadRoutineNum];
    private int m_RoutineIndex = 0;//下载器索引

    private bool m_IsDownloadover;
    protected override void OnStart()
    {
        base.OnStart();
        //真正的运行
        StartCoroutine(DownLoadVersion(m_VersionUrl));
    }

    private float m_Time = 2;//采样时间
    private float m_AleadyTime = 0;//已经下载的时间

    private float m_NeedTime = 0;//剩余时间
    private float m_Speed = 0;//下载速度


    protected override void OnUpdate()
    {
        base.OnUpdate();
        //如果需要下载的数量大于0 并且没有下载完成
        if (TotalCount > 0 && !m_IsDownloadover)
        {
            int totalCompleteCount = CurrCompeletTotalCount();
            totalCompleteCount = totalCompleteCount == 0 ? 1 : totalCompleteCount;
            int totalCompleteSize = CurrCompeletTotalSize();
            m_AleadyTime += Time.deltaTime;
            if (m_AleadyTime > m_Time && m_Speed == 0)
            {
                m_Speed = totalCompleteSize / m_Time;
            }
            if (m_Speed > 0)
            {
                //剩余时间=(总大小-已经下载的大小)/速度
                m_NeedTime = (ToalSize - totalCompleteSize) / m_Speed;
            }
            string str = string.Format("资源正在下载{0}/{1}", totalCompleteCount, TotalCount);
            // string strProgress = string.Format("下载进度={0}",totalCompleteSize/(float)ToalSize);
            UISceneInitCtrl.Instance.SetProgress(str, totalCompleteCount / (float)TotalCount);


            if (m_NeedTime > 0)
            {
                string strNeedTime = string.Format("剩余{0}秒", m_NeedTime);
            }

            if (totalCompleteCount == TotalCount)
            {
                m_IsDownloadover = true;
                ///DebugApp.Log("资源更新完毕");
                UISceneInitCtrl.Instance.SetProgress("资源更新完毕", 1);

                if (DownloadMgr.Instance.OnInitComplete != null)
                {
                    DownloadMgr.Instance.OnInitComplete(1);
                }
            }
        }
    }
    /// <summary>
    /// 初始化服务器的版本信息
    /// </summary>
    /// <param name="url"></param>
    /// <param name="onInityVersion"></param>
    public void InitServerVersion(string url, Action<List<DownloadDataEntity>> onInityVersion)
    {
        m_VersionUrl = url;
        m_OnInityVersion = onInityVersion;
    }

    /// <summary>
    /// 查看CDN服务器上Version信息
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    private IEnumerator DownLoadVersion(string url)
    {
        WWW www = new WWW(url);

        float timeOut = Time.time;
        float progress = www.progress;
        while (www != null && www.isDone)
        {
            if (progress < www.progress)
            {
                timeOut = Time.time;
                progress = www.progress;
            }
            if ((Time.time - timeOut) > DownloadMgr.DownLoadTimeOut)
            {
                DebugApp.Log("下载超时");
                yield break;
            }
        }
        yield return www;

        if (www != null && www.error == null)
        {
            string conten = www.text;
            Debug.Log(conten);
            if (m_OnInityVersion != null)
            {
                m_OnInityVersion(DownloadMgr.Instance.PackDownloadData(conten));
            }
        }
        else
        {
            if (m_OnInityVersion != null)
            {
                m_OnInityVersion(null);
            }
            DebugApp.Log("下载失败 原因:" + www.error);
        }
    }
    /// <summary>
    /// 总大小
    /// </summary>
    public int ToalSize
    {
        get;
        private set;
    }
    /// <summary>
    /// 总数量
    /// </summary>
    public int TotalCount
    {
        get;
        private set;
    }
    /// <summary>
    /// 当前已经下载的文件总大小
    /// </summary>
    /// <returns></returns>
    public int CurrCompeletTotalSize()
    {
        int compeleteTotalSize = 0;
        for (int i = 0; i < m_Routines.Length; i++)
        {
            if (m_Routines[i] == null) continue;
            compeleteTotalSize += m_Routines[i].DownloadSize;
        }
        return compeleteTotalSize;
    }

    /// <summary>
    /// 当前已经下载的文件总数量
    /// </summary>
    /// <returns></returns>
    public int CurrCompeletTotalCount()
    {
        int compeleteTotalCount = 0;
        for (int i = 0; i < m_Routines.Length; i++)
        {
            if (m_Routines[i] == null) continue;
            compeleteTotalCount += m_Routines[i].CompleteCount;
        }
        return compeleteTotalCount;
    }

    /// <summary>
    /// 下载文件
    /// </summary>
    /// <param name="downloadLst"></param>
    public void DownloadFiles(List<DownloadDataEntity> downloadLst)
    {
        ToalSize = 0;
        TotalCount = 0;
        //初始化下载器
        for (int i = 0; i < m_Routines.Length; i++)
        {
            if (m_Routines[i] == null)
            {
                m_Routines[i] = gameObject.AddComponent<AssetBundleDownloadRoutine>();
            }
        }

        for (int i = 0; i < downloadLst.Count; i++)
        {
            m_RoutineIndex = m_RoutineIndex % m_Routines.Length;//0-4
            //其中一个下载器 分配一个文件
            m_Routines[m_RoutineIndex].AddDownload(downloadLst[i]);
            m_RoutineIndex++;
            ToalSize += downloadLst[i].Size;
            TotalCount++;
        }
        //让下载器开始下载
        for (int i = 0; i < m_Routines.Length; i++)
        {
            if (m_Routines[i] == null) continue;
            m_Routines[i].StartDownload();
        }
    }

    public IEnumerator DownloadData(DownloadDataEntity currDownLoadData, Action<bool> onComplete)
    {
        string dataUrl = DownloadMgr.DownloadUrl + currDownLoadData.FullName;//资源下载路径
        //短路径 用于创建文件夹
        string path = currDownLoadData.FullName.Substring(0, currDownLoadData.FullName.LastIndexOf('\\'));

        //得到本地路径
        string localFilePath = DownloadMgr.Instance.LocalFilePath + path;

        if (!Directory.Exists(localFilePath))
        {
            Directory.CreateDirectory(localFilePath);
        }
        WWW www = new WWW(dataUrl);
        float timeOut = Time.time;
        float progress = www.progress;

        while (www != null && www.isDone)
        {
            if (progress < www.progress)
            {
                timeOut = Time.time;
                progress = www.progress;
            }
            if (Time.time - timeOut > DownloadMgr.DownLoadTimeOut)
            {
                DebugApp.LogError("下载超时");
                onComplete(false);
                yield break;
            }
            yield return null; //等一帧  会卡死

        }

        yield return www;
        if (www != null && www.error == null)
        {
            using (FileStream fs = new FileStream(DownloadMgr.Instance.LocalFilePath + currDownLoadData.FullName, FileMode.Create, FileAccess.ReadWrite))
            {
                fs.Write(www.bytes, 0, www.bytes.Length);
            }
        }

        //写入本地文件
        DownloadMgr.Instance.ModifyLocalData(currDownLoadData);

        if (onComplete != null)
        {
            onComplete(true);
        }
    }
}

封装一个文件异步下载器:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

/// <summary>
/// 下载器
/// </summary>
public class AssetBundleDownloadRoutine : MonoBehaviour
{
    //需要下载的文件列表
    private List<DownloadDataEntity> m_List = new List<DownloadDataEntity>();
    private DownloadDataEntity m_CurrDownloadData;//当前正在下载的数据
    /// <summary>
    /// 需要下载的数量
    /// </summary>
    public int NeedDownloadCount
    {
        private set;
        get;
    }
    /// <summary>
    /// 已经下载完成的数量
    /// </summary>
    public int CompleteCount
    {
        private set;
        get;
    }

    private int m_DownloadSize;//已经下载好的文件的总大小

    private int m_CurrDownloadSize;//当前下载的文件大小


    /// <summary>
    /// 这个下载器已经下载的大小
    /// </summary>
    public int DownloadSize
    {
        get { return m_DownloadSize + m_CurrDownloadSize; }
    }
    //是否开始下载
    public bool IsStartDownload
    {
        private set;
        get;
    }

    /// <summary>
    /// 添加一个下载对象
    /// </summary>
    /// <param name="entity"></param>
    public void AddDownload(DownloadDataEntity entity)
    {
        Debug.LogError("添加需要下载数据" + entity.FullName);
        m_List.Add(entity);
    }

    /// <summary>
    /// 开始下载
    /// </summary>
    public void StartDownload()
    {
        IsStartDownload = true;
        NeedDownloadCount = m_List.Count;
    }
    private void Update()
    {
        if (IsStartDownload)
        {
            IsStartDownload = false;
            StartCoroutine(DownloadData());
        }
    }

    private IEnumerator DownloadData()
    {
        if (NeedDownloadCount == 0) yield break;
        m_CurrDownloadData = m_List[0];
        string dataUrl = DownloadMgr.DownloadUrl + m_CurrDownloadData.FullName;//资源下载路径
        int lastIndex = m_CurrDownloadData.FullName.LastIndexOf('\\');
        if (lastIndex > -1)
        {
            //短路径 用于创建文件夹
            string path = m_CurrDownloadData.FullName.Substring(0, lastIndex);
            //得到本地路径
            string localFilePath = DownloadMgr.Instance.LocalFilePath + path;
            if (!Directory.Exists(localFilePath))
            {
                Directory.CreateDirectory(localFilePath);
            }
        }

        WWW www = new WWW(dataUrl);
        float timeOut = Time.time;
        float progress = www.progress;
        while (www != null && www.isDone)
        {
            if (progress < www.progress)
            {
                timeOut = Time.time;
                progress = www.progress;

                m_CurrDownloadSize = (int)(m_CurrDownloadData.Size * progress);
            }

            if (Time.time - timeOut > DownloadMgr.DownLoadTimeOut)
            {
                DebugApp.LogError("下载超时");

                yield break;
            }

            yield return null; //等一帧  会卡死
        }

        yield return www;

        if (www != null && www.error == null)
        {
            using (FileStream fs = new FileStream(DownloadMgr.Instance.LocalFilePath + m_CurrDownloadData.FullName, FileMode.Create, FileAccess.ReadWrite))
            {
                fs.Write(www.bytes, 0, www.bytes.Length);
            }
        }
        //下载成功
        m_CurrDownloadSize = 0;
        m_DownloadSize += m_CurrDownloadData.Size;

        //写入本地文件
        DownloadMgr.Instance.ModifyLocalData(m_CurrDownloadData);

        m_List.RemoveAt(0);
        CompleteCount++;

        if (m_List.Count == 0)
        {
            m_List.Clear();
        }
        else
        {
            IsStartDownload = true;
        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值