- 对AB包加密
创建加密相关脚本,这里使用 AES 加密对AB包资源进行加密,脚本如下
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
public class ABPackMenu : Editor
{
[MenuItem("My Tool/AB包加密/创建AB包版本文件/Window 版本")]
private static void EncrypABPackVersionFile_Window()
{
Debug.Log("加密并创建 Window 平台的AB包版本信息");
EncryptAndCreateVersionFile(BuildTarget.StandaloneWindows);
}
[MenuItem("My Tool/AB包加密/解密AB包版本文件/Window 版本")]
private static void DecryptABPack_Window()
{
Debug.Log("解密 Window 平台的AB包版本信息");
DecryptVersionFile(BuildTarget.StandaloneWindows);
}
[MenuItem("My Tool/AB包加密/创建AB包版本文件/Android 版本")]
private static void CreateABPackVersionFile_Android()
{
Debug.Log("加密并创建 Android 平台的AB包版本信息");
EncryptAndCreateVersionFile(BuildTarget.Android);
}
[MenuItem("My Tool/AB包加密/解密AB包版本文件/Android 版本")]
private static void DecryptABPack_Android()
{
Debug.Log("解密 Android 平台的AB包版本信息");
DecryptVersionFile(BuildTarget.Android);
}
[MenuItem("My Tool/AB包加密/创建AB包版本文件/IOS 版本")]
private static void CreateABPackVersionFile_IOS()
{
Debug.Log("加密并创建 IOS 平台的AB包版本信息");
DecryptVersionFile(BuildTarget.iOS);
}
[MenuItem("My Tool/AB包加密/解密AB包版本文件/IOS 版本")]
private static void DecryptABPack_IOS()
{
Debug.Log("解密 IOS 平台的AB包版本信息");
DecryptVersionFile(BuildTarget.iOS);
}
private static void EncryptAndCreateVersionFile(BuildTarget e_buildTarget)
{
/// ABPack版本信息保存路径
Debug.Log("平台是: " + e_buildTarget.ToString());
/// 加密文件存放路径
string sBasePath = Application.dataPath + @"/../AssetBundlesEncrypt/" + e_buildTarget.ToString() + @"/";
if (!Directory.Exists(sBasePath))
{
Directory.CreateDirectory(sBasePath);
}
StringBuilder obj_sb = new StringBuilder();
string sAllABPath = Application.dataPath + @"/../AssetBundles/" + e_buildTarget.ToString();
DirectoryInfo obj_folder = new DirectoryInfo(sAllABPath); // 获取输出路径的文件夹管理器
FileInfo[] arr_allFiles = obj_folder.GetFiles("*", SearchOption.AllDirectories); // 取得所有文件
foreach (FileInfo obj_item in arr_allFiles)
{
string sFilePath = obj_item.FullName; // 获取文件全名(包含路径 C:/ D:/ 全路径)
string sFileName = obj_item.Name; //
//Debug.Log("AB包 全路径 >>>>> " + sFilePath);
string sExName = sFilePath.Substring(sFilePath.LastIndexOf(".") + 1); // 得到后缀名
// 加密后的AB包存放路径
string sEncryptABOutPath = sBasePath + sFileName;
if (ABPackUtils.ABPackExName.IndexOf(sExName) > -1) // 匹配AB包的后缀名,取得对应的AB包文件
{
AESEncryptMgr.AESEncryptFile(sFilePath, sEncryptABOutPath);
}
else
{
// 不用加密的文件,拷贝的加密后的对应目录中
bool bIsReWrite = true; // true=覆盖已存在的同名文件, false 则反之
System.IO.File.Copy(sFilePath, sEncryptABOutPath, bIsReWrite);
}
string sABName = sFilePath.Substring(sFilePath.IndexOf("AssetBundles"));
string sFileVersion = MD5Mgr.GetABPackEncryptVersion(sEncryptABOutPath);
//Debug.Log("加密AB包的版本字符串是 >>>>> " + sFileVersion);
if (sFileVersion == null)
{
Debug.LogError("有文件没有拿到MD5:" + sABName);
continue;
}
string sFileSize = Mathf.Ceil(obj_item.Length / 1024f).ToString();//文件大小
sABName = sABName.Replace("\", "/");
string sABVersionStr = ABPackUtils.GetABPackVersionStr(sABName, sFileVersion, sFileSize); //每个文件的版本信息
obj_sb.AppendLine(sABVersionStr); // 写入版本文件要构建的内容中,按行写入
}
string sABVersionFile = sBasePath + ABPackUtils.sABVersionName;
//Debug.Log("AB包版本信息文件保存路径是 >>> " + sABVersionFile);
// 判断是否存在AB包的版本文件信息,存在则删除
IOUtils.CreatTextFile(sABVersionFile, obj_sb.ToString());
}
///
/// 解密AB包文件
///
///
private static void DecryptVersionFile(BuildTarget e_buildTarget)
{
/// ABPack版本信息保存路径
Debug.Log("平台是: " + e_buildTarget.ToString());
string sAllABFile = Application.dataPath + @"/../AssetBundlesEncrypt/" + e_buildTarget.ToString();
Debug.Log("加密版本的AB包文件路径是 >>> " + sAllABFile);
DirectoryInfo obj_folder = new DirectoryInfo(sAllABFile); // 获取输出路径的文件夹管理器
FileInfo[] arr_allFiles = obj_folder.GetFiles("*", SearchOption.AllDirectories); // 取得所有文件
/// 解密文件存放路径
string sBasePath = Application.dataPath + @"/../AssetBundlesDecrypt/" + e_buildTarget.ToString() + @"/";
if (!Directory.Exists(sBasePath))
{
Directory.CreateDirectory(sBasePath);
}
foreach (FileInfo obj_item in arr_allFiles)
{
string sFilePath = obj_item.FullName; // 获取文件全名(包含路径 C:/ D:/ 全路径)
Debug.Log("AB包 全路径 >>>>> " + sFilePath);
string sExName = sFilePath.Substring(sFilePath.LastIndexOf(".") + 1);//得到后缀名
// 加密后的AB包存放路径
string sDecryptABOutPath = sBasePath + obj_item.Name;
if (ABPackUtils.ABPackExName.IndexOf(sExName) > -1) // 匹配AB包的后缀名,取得对应的AB包文件
{
// 加密后的AB包存放路径
AESEncryptMgr.AESDecryptFile(sFilePath, sDecryptABOutPath);
}
else
{
// 不用加密的文件,拷贝的加密后的对应目录中
bool bIsReWrite = true; // true=覆盖已存在的同名文件, false 则反之
System.IO.File.Copy(sFilePath, sDecryptABOutPath, bIsReWrite);
}
}
}
}
AES 加密:其中 SingletonBase 只是实现单例的基类,提供对文件加密解密的工具类
using System; using System.IO; using System.Security.Cryptography; using UnityEngine; public class AESEncryptMgr : SingletonBase{ /// /// 缓存加密使用的加密相关数据对象 /// private static AesCryptoServiceProvider _obj_AesCSP; ////// 加密使用的 Key /// private const string _sKey = "hycai12365479807"; ////// 加密使用的 IV /// private const string _sIV = "987465132hycai07"; ////// 加密文件 /// /// /// /// /// ///public static int AESEncryptFile(string sFilePath, string sOutSavePath, string sKey = _sKey, string sIV = _sIV) { if (!File.Exists(sFilePath)) { Debug.Log("Error: 不存在AES加密的文件,sFilePath = null !!!"); return -1; } int nKeyCount = sKey.Length; int nIvCount = sIV.Length; if (nKeyCount < 7 || nKeyCount > 16 || nIvCount < 7 || nIvCount > 16) { Debug.Log("AES错误,秘钥sKey与sIV偏移量长度必须是8到16位"); return -1; } AesCryptoServiceProvider obj_aesCSP = CreateAES_CSP(sKey, sIV); ICryptoTransform obj_trans = obj_aesCSP.CreateEncryptor(); if (AESFileCoding(sFilePath, sOutSavePath, obj_trans) == -1) { return -1; } return 1; } /// /// 解密文件 /// /// /// /// /// ///public static int AESDecryptFile(string sFilePath, string sOutSavePath, string sKey = _sKey, string sIV = _sIV) { if (!File.Exists(sFilePath)) { Debug.Log("Error: 不存在AES解密的文件,sFilePath = null !!!"); return -1; } int nKeyCount = sKey.Length; int nIvCount = sIV.Length; if (nKeyCount < 7 || nKeyCount > 16 || nIvCount < 7 || nIvCount > 16) { Debug.Log("AES错误,秘钥sKey与sIV偏移量长度必须是8到16位"); return -1; } AesCryptoServiceProvider obj_aesCSP = CreateAES_CSP(sKey, sIV); ICryptoTransform obj_trans = obj_aesCSP.CreateDecryptor(); if (AESFileCoding(sFilePath, sOutSavePath, obj_trans) == -1) { return -1; } return 1; } /// /// 解密文件为Stream数据流 /// /// /// /// /// ///public static byte[] AESDecryptFileToStream(string sFilePath, string sKey = _sKey, string sIV = _sIV) { if (!File.Exists(sFilePath)) { Debug.Log("Error: 不存在AES解密的文件,sFilePath = null !!!"); return null; } int nKeyCount = sKey.Length; int nIvCount = sIV.Length; if (nKeyCount < 7 || nKeyCount > 16 || nIvCount < 7 || nIvCount > 16) { Debug.Log("AES错误,秘钥sKey与sIV偏移量长度必须是8到16位"); return null; } AesCryptoServiceProvider obj_aesCSP = CreateAES_CSP(sKey, sIV); ICryptoTransform obj_trans = obj_aesCSP.CreateDecryptor(); return AESFileCodingToStream(sFilePath, obj_trans); } /// /// 根据传入的 key 与 算法的向量 IV 创建 AES 的数据对象 /// /// /// ///public static AesCryptoServiceProvider CreateAES_CSP(string sKey, string sIV) { if (_obj_AesCSP != null) { return _obj_AesCSP; } byte[] arr_keySource = System.Text.Encoding.ASCII.GetBytes(sKey); byte[] arr_ivSource = System.Text.Encoding.ASCII.GetBytes(sIV); int nKeySounceLen = arr_keySource.Length; int nIVSounceLen = arr_ivSource.Length; byte[] arr_key = new byte[16]; byte[] arr_iv = new byte[16]; int nKeyTargetLen = arr_key.Length; int nIVTargetLen = arr_iv.Length; // 确保加密Key与IV是在16 byte 内 nKeyTargetLen = nKeySounceLen > nKeyTargetLen ? nKeyTargetLen : nKeySounceLen; nIVTargetLen = nIVSounceLen > nIVTargetLen ? nIVTargetLen : nIVSounceLen; System.Array.Copy(arr_keySource, arr_key, nKeyTargetLen); System.Array.Copy(arr_ivSource, arr_iv, nIVTargetLen); //string sShowKey = System.Text.Encoding.Default.GetString(arr_key); //Debug.Log("输出 >>>> sShowKey >>>>> " + sShowKey); //string sShowIV = System.Text.Encoding.Default.GetString(arr_iv); //Debug.Log("输出 >>>> sShowIV >>>>>> " + sShowIV); // 创建对称 AES 加密的数据模式对象 _obj_AesCSP = new AesCryptoServiceProvider() { Mode = CipherMode.CBC, // 设置对称算法的运算模式 Padding = PaddingMode.PKCS7, // 设置对称算法中使用的填充模式 KeySize = 128, // 设置密钥的大小(以位为单位) BlockSize = 128, // 设置加密操作的块大小(以位为单位) Key = arr_key, // 设置用于加密和解密的对称密钥 IV = arr_iv, // 设置对称算法的初始化向量 (IV) }; return _obj_AesCSP; } /// /// 将加密解密文件转换并保存到 sOutSavePath 路径下 /// /// /// /// ///private static int AESFileCoding(string sFilePath, string sOutSavePath, ICryptoTransform obj_trans) { try { FileStream obj_fileStream; // 创建文件流 byte[] arr_input = null; using (MemoryStream obj_memory = new MemoryStream()) { // 创建加密解密流,从 using (CryptoStream obj_cryptoStream = new CryptoStream(obj_memory, obj_trans, CryptoStreamMode.Write)) { obj_fileStream = File.OpenRead(sFilePath); // 从文件中读取数据到文件流中 // 将文件流转换成2进制数据 using (BinaryReader obj_binaryReader = new BinaryReader(obj_fileStream)) { arr_input = new byte[obj_fileStream.Length]; obj_binaryReader.Read(arr_input, 0, arr_input.Length); } // 解密操作 obj_cryptoStream.Write(arr_input, 0, arr_input.Length); // 释放解密操作 obj_cryptoStream.FlushFinalBlock(); // 写入到保存文件中 using (obj_fileStream = File.OpenWrite(sOutSavePath)) { obj_memory.WriteTo(obj_fileStream); } } } Debug.Log("AES文件转换成功..."); return 1; } catch(Exception obj_ex) { Debug.Log("Error AES 加密失败 " + obj_ex.Message); return -1; } } /// /// 将加密解密文件转换并保存到 sOutSavePath 路径下 /// /// /// /// ///private static byte[] AESFileCodingToStream(string sFilePath, ICryptoTransform obj_trans) { try { FileStream obj_fileStream; // 创建文件流 byte[] arr_input = null; byte[] arr_data = null; using (MemoryStream obj_memory = new MemoryStream()) { // 创建加密解密流,从文件中获取解密到其中 using (CryptoStream obj_cryptoStream = new CryptoStream(obj_memory, obj_trans, CryptoStreamMode.Write)) { obj_fileStream = File.OpenRead(sFilePath); // 从文件中读取数据到文件流中 // 将文件流转换成2进制数据 using (BinaryReader obj_binaryReader = new BinaryReader(obj_fileStream)) { arr_input = new byte[obj_fileStream.Length]; obj_binaryReader.Read(arr_input, 0, arr_input.Length); } obj_cryptoStream.Write(arr_input, 0, arr_input.Length); // 释放解密操作 obj_cryptoStream.FlushFinalBlock(); //obj_memory.WriteTo(obj_fileStream); arr_data = obj_memory.ToArray(); } Debug.Log("AES文件解密到Stream完成..."); return arr_data; } } catch (Exception obj_ex) { Debug.Log("Error AES 加密失败 " + obj_ex.Message); return null; } } }
- ABPack相关抽取的工具类方法
using UnityEngine; ////// AB包相关处理的工具 /// public static class ABPackUtils { ////// AB包的后缀扩展名 /// private static string _sABPackExName = ".ab"; public static string ABPackExName { get { return _sABPackExName; } } ////// 缓存AB包版本信息的文件名 /// private static string _sABVersionName = "ABVersionFile.txt"; public static string sABVersionName { get { return _sABVersionName; } } ////// 获取AB包的版本信息字符串 /// /// 包名(含AssetBundle路径) /// 版本信息的MD5值 /// 文件大小 ///public static string GetABPackVersionStr(string sABName, string sFileVersionMd5, string sFileSize) { return string.Format("{0} {1} {2}", sABName, sFileVersionMd5, sFileSize); } /// /// 获取不同平台AB包存放路径的字符串 /// ///public static string GetABPackPathPlatformStr() { RuntimePlatform obj_platform = Application.platform; string sPlatformStr = "/AssetBundles/"; if (obj_platform == RuntimePlatform.WindowsEditor || obj_platform == RuntimePlatform.WindowsPlayer) { sPlatformStr += "StandaloneWindows/"; } else if (obj_platform == RuntimePlatform.Android) { sPlatformStr += "Android/"; } else if (obj_platform == RuntimePlatform.IPhonePlayer) { sPlatformStr += "iOS/"; } return sPlatformStr; } }
- IO工具脚本
using System.IO;
public class IOUtils
{
///
/// 创建txt文件的方法
///
///
///
public static void CreatTextFile(string sFilePath, string sContent)
{
//文件存在则删除
if (File.Exists(sFilePath))
{
File.Delete(sFilePath);
}
using (FileStream obj_versionStream = File.Create(sFilePath))
{
using (StreamWriter obj_writer = new StreamWriter(obj_versionStream))
{
obj_writer.WriteLine(sContent);
}
}
}
///
/// 根据文件路径,创建其文件的文件夹路径
///
///
public static void CreateDirectroryOfFile(string sFilePath)
{
//Debug.Log($"根据文件创建对应的文件夹路径 文件 >>>> {sFilePath}");
if (!string.IsNullOrEmpty(sFilePath))
{
string sDirName = Path.GetDirectoryName(sFilePath);
if (!Directory.Exists(sDirName))
{
//Debug.Log($"不存在路径 {sDirName},");
Directory.CreateDirectory(sDirName);
}
//else
//{
// Debug.Log($"已存在路径 >>>> {sDirName},");
//}
}
}
}
- 打包 AB 包
打包AB包,请看另外一篇文章 AssetBundle - 工具与打包操作 https://blog.csdn.net/hycai_007/article/details/121380465
测试AB包,自行选择资源
如果说后缀名不为"*.ab",注:记得修改 ABPackUtils 的对应属性
AB包生产成功后会在对应项目的文件夹下生成 AssetBundles 文件
然后对生成的AssetBundles进行加密,点击对对应平台的AB包进行加密
创建成功后会在Console窗口有对应日志输出,并在项目文件夹中生成 AssetBundlesEncrypt 文件夹,其内部就是AB包加密后的文件
从项目的 NetBox Server 文件夹中取得安装包进行安装,安装后,创建我们自己的挂载网址
在任意创建文件夹,在文件夹中创建文件 main.box,并写入如下内容
在当前文件夹中创建上述图中的 “Web” 路径,在其添加 AssetBundles 存放路径与 index.asp 的测试网页
index.asp 文件内容则如下
<%="TEST #@@#@#@#@#@ V@#@$@@ SSSSS GGGGG "%>
- 测试环境是否搭建成功
其电脑右下角就会出现对应icon
- 热更新控制器:从服务端获取 ABVersionFile.txt 的文件数据与前端缓存的 ABVersionFile.txt 数据进行比对,确定所需更新的AB包资源,创建 _nMaxDownloader 最大数量的下载器进行下载AB包,直至所有资源下载完成。进入下一阶段 HotUpdateEnd 方法
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking; using System.IO; using System.Text; public class HotUpdateMgr : MonoSingletonBase{ /// /// _sBaseUrl下载网址 /// #if UNITY_EDITOR || UNITY_STANDALONE_WIN public static string _sBaseUrl = "http://127.0.0.1:5858"; #elif UNITY_ANDROID public static string _sBaseUrl = "http://192.168.255.10:5858"; #elif UNITY_IPHONE public static string _sBaseUrl = "http://192.168.255.10:5858"; #endif private string _sABVersionName = ""; ////// 本地版本信息缓存路径 /// private string _sVersionLocalFilePath = ""; ////// 同时下载的最大数量 /// private int _nMaxDownloader = 5; ////// 当前需要下载的AB包数据 /// List_list_allNeedABPack = new List (); /// /// 所需下载资源总大小 /// private float _nDownloadTotalSize = 0; ////// 当前已下载资源的大小 /// private float _nCurDownloadedSize = 0; ////// AB包下载器 /// private List_list_allABDownloader = new List (); /// /// 客户端的AB版本数据 /// private Dictionary_dict_clientABInfoList = null; protected override void Awake() { string sPlatformStr = ABPackUtils.GetABPackPathPlatformStr(); _sABVersionName = sPlatformStr + ABPackUtils.sABVersionName; _sVersionLocalFilePath = Application.persistentDataPath + _sABVersionName; IOUtils.CreateDirectroryOfFile(_sVersionLocalFilePath); } /// /// 开始热更 /// public void StartHotUpdate() { Debug.Log("开始热更 >>>>>> "); StartCoroutine(DownloadAllABPackVersion()); } ////// 解析版本文件,返回一个文件列表 /// /// ///public Dictionary ConvertToAllABPackDesc(string sContent) { Dictionary dict_allABPackDesc = new Dictionary (); string[] arrLines = sContent.Split('n');//用回车 字符 n 分割每一行 foreach (string item in arrLines) { string[] arrData = item.Split(' ');//用空格分割每行数据的三个类型 if (arrData.Length == 3) { ABPackInfo obj_ABPackData = new ABPackInfo(); obj_ABPackData.sABName = arrData[0]; // 名称即路径 obj_ABPackData.sMd5 = arrData[1]; // md5值 obj_ABPackData.nSize = int.Parse(arrData[2]); // AB包大小 //Debug.Log(string.Format("解析的路径:{0}n解析的MD5:{1}n解析的文件大小KB:{2}", obj_ABPackData.sABName, obj_ABPackData.sMd5, obj_ABPackData.nSize)); dict_allABPackDesc.Add(obj_ABPackData.sABName, obj_ABPackData); } } return dict_allABPackDesc; } /// /// 获取服务端的AB包版本信息 /// ///IEnumerator DownloadAllABPackVersion() { string sVersionUrl = _sBaseUrl + @"/" + _sABVersionName; //Debug.Log("下载版本数据路径:" + sVersionUrl); using (UnityWebRequest uObj_versionWeb = UnityWebRequest.Get(sVersionUrl)) { yield return uObj_versionWeb.SendWebRequest(); // 等待资源下载 if (uObj_versionWeb.isNetworkError || uObj_versionWeb.isHttpError) { Debug.LogError("获取版本AB包数据错误: " + uObj_versionWeb.error); yield break; } else { string sVersionData = uObj_versionWeb.downloadHandler.text; //Debug.Log("成功获取到版本相关数据 >>>> n" + sVersionData); CheckNeedDownloadABPack(sVersionData); } } } /// /// 检测需要下载 /// /// void CheckNeedDownloadABPack(string sServerVersionData) { //Debug.Log("运行平台:" + Application.platform); //Debug.Log("本地版本文件路径是:" + _sVersionLocalFilePath); Dictionarydict_serverDownList = ConvertToAllABPackDesc(sServerVersionData); // 服务端获取的资源下载列表 if (File.Exists(_sVersionLocalFilePath)) { //Debug.Log("存在本地,对比服务器版本信息"); string sClientVersionData = File.ReadAllText(_sVersionLocalFilePath); // 本地版本信息 _dict_clientABInfoList = ConvertToAllABPackDesc(sClientVersionData); // 客户端本地缓存的资源下载列表 //遍历服务器文件 foreach (ABPackInfo obj_itemData in dict_serverDownList.Values) { // 存在对应已下载文件,对比Md5值是否一致 if (_dict_clientABInfoList.ContainsKey(obj_itemData.sABName)) { // md5值不一致,则更新文件 if (_dict_clientABInfoList[obj_itemData.sABName].sMd5 != obj_itemData.sMd5) { _list_allNeedABPack.Add(obj_itemData); _nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize; //Debug.Log("MD5 值不一样,资源存在变更,增加文件 >>>>> " + obj_itemData.sABName); } } else { _list_allNeedABPack.Add(obj_itemData); _nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize; } } } else // 如果说不存在本地缓存,那就直接下载所有的AB包 { foreach (ABPackInfo obj_itemData in dict_serverDownList.Values) { _list_allNeedABPack.Add(obj_itemData); _nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize; //Debug.Log("所需下载文件 >>>>> " + obj_itemData.sABName); } } StartDownloadAllABPack(); } /// /// 开始下载所有所需下载的AB包资源 /// /// void StartDownloadAllABPack() { int nMaxCount = _list_allNeedABPack.Count; if (nMaxCount <= 0) { HotUpdateEnd(); return; } int nNeedCount = Mathf.Min(nMaxCount, _nMaxDownloader); for (int i = 0; i < nNeedCount; i++) { ABPackInfo obj_ABPackDesc = _list_allNeedABPack[0]; ABDownloader obj_downloader = new ABDownloader(); _list_allABDownloader.Add(obj_downloader); StartCoroutine(obj_downloader.DownloadABPack(obj_ABPackDesc)); _list_allNeedABPack.RemoveAt(0); } } ////// 切换下载下一个AB包 /// /// 需要切换的下载器 public void ChangeDownloadNextABPack(ABDownloader obj_ABDownloader) { //Debug.Log("切换下载下一个 AB 包"); _nCurDownloadedSize += obj_ABDownloader.GetDownloadResSize(); if (_list_allNeedABPack.Count > 0) // 还存在需要下载的资源,下载器切换资源,继续下载 { StartCoroutine(obj_ABDownloader.DownloadABPack(_list_allNeedABPack[0])); _list_allNeedABPack.RemoveAt(0); } else { bool bIsDownloadSuc = true; // 资源是否全部下载完成 foreach(ABDownloader obj_downloader in _list_allABDownloader) { if(obj_downloader.bIsDownloading) // 存在一个下载中,即表示当前还有未下载完成的部分 { bIsDownloadSuc = false; break; } } if (bIsDownloadSuc) // 已完成全部下载 { HotUpdateEnd(); } } } ////// 更新本地缓存的AB包版本数据 /// /// public void UpdateClientABInfo(ABPackInfo obj_ABPackDecs) { if (_dict_clientABInfoList == null) { _dict_clientABInfoList = new Dictionary(); } _dict_clientABInfoList[obj_ABPackDecs.sABName] = obj_ABPackDecs; StringBuilder obj_sb = new StringBuilder(); foreach (ABPackInfo obj_temp in _dict_clientABInfoList.Values) { obj_sb.AppendLine(ABPackUtils.GetABPackVersionStr(obj_temp.sABName, obj_temp.sMd5, obj_temp.nSize.ToString())); } IOUtils.CreatTextFile(_sVersionLocalFilePath, obj_sb.ToString()); } /// /// 热更新结束,进入下一个阶段 /// private void HotUpdateEnd() { // TODO 进入下一个阶段 Debug.Log("热更新: 已完成所有的AB包下载, 进入下一个阶段 TODO"); HotUpdateTest.GetInstance().RunLua(); HotUpdateTest.GetInstance().InitShow(); } }
-AB包资源下载器 : AB包下载器,进行AB包资源下载,然后对AB包进行保存在本地,将下载完成的版本信息写入到本地的 ABVersionFile.txt 文件中记录
using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking; ////// AB包下载器 /// public class ABDownloader { ////// 当前下载器下载的AB包数据 /// private ABPackInfo _obj_ABDecs; ////// 是否资源下载中 /// private bool _bIsDownloading = false; public bool bIsDownloading { get => _bIsDownloading; } ////// 获取当前下载资源的大小 /// ///public float GetDownloadResSize() { if (_obj_ABDecs != null) { return _obj_ABDecs.nSize; } return 0; } /// /// 下载AB包 /// /// AB包资源的说明,包含文件名,大小,md5值 ///public IEnumerator DownloadABPack(ABPackInfo obj_ABDecs) { _bIsDownloading = true; string sDownloadUrl = HotUpdateMgr._sBaseUrl + @"/" + obj_ABDecs.sABName; Debug.Log("下载资源:" + sDownloadUrl); UnityWebRequest uObj_web = UnityWebRequest.Get(sDownloadUrl); yield return uObj_web.SendWebRequest(); if (uObj_web.isNetworkError || uObj_web.isHttpError) { Debug.Log("获取AB包 " + sDownloadUrl + " 错误: " + uObj_web.error); yield break; } else { string sABPath = Application.persistentDataPath + @"/" + obj_ABDecs.sABName; Debug.Log("AB包 保存本地路径是:" + sABPath); IOUtils.CreateDirectroryOfFile(sABPath); if (!File.Exists(sABPath)) { File.Create(sABPath).Dispose(); } File.WriteAllBytes(sABPath, uObj_web.downloadHandler.data); // 下载完成后,更新本地版本数据 HotUpdateMgr.GetInstance().UpdateClientABInfo(obj_ABDecs); _bIsDownloading = false; HotUpdateMgr.GetInstance().ChangeDownloadNextABPack(this); } } }
- AB包解压加载资源管理器 :从AB包中取得资源使用,注:_bIsEncrypt 属性,可控制是否使用是读取加密的AssetBundler的
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class AssetBundleMgr : MonoSingletonBase{ /// /// 是否使用本地文件 /// public bool _bIsUseLocalFile = false; ////// 是否使用加密 /// public bool _bIsEncrypt = true; ////// AB包的主包对象 /// private AssetBundle _obj_mainABPack = null; ////// AB包主包的相关依赖信息对象 /// private AssetBundleManifest _obj_mainManifest = null; ////// AB包不能重复加载,重复加载会报错 /// 用字典的方式来储存,已经加载过的AB包 /// ///AB包路径 ///AB包对象 ///private Dictionary _dict_ABObj = new Dictionary (); /// /// AB包存放路径 /// ///private string _sPathUrl; /// /// 存放AB包的主文件夹,主包名 /// ///private string _sMainABName { get { #if UNITY_IOS return "IOS"; #elif UNITY_ANDROID return "Android"; #else return "StandaloneWindows"; #endif } } private void Awake() { string sPlatformStr = ABPackUtils.GetABPackPathPlatformStr(); if (_bIsUseLocalFile) // 是否从本地中读取 { _sPathUrl = Application.streamingAssetsPath + sPlatformStr; } else { _sPathUrl = Application.persistentDataPath + sPlatformStr; } } /// /// 加载AB包 /// /// ///private AssetBundle LoadPack(string sABName) { bool bIsEncryptFile = false; if (sABName.Contains(ABPackUtils.ABPackExName)) { bIsEncryptFile = true; } if (_bIsEncrypt && bIsEncryptFile) { //Debug.Log("加载AB包文件路径 To Stream >>>> " + _sPathUrl + sABName); byte[] arr_abData = AESEncryptMgr.AESDecryptFileToStream(_sPathUrl + sABName); if (arr_abData != null) { //Debug.Log("AB包解密, 加载流 成功 >>>>>>"); return AssetBundle.LoadFromMemory(arr_abData); } Debug.LogError("Error AB包解密, 加载流 失败 >>>>>>"); return null; } else { return AssetBundle.LoadFromFile(_sPathUrl + sABName); } } #region 同时加载AB包与资源 /// /// 加载某个AB包 /// /// ///public bool LoadABPack(string sABName) { if (_obj_mainABPack == null) { _obj_mainABPack = LoadPack(_sMainABName); if (_obj_mainABPack == null) return false; _obj_mainManifest = _obj_mainABPack.LoadAsset ("AssetBundleManifest"); } // 加载sABName的所有依赖包 AssetBundle obj_relyOnAB = null; string[] arr_sManifest = _obj_mainManifest.GetAllDependencies(sABName); for (int i = 0; i < arr_sManifest.Length; i++) { // 未加载过的才进行加载,已加载的AB包,不能重复加载 if(!_dict_ABObj.ContainsKey(arr_sManifest[i])) { obj_relyOnAB = LoadPack(arr_sManifest[i]); if (obj_relyOnAB == null) return false; _dict_ABObj.Add(arr_sManifest[i], obj_relyOnAB); } } AssetBundle obj_curLoadAB = null; if(!_dict_ABObj.ContainsKey(sABName)) { obj_curLoadAB = LoadPack(sABName); if (obj_curLoadAB == null) return false; _dict_ABObj.Add(sABName, obj_curLoadAB); } return true; } /// /// 同步加载AB包中的资源 /// /// AB包的包名 /// AB包的资源名 ///返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null public Object LoadABPackRes(string sABName, string sResName) { bool bIsLoadSuc = LoadABPack(sABName); if (!bIsLoadSuc) return null; if(_dict_ABObj.ContainsKey(sABName)) { Object uObj_cur = _dict_ABObj[sABName].LoadAsset(sResName); if(uObj_cur is GameObject) { return Instantiate(uObj_cur); } else { return uObj_cur; } } return null; } ////// 同步加载AB包中的资源, 重载增加转换的参数类型 /// /// AB包的包名 /// AB包的资源名 /// 加载后的资源转换成的资源类型 ///返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null public Object LoadABPackRes(string sABName, string sResName, System.Type eToType) { bool bIsLoadSuc = LoadABPack(sABName); if (!bIsLoadSuc) return null; if (_dict_ABObj.ContainsKey(sABName)) { Object uObj_cur = _dict_ABObj[sABName].LoadAsset(sResName, eToType); if(uObj_cur is GameObject) { return Instantiate(uObj_cur); } else { return uObj_cur; } } return null; } ////// 同步加载AB包中的资源,重载的泛型方法 /// /// AB包的包名 /// AB包的资源名 ///返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null public T LoadABPackRes(string sABName, string sResName) where T: Object { bool bIsLoadSuc = LoadABPack(sABName); if (!bIsLoadSuc) return null; if (_dict_ABObj.ContainsKey(sABName)) { T obj_cur = _dict_ABObj[sABName].LoadAsset (sResName); if(obj_cur is GameObject) { return Instantiate(obj_cur); } else { return obj_cur; } } return null; } #endregion #region 同步加载AB包,异步加载资源 /// /// 同步加载AB包,异步加载资源 /// /// AB包的包名 /// AB包的资源名 /// 异步加载完成后的回调方法 public void LoadResAsync(string sABName, string sResName, UnityAction
HotUpdateTest 热更新测试脚本
using UnityEngine; public class HotUpdateTest : MonoSingletonBase{ void Start() { HotUpdateMgr.GetInstance().StartHotUpdate(); } public void RunLua() { LuaInterpreter.GetInstance().RequireLua("HelloWorld"); LuaInterpreter.GetInstance().RequireLua("Test"); } public void InitShow() { GameObject obj_cube = AssetBundleMgr.GetInstance().LoadABPackRes ("mode.ab", "Cube"); Debug.Log("实例化 Cube"); } }
准备就绪后运行可查看效果
本地的Lua测试代码与Cube材质
热更新执行后的实际效果是
Unity版本 2019.4
项目Git:https://github.com/hycai007/Learn_HotUpdate



