unity下HybridCLR热更新简易框架

简易打AB包工具

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using HybridCLR.Editor;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using System.Text;
using System.Security.Cryptography;

public class ABTool : Editor
{
    static string dllpath = Application.dataPath + "/../" + HybridCLRSettings.Instance.hotUpdateDllCompileOutputRootDir;
    static string aotpath = Application.dataPath + "/../" + HybridCLRSettings.Instance.strippedAOTDllOutputRootDir;

    public static List<string> AOTMetaAssemblyNames { get; } = new List<string>()
    {
        "mscorlib.dll",
        "System.dll",
        "System.Core.dll",
    };

    [MenuItem("ABTool/生成AB包")]
    public static void CreatAB()
    {
        BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
        //找到对应目录
        DirectoryInfo direction = new DirectoryInfo(dllpath + "/" + target);
        //获取目录下所有文件
        FileInfo[] files = direction.GetFiles("*", SearchOption.AllDirectories);
        //把所有dll文件拷贝到s目录下
        foreach (var item in files)
        {
            if (item.Name.EndsWith(".dll"))
            {
                string dllPath = $"{dllpath + "/" + target}/{item.Name}";
                string dllBytesPath = $"{Application.streamingAssetsPath}/{item.Name}.bytes";
                File.Copy(dllPath, dllBytesPath, true);
                Debug.Log($"hotfix dll {dllPath} -> {dllBytesPath}");
            }
        }
        //预制体
        List<AssetBundleBuild> abs = new List<AssetBundleBuild>();
        //找到预制体目录下的所有目录
        AddABPath(abs, $"{Application.dataPath}/Prefabs/");
        AddABPath(abs, $"{Application.dataPath}/Resources/");
        //构建AB包放到S目录下
        BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, abs.ToArray(), BuildAssetBundleOptions.None, target);
        //加载补充元数据
        CopyAOTAssembliesToStreamingAssets();

        AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);

    }

    public static void CopyAOTAssembliesToStreamingAssets()
    {
        var target = EditorUserBuildSettings.activeBuildTarget;
        string aotAssembliesSrcDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
        string aotAssembliesDstDir = Application.streamingAssetsPath;

        foreach (var dll in AOTMetaAssemblyNames)
        {
            string srcDllPath = $"{aotAssembliesSrcDir}/{dll}";
            if (!File.Exists(srcDllPath))
            {
                Debug.LogError($"ab中添加AOT补充元数据dll:{srcDllPath} 时发生错误,文件不存在。裁剪后的AOT dll在BuildPlayer时才能生成,因此需要你先构建一次游戏App后再打包。");
                continue;
            }
            string dllBytesPath = $"{aotAssembliesDstDir}/{dll}.bytes";
            File.Copy(srcDllPath, dllBytesPath, true);
            Debug.Log($"[CopyAOTAssembliesToStreamingAssets] copy AOT dll {srcDllPath} -> {dllBytesPath}");
        }
    }

    public static void AddABPath(List<AssetBundleBuild> abs, string path)
    {
        DirectoryInfo directionInfo = new DirectoryInfo(path);
        DirectoryInfo[] directions = directionInfo.GetDirectories();
        //每个目录打一个AB包
        for (int i = 0; i < directions.Length; i++)
        {
            List<string> prefabAssets = new List<string>();
            Debug.Log("打包文件夹:" + directions[i].Name);
            FileInfo[] fileinfos = directions[i].GetFiles("*", SearchOption.AllDirectories);
            Debug.Log("目录下文件个数:" + fileinfos.Length);
            foreach (var item in fileinfos)
            {
                if (item.Name.EndsWith(".meta"))
                {
                    continue;
                }
                Debug.Log("打包文件:" + item.Name);
                prefabAssets.Add(item.FullName.Replace("\\", "/"));
                AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
            }

            AssetBundleBuild PrefabsAb = new AssetBundleBuild
            {
                assetBundleName = directions[i].Name,
                assetNames = prefabAssets.Select(s => ToReleateAssetPath(s)).ToArray(),
            };
            abs.Add(PrefabsAb);
        }
    }
    public static string ToReleateAssetPath(string s)
    {
        string path = s.Substring(s.IndexOf("Assets/"));
        Debug.Log(path);
        return path;
    }

    [MenuItem("ABTool/生成version")]
    public static void MakeVersion()
    {
        string json = AssetDatabase.LoadAssetAtPath<TextAsset>("Assets/Resources/Config/Config.json").text;
        Config config = JsonConvert.DeserializeObject<Config>(json);
        List<VersionData> versionDatas = new List<VersionData>();
        //第一条是假数据,用来判断是否需要热更新
        VersionData version = new VersionData();
        //判断使用测试版还是正式版热更地址
        config.upid++;
        version.abName = config.isBate ? config.hotPath_Bate : config.hotPath;
        version.len = config.upid;//更新号
        version.md5 = config.version;//版本号
        versionDatas.Add(version);
        json = JsonConvert.SerializeObject(config);
        File.WriteAllText(Application.dataPath +"/Resources/Config/Config.json", json);

        //找到S目录下所有文件
        string[] files = Directory.GetFiles(Application.streamingAssetsPath, ".", SearchOption.AllDirectories);

        foreach (var item in files)
        {
            //取后缀名
            string extensionName = Path.GetExtension(item);
            //排除.meta和.manifest文件
            if (extensionName.Contains("meta") || extensionName.Contains("manifest")) continue;
            Debug.Log(item);
            string abName = item.Replace(Application.dataPath, "Assets");
            abName = abName.Replace("\\", "/");
            string assetBundleName = "";
            assetBundleName = abName.Replace("Assets/StreamingAssets/", string.Empty);

            VersionData versionData = new VersionData();

            versionData.abName = assetBundleName;
            versionData.len = File.ReadAllBytes(item).Length;
            versionData.md5 = FileMD5(item);
            versionDatas.Add(versionData);

        }
        //写入文件
        string versionInfo = JsonConvert.SerializeObject(versionDatas);
        File.WriteAllText(Application.streamingAssetsPath + "/version.txt", versionInfo, Encoding.UTF8);
        AssetDatabase.Refresh();
        Debug.Log("version.txt创建成功");
    }

    static string FileMD5(string filepath)
    {
        FileStream fileStream = new FileStream(filepath, FileMode.Open);
        MD5 mD5 = new MD5CryptoServiceProvider();
        byte[] bytes = mD5.ComputeHash(fileStream);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.Length; i++)
        {
            sb.Append(bytes[i].ToString("x2"));
        }

        return sb.ToString();
    }
}

简易AB包管理器

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

public class ABData
{
    public string abname;
    public AssetBundle ab;
    public int num;
}

public class ABManager : Singleton<ABManager>
{
    public Dictionary<string, List<string>> reflist = new Dictionary<string, List<string>>();
    public string SPath = Main.config.isEditor ? Application.streamingAssetsPath + "/" : Application.persistentDataPath + "/AB/";
    public Dictionary<string, byte[]> assetDatas = new Dictionary<string, byte[]>();
    public Dictionary<string, ABData> abDic = new Dictionary<string, ABData>();
    Assembly ass;

    public byte[] GetAssetData(string dllName)
    {
        return assetDatas[dllName];
    }

