我这里实现了3个文件来处理热更新:
Logo.cs 游戏的启动文件,里面处理下载存放资源路径的初始化。下载列表检查,下载,进入游戏
DownLoader.cs U3d的一个组件,里面包含具体的下载线程,用来处理开启多线程下载
HttpDownLoad.cs 具体的文件下载类,线程运行下载,支持断点下载
/* Logo.cs
* Created By Zhaotao On 2019-4-10
* Desc:游戏启动文件,处理更新流程,资源加载
*/
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using DigiskyUnity;
using UnityEngine;
public class Logo : MonoBehaviour
{
[SerializeField]
private bool _UseHotUpdate = false;
[SerializeField]
private string _ResServerHttp = "http://127.0.0.1:8001/";
private bool _FileListDownFinish = false;
private ResManager _ResMgr = null;
private LogoState _CurState;
private Splash _SplashCom = null;
private const string _VerFileName = "ver.txt";
private const string _FileListName = "files_list.txt";
private Dictionary _OldDict = new Dictionary();
private Dictionary _ServerDict = new Dictionary();
private long _DownByteSize = 0;
private long _FinishByteSize = 0;
private DownLoader _DownLoader;
private int _TotalDownloadCount = 0;
private string _ServerVer = "";
private bool _NeedSaveFileList = false;
private enum LogoState
{
INIT,
INIT_FINISH,
CHECK_UPDATE_LIST,
CHECK_UPDATE_LIST_FINISH,
DOWN_FILE,
DOWN_FILE_FINISH,
PRE_RES,
PRE_RES_FINISH,
ENTER_GAME,
}
// Start is called before the first frame update
void Start()
{
_CurState = LogoState.INIT;
Init();
_CurState = LogoState.INIT_FINISH;
}
// Update is called once per frame
void Update()
{
StartCoroutine(StartServer());
}
void Init()
{
//一开始就加载Resmanager ,初始化资源路径
_ResMgr = this.gameObject.AddComponent();
_ResMgr.SetBundleFolder(new string[]{"StreamingAssets"});
}
//检查更新列表
IEnumerator CheckUpdateFileList()
{
_CurState = LogoState.CHECK_UPDATE_LIST;
_SplashCom.SetDesc("检查文件更新...");
WWW www = new WWW(_ResServerHttp+_VerFileName);
yield return www;
_ServerVer = www.text;
_CurState = LogoState.CHECK_UPDATE_LIST_FINISH;
string resRootPath = ResManager.GetResPath();
if (File.Exists(resRootPath + "/" + _VerFileName))
{
StreamReader sr = new StreamReader(resRootPath + "/" + _VerFileName);
string oldVer = sr.ReadToEnd();
sr.Close();
if (www.text != oldVer)
{
//需要检查每个文件
_SplashCom.SetDesc("整理下载文件...");
www = new WWW(_ResServerHttp+_FileListName);
yield return www;
//读取包里面的资源版本信息
sr = new StreamReader(resRootPath + "/" + _FileListName);
string line = sr.ReadLine();
while (line != null)
{
string[] patt = line.Split('|');
if (patt.Length < 3)
{
continue;
}
UpdateFileInfo info = new UpdateFileInfo(patt[0].Trim(),patt[1].Trim(),int.Parse(patt[2]));
_OldDict.Add(patt[0].Trim(),info);
line = sr.ReadLine();
}
sr.Close();
yield return new WaitForEndOfFrame();
//解析服务器上的最新资源信息
string[] fileInfos = www.text.Split('\n');
foreach (var it in fileInfos)
{
string[] patt = it.Split('|');
if (patt.Length < 3)
{
continue;
}
UpdateFileInfo info = new UpdateFileInfo(patt[0].Trim(),patt[1].Trim(),int.Parse(patt[2]));
_ServerDict.Add(patt[0].Trim(),info);
}
yield return new WaitForEndOfFrame();
//做比对
Dictionary downList = new Dictionary();
foreach (var it in _ServerDict)
{
if (!_OldDict.ContainsKey(it.Key) || _OldDict[it.Key].md5 != it.Value.md5)
{
downList.Add(_ResServerHttp + it.Key,resRootPath+"/"+it.Key);
_DownByteSize += it.Value.size;
}
}
_TotalDownloadCount = downList.Keys.Count;
if (_TotalDownloadCount > 0)
{
StartDownloadFiles(downList);
}
else
{
_CurState = LogoState.DOWN_FILE_FINISH;
SaveNewVerFile();
}
}
else
{
_CurState = LogoState.DOWN_FILE_FINISH;
}
}
else
{
//全部下载
//创建版本好文件
File.Create(resRootPath + "/" + _VerFileName).Close();
_SplashCom.SetDesc("整理下载文件...");
www = new WWW(_ResServerHttp + "/" + _FileListName);
yield return www;
//解析服务器上的最新资源信息
string[] fileInfos = www.text.Split('\n');
foreach (var it in fileInfos)
{
string[] patt = it.Split('|');
if (patt.Length < 3)
{
continue;
}
UpdateFileInfo info = new UpdateFileInfo(patt[0].Trim(),patt[1],int.Parse(patt[2]));
_ServerDict.Add(patt[0].Trim(),info);
}
yield return new WaitForEndOfFrame();
//做比对
Dictionary downList = new Dictionary();
foreach (var it in _ServerDict)
{
if (!_OldDict.ContainsKey(it.Key) || _OldDict[it.Key].md5 != it.Value.md5)
{
downList.Add(_ResServerHttp + it.Key,resRootPath+"/"+it.Key);
_DownByteSize += it.Value.size;
}
}
_TotalDownloadCount = downList.Keys.Count;
if (_TotalDownloadCount > 0)
{
StartDownloadFiles(downList);
}
else
{
_CurState = LogoState.DOWN_FILE_FINISH;
SaveNewVerFile();
}
}
}
void StartDownloadFiles(Dictionary fileList)
{
string desc = string.Format("总进度:{0}/{1}({2}/{3})", 0, _TotalDownloadCount,
0, _DownByteSize);
SetSplashDesc(desc);
_CurState = LogoState.DOWN_FILE;
_DownLoader = this.gameObject.AddComponent();
_DownLoader.SetThreadCount(3);
_DownLoader.StartDownload(fileList,DownloadFinishCallBack);
}
void DownloadFinishCallBack(DownLoader.ProgressInfo info)
{
foreach (var it in info.FinishFiles)
{
string fileName = it.Replace(_ResServerHttp, "");
Debug.Log("Download Finish:"+fileName);
_FinishByteSize += _ServerDict[fileName].size;
//记录下载完的文件,防止退出游戏记录失败
UpdateFileInfo tmp = new UpdateFileInfo();
tmp.fileName = _ServerDict[fileName].fileName;
tmp.md5 = _ServerDict[fileName].md5;
tmp.size = _ServerDict[fileName].size;
if (_OldDict.ContainsKey(fileName))
{
_OldDict[fileName] = tmp;
}
else
{
_OldDict.Add(fileName,tmp);
}
_NeedSaveFileList = true;
}
if (info.AllFinish)
{
SaveDownFileList();
SaveNewVerFile();
_DownLoader.RemoveListener();
_CurState = LogoState.DOWN_FILE_FINISH;
}
}
void SaveDownFileList()
{
Debug.Log("SaveDownFileList");
string resRootPath = ResManager.GetResPath();
StreamWriter sw = new StreamWriter(resRootPath + "/" + _FileListName, false, Encoding.UTF8);
foreach (var it in _OldDict)
{
sw.WriteLine(it.Value.fileName + "|" + it.Value.md5 + "|" + it.Value.size);
}
sw.Close();
sw.Dispose();
_NeedSaveFileList = false;
}
void SaveNewVerFile()
{
string resRootPath = ResManager.GetResPath();
StreamWriter sw = new StreamWriter(resRootPath + "/" + _VerFileName, false, Encoding.UTF8);
sw.Write(_ServerVer);
sw.Close();
sw.Dispose();
}
//开始流程服务
IEnumerator StartServer()
{
if (_UseHotUpdate && _CurState == LogoState.INIT_FINISH)
{
OpenSplashScreen();
yield return CheckUpdateFileList();
yield return new WaitForEndOfFrame();
}
else if(!_UseHotUpdate && _CurState < LogoState.DOWN_FILE_FINISH)
{
_CurState = LogoState.DOWN_FILE_FINISH;
}
if (_CurState == LogoState.DOWN_FILE)
{
//更新进度条信息
if (_DownLoader != null)
{
Dictionary dict = _DownLoader.GetDownloadingFileInfo();
float subByte = 0;
StringBuilder subPerStr = new StringBuilder();
foreach (var it in dict)
{
string fileName = it.Key.Replace(_ResServerHttp, "");
subPerStr.Append(fileName);
subPerStr.Append("\t");
subPerStr.AppendFormat("{0}%\n",it.Value.progress * 100);
subByte += _ServerDict[fileName].size * it.Value.progress;
}
subPerStr.AppendFormat("总进度:{0}/{1}({2}/{3})", _DownLoader.FinishCount(), _TotalDownloadCount,
_FinishByteSize +subByte, _DownByteSize);
SetSplashDesc(subPerStr.ToString());
SetSplashProgress((_FinishByteSize +subByte)/_DownByteSize);
}
}
if ( _CurState == LogoState.DOWN_FILE_FINISH)
{
if (_DownLoader != null)
{
Destroy(_DownLoader);
_DownLoader = null;
}
SetSplashDesc("下载完成");
SetSplashProgress(1);
_CurState = LogoState.PRE_RES;
yield return new WaitForSeconds(1);
_ResMgr.OnPreInit();
_CurState = LogoState.PRE_RES_FINISH;
}
if (_CurState == LogoState.PRE_RES_FINISH)
{
if (_SplashCom != null)
{
Destroy(_SplashCom.gameObject);
_SplashCom = null;
}
this.gameObject.AddComponent();
_CurState = LogoState.ENTER_GAME;
}
}
//设置SplashScreen 进度条描述
private void SetSplashDesc(string desc)
{
if (_SplashCom != null)
{
_SplashCom.SetDesc(desc);
}
}
//设置SplashSreen 的进度条值[0-1]
private void SetSplashProgress(float value)
{
if (_SplashCom != null)
{
_SplashCom.SetProgressValue(value);
}
}
private void OpenSplashScreen()
{
GameObject obj = Instantiate(Resources.Load("SplashWnd"));
_SplashCom = obj.GetComponent();
_SplashCom.SetProgressValue(0);
}
private struct UpdateFileInfo
{
public string fileName;
public string md5;
public int size;
public UpdateFileInfo(string name, string m, int s)
{
fileName = name;
md5 = m;
size = s;
}
}
//应用Home出去的时候
private void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus && _NeedSaveFileList)
{
SaveDownFileList();
}
}
//应该关闭的时候
private void OnApplicationQuit()
{
if (_NeedSaveFileList)
{
SaveDownFileList();
}
}
}
/* DownLoader.cs
* Created By Zhaotao On 2019-4-09
* Desc:资源下载器
*/
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
public class DownLoader : MonoBehaviour
{
//同时开启的下载线程数量
[SerializeField]
private int _ThreadCount = 1;
//下载文件列表
private Dictionary _FilesDict = null;
//下载器
private HttpDownLoad[] _Loader = null;
private DownLoaderCallBack _onDownFinishCallBack = new DownLoaderCallBack();
private static object _sLockCallback = new object();
private int _FinishCount = 0;
private float _TotalMemery = 0;
private DownLoadState _CurState = DownLoadState.NONE;
private int _CurDownIndex = -1;
private Dictionary _DownloadingFilePerDict = new Dictionary();
private enum DownLoadState
{
NONE,
NOTHREAD,
READY,
DOWNLOADING,
FINISH,
}
void Init()
{
if (_ThreadCount <= 0)
{
_CurState = DownLoadState.NOTHREAD;
return;
}
_Loader = new HttpDownLoad[_ThreadCount];
for (int i = 0; i < _Loader.Length; ++i)
{
_Loader[i] = new HttpDownLoad();
}
_CurState = DownLoadState.READY;
}
public Dictionary GetDownloadingFileInfo()
{
return _DownloadingFilePerDict;
}
private void OnDisable()
{
for (int i = 0; i < _Loader.Length; ++i)
{
_Loader[i].Close();
}
}
///
/// 开始下载
///
/// 下载的文件列表
/// 下载的进度回调
public void StartDownload( Dictionary dict,UnityAction fileFinishCallBack)
{
Init();
_DownloadingFilePerDict.Clear();
if (_CurState == DownLoadState.NOTHREAD)
{
throw new Exception("No Thread Count,Thread Count must >= 1");
}
_FilesDict = dict;
_onDownFinishCallBack.AddListener(fileFinishCallBack);
int fileCount = _FilesDict.Keys.Count;
if (_CurState == DownLoadState.READY)
{
_CurState = DownLoadState.DOWNLOADING;
for (int i = 0; i < _Loader.Length; ++i)
{
_CurDownIndex++;
if (_CurDownIndex >= fileCount && _DownloadingFilePerDict.Keys.Count > 0 )
{
//确定其它线程完成
return;
}else if (_CurDownIndex >= fileCount && _DownloadingFilePerDict.Keys.Count == 0)
{
_CurState = DownLoadState.FINISH;
return;
}
string url = _FilesDict.Keys.ToArray()[_CurDownIndex];
string fileName = _FilesDict[url];
HttpDownLoad curLoader = _Loader[i];
curLoader.DownLoad(url,fileName,HttpDownloadCallBack);
_DownloadingFilePerDict.Add(url,curLoader);
}
}
}
void HttpDownloadCallBack(object obj)
{
lock (_sLockCallback)
{
HttpDownLoad curLoader = obj as HttpDownLoad;
_DownloadingFilePerDict.Remove(curLoader.Url);
_FinishCount++;
int totalCount = _FilesDict.Keys.Count;
ProgressInfo info = new ProgressInfo(_FinishCount,totalCount == _FinishCount);
info.FinishFiles.Add(curLoader.Url);
_onDownFinishCallBack.Invoke(info);
_CurDownIndex++;
if (_CurDownIndex >= totalCount && _DownloadingFilePerDict.Keys.Count > 0)
{
//等到其它线程的完成
return;
}else if (_CurDownIndex >= totalCount && _DownloadingFilePerDict.Keys.Count == 0)
{
_CurState = DownLoadState.FINISH;
return;
}
string url = _FilesDict.Keys.ToArray()[_CurDownIndex];
string fileName = _FilesDict[url];
curLoader.DownLoad(url,fileName,HttpDownloadCallBack);
_DownloadingFilePerDict.Add(url,curLoader);
}
}
public void RemoveListener()
{
_onDownFinishCallBack.RemoveAllListeners();
}
public void SetThreadCount(int c)
{
_ThreadCount = c;
}
public int FinishCount()
{
return _FinishCount;
}
public struct ProgressInfo
{
//全部下载完的文件数量
public int FinishCount;
//当前此帧完成的文件
public List FinishFiles;
//是否全部下载完成
public bool AllFinish;
public ProgressInfo(int finishCount,bool allFinish)
{
FinishCount = finishCount;
FinishFiles = new List();
AllFinish = allFinish;
}
}
private class DownLoaderCallBack : UnityEvent
{
}
}
/* HttpDownLoad.cs
* Created By Zhaotao On 2019-4-08
* Desc:文件斷點下載
*/
using System.Threading;
using System.IO;
using System.Net;
using System;
using JetBrains.Annotations;
///
/// 通过http下载资源
///
public class HttpDownLoad
{
//下载进度
public float progress { get; private set; }
//涉及子线程要注意,Unity关闭的时候子线程不会关闭,所以要有一个标识
private bool isStop;
//子线程负责下载,否则会阻塞主线程,Unity界面会卡主
private Thread thread;
//表示下载是否完成
public bool isDone { get; private set; }
//下载链接
public string Url { get; private set; }
///
/// 文件下载
///
/// 下载链接
/// 存储地址
/// 回调函数
public void DownLoad(string url, string saveFile, [CanBeNull] Action