    public AssetBundle LoadAB(string dllName)
    {
        return AssetBundle.LoadFromMemory(GetAssetData(dllName));
    }
    public T Load<T>(string refname, string abName, string assetName) where T : UnityEngine.Object
    {
        AssetBundle ab;
        if (abDic.ContainsKey(abName))
        {
            ab = abDic[abName].ab;
            abDic[abName].num++;
        }
        else
        {
            ab = AssetBundle.LoadFromFile(SPath + abName);
            ABData data = new ABData();
            data.abname = abName;
            data.ab = ab;
            data.num = 1;
            abDic.Add(abName, data);
        }
        if (ab != null)
        {
            if (!reflist.ContainsKey(refname))
            {
                reflist.Add(refname, new List<string>());
            }
            reflist[refname].Add(abName);
        }
        return ab.LoadAsset<T>(assetName);
    }
    public GameObject Load(string abName, string assetName)
    {
        AssetBundle ab;
        if (abDic.ContainsKey(abName))
        {
            ab = abDic[abName].ab;
            abDic[abName].num++;
        }
        else
        {
            ab = AssetBundle.LoadFromFile(SPath + abName);
            ABData data = new ABData();
            data.abname = abName;
            data.ab = ab;
            data.num = 1;
            abDic.Add(abName, data);
        }
        if (ab != null)
        {
            if (!reflist.ContainsKey(assetName))
            {
                reflist.Add(assetName, new List<string>());
            }
            reflist[assetName].Add(abName);
        }
        return ab.LoadAsset<GameObject>(assetName);
    }
    public void InitScript()
    {
        ass = Assembly.Load(GetAssetData("Script.dll.bytes"));
    }
    public Type LoadScript(string assetName)
    {
        if (ass == null)
        {
            ass = Assembly.Load(GetAssetData("Script.dll.bytes"));
        }
        return ass.GetType(assetName);
    }
    public void Release(string refname)
    {
        if (reflist.ContainsKey(refname))
        {
            foreach (var abName in reflist[refname])
            {
                if (abDic.ContainsKey(abName))
                {
                    abDic[abName].num--;
                    if (abDic[abName].num <= 0)
                    {
                        ScheduleOnce.Ins.AddSchedule(9, (obj) =>
                        {
                            object[] arr = obj as object[];
                            ABData data = arr[0] as ABData;
                            if (data.num <= 0)
                            {
                                data.ab.Unload(true);
                                abDic.Remove(abName);
                                Debug.Log("释放成功");
                            }
                        }, abDic[abName]);
                    }
                }
            }
            reflist.Remove(refname);
        }
    }
}

热更脚本加载器

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.Networking;

public class LoadDll : MonoBehaviour
{
    string SPath;
    string PPath;
    void Start()
    {
        SPath = Application.streamingAssetsPath;
        PPath = Application.persistentDataPath + "/AB";
        StartCoroutine(this.DownLoadAssets( ()=>
        {
#if !UNITY_EDITOR
            ABManager.Ins.InitScript();
#endif
            Main.main.gameObject.AddComponent(ABManager.Ins.LoadScript("Language"));
            Instantiate(ABManager.Ins.Load("Login", "Notice"), Main.GetUILayout(UILayout.Tips));
            Instantiate(ABManager.Ins.Load("Login", "Login"), Main.GetUILayout(UILayout.UIScene));
        }));
    }

    IEnumerator DownLoadAssets(Action action)
    {
        //找到对应目录
        DirectoryInfo direction = new DirectoryInfo(Main.config.isEditor ? SPath : PPath);
        //获取目录下所有文件
        FileInfo[] files = direction.GetFiles("*", SearchOption.AllDirectories);
        List<string> AOTMetaAssemblyNames = new List<string>();
        //加载所有dll文件
        foreach (var item in files)
        {
            Debug.Log("文件:" + item.Name);
            if (item.Name.EndsWith(".bytes"))
            {
                Debug.Log("开始加载:" + item.Name);
                string dllPath = item.FullName.Replace("\\", "/");
                UnityWebRequest www = UnityWebRequest.Get(dllPath);
                yield return www.SendWebRequest();
                if (www.result != UnityWebRequest.Result.Success)
                {
                    Debug.Log(www.error);
                }
                else
                {
                    byte[] data = www.downloadHandler.data;
                    ABManager.Ins.assetDatas.Add(item.Name, data);
                    Debug.Log("加载成功:" + item.Name);
                    AOTMetaAssemblyNames.Add(item.Name);
                }
            }
        }
        LoadMetadataForAOTAssemblies(AOTMetaAssemblyNames);
        action();
    }
    private static void LoadMetadataForAOTAssemblies(List<string> AOTMetaAssemblyNames)
    {
        // 不限补充元数据dll文件的读取方式,你可以从ab、StreamingAssets、或者裸文件下载等办法获得
        HomologousImageMode mode = HomologousImageMode.SuperSet;
        foreach (var aotDllName in AOTMetaAssemblyNames)
        {
            Debug.Log(aotDllName);
            byte[] dllBytes = ABManager.Ins.GetAssetData(aotDllName); // 获得某个aot dll文件所有字节
            Debug.Log(dllBytes);
            // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码
            LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);
            Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}");
        }
    }
}

版本数据

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

public class VersionData
{
	public string abName;
	public int len;
	public string md5;
}

简易热更框架

using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class HotUpDate : MonoBehaviour
{
    public Scrollbar scrollbar;
    public Text text;
	// Start is called before the first frame update

	string streamingAssetsPath;
	string persistentDataPath;
	// Start is called before the first frame update
	void Start()
	{
		Debug.Log("开始热更新");
		text.text = "开始热更新";
		streamingAssetsPath = Application.streamingAssetsPath + "/";
		//p目录在unity运行时就会自动创建,咱们要在下面新建一个目录使用
		persistentDataPath = Application.persistentDataPath + "/AB/";
		//没有p目录,创建p目录,并把s目录下的拷贝到p目录
		//有p目录,直接热更新
		//拷贝跟热更都是使用协程异步完成的
		if (!Directory.Exists(persistentDataPath))
		{
			Directory.CreateDirectory(persistentDataPath);
			// -- todo  S 拷贝到P
			StartCoroutine(Copy());
		}
		else
		{
			//  todo  更新
			StartCoroutine(CheckUpdate());
		}
	}
    public void Init()
    {
		GameObject.Find("Main").AddComponent<LoadDll>();
		Destroy(gameObject);
    }
    IEnumerator Copy()
	{
		Debug.Log("S 拷贝到P");
		//找到版本文件地址
		string localVersion = streamingAssetsPath + "version.txt";
		//读取版本文件
		string localVersionContent = File.ReadAllText(localVersion);
		//解析版本文件
		List<VersionData> localVersionDataList = JsonConvert.DeserializeObject<List<VersionData>>(localVersionContent);
		for (int i = 1; i < localVersionDataList.Count; i++)
		{
			text.text = "验证本地文件";
			//更新进度条
			scrollbar.size = (float)i / (float)localVersionDataList.Count;
			//ab包地址
			string tempS = streamingAssetsPath + localVersionDataList[i].abName;
			//获取文件名
			string fileName = Path.GetFileName(tempS);
			//获取文件夹名称
			string dir = Path.GetDirectoryName(tempS) + "/";
			dir = dir.Replace("\\", "/");//Assets/StreamingAssets/ABTest/xxxx/xxxxx
										 //把对应的s目录替换成p目录
			dir = dir.Replace(streamingAssetsPath, persistentDataPath);
			//创建对应目录
			if (!Directory.Exists(dir))
			{
				Directory.CreateDirectory(dir);
			}
			//拷贝内容
			File.Copy(tempS, dir + fileName);
		}
		//p目录下写入版本文件
		File.WriteAllText(persistentDataPath + "version.txt", localVersionContent);

		yield return null;
		//从p目录下开始热更新
		StartCoroutine(CheckUpdate());
	}
	IEnumerator CheckUpdate()
	{
		Debug.Log("检查更新");
		//检查p目录下的版本文件
		string localVersion = persistentDataPath + "version.txt";
		string localVersionContent = File.ReadAllText(localVersion);
		List<VersionData> localVersionDataList = JsonConvert.DeserializeObject<List<VersionData>>(localVersionContent);
		//创建版本对应字典
		Dictionary<string, VersionData> dicVersion = new Dictionary<string, VersionData>();
		for (int i = 1; i < localVersionDataList.Count; i++)
		{
			text.text = "检查更新";
			//更新进度条
			scrollbar.size = (float)i / (float)localVersionDataList.Count;
			//把p目录下的版本信息全部存入字典
			dicVersion.Add(localVersionDataList[i].abName, localVersionDataList[i]);
		}
		//获取热更新服务器上的版本文件地址
		string serverUrl = localVersionDataList[0].abName + "version.txt";
		Debug.Log(serverUrl);
		//创建用于接收版本文件的字符串
		string serverVesionContent = string.Empty;
		Debug.Log(serverVesionContent);
		//从热更新服务器上获取版本文件
		UnityWebRequest unityWebRequest = UnityWebRequest.Get(serverUrl);
		yield return unityWebRequest.SendWebRequest();
		//判断是否获取成功
		if (unityWebRequest.result == UnityWebRequest.Result.ConnectionError)
		{
			text.text = "无法连接更新服务器";
			Debug.Log(unityWebRequest.error);
			yield break;
		}
		else
		{
			//获取成功后,给字符串赋值
			serverVesionContent = unityWebRequest.downloadHandler.text;
			//Debug.Log(serverVesionContent);
			//把热更版本文件写入p目录
			File.WriteAllText(persistentDataPath + "serverVesion.txt", serverVesionContent);
		}
		//Debug.Log(persistentDataPath);
		//重新读取热更版本文件
		string serverVesion = File.ReadAllText(persistentDataPath + "serverVesion.txt");
		//解析热更版本文件
		List<VersionData> serverVersionDataList = JsonConvert.DeserializeObject<List<VersionData>>(serverVesion);

		//创建一个需要更新的文件集合
		List<VersionData> updateList = new List<VersionData>();
		//本地文件更新号小于网络文件更新号,说明需要更新
		if (localVersionDataList[0].len < serverVersionDataList[0].len) //需要更新
		{
			// 不一样的要更新?????md5 不一样
			//新增的文件
			for (int i = 1; i < serverVersionDataList.Count; i++)
			{
				text.text = "查找需要更新的资源";
				scrollbar.size = (float)i / (float)serverVersionDataList.Count;
				Debug.Log("资源验证" + i);
				//判断本地是否有相同名称的资源
				if (dicVersion.ContainsKey(serverVersionDataList[i].abName))
				{
					//有该资源,则通过MD5码判断该资源是否修改过,修改过则加入更新集合
					if (dicVersion[serverVersionDataList[i].abName].md5 != serverVersionDataList[i].md5)
					{
						updateList.Add(serverVersionDataList[i]);
					}
				}
				else
				{
					//没有该资源,直接加入更新集合
					updateList.Add(serverVersionDataList[i]);
				}

			}
		}
		else
		{
			Debug.Log("localVersionDataList[0].len " + localVersionDataList[0].len);
			Debug.Log("serverVersionDataList[0].len " + serverVersionDataList[0].len);
			Debug.Log("不更新");
			//初始化游戏
			Init();
			//到此结束,停止该协程
			yield break;
		}
		//循环更新集合
		for (int i = 0; i < updateList.Count; i++)
		{
			text.text = "资源下载中";
			scrollbar.size = (float)i / (float)updateList.Count;
			Debug.Log("资源下载中" + i);
			//下载0是网络地址 + 资源i的名字
			UnityWebRequest unityWebRequest1 = UnityWebRequest.Get(localVersionDataList[0].abName + updateList[i].abName);
			yield return unityWebRequest1.SendWebRequest();
			//判断下载是否成功
			if (unityWebRequest.result == UnityWebRequest.Result.ConnectionError)
			{
				Debug.Log(unityWebRequest1.error);
			}
			else
			{
				//创建一个p目录下的地址
				string filePath = persistentDataPath + updateList[i].abName;
				//获取相对路径
				string dir = Path.GetDirectoryName(filePath);
				dir = dir.Replace("\\", "/");
				if (!Directory.Exists(dir))
				{
					Directory.CreateDirectory(dir);
				}
				//写入p目录下的地址
				File.WriteAllBytes(filePath, unityWebRequest1.downloadHandler.data);
			}
		}
		//重新写入版本文件
		File.WriteAllText(persistentDataPath + "version.txt", serverVesionContent);
		yield return null;

		Debug.Log("更新完成");
		Init();
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值