怎么做宇宙网站,池州建行网站,wordpress建设企业官网,移动网站有哪些1#xff0c;热更新的概念与作用 app更新通常分为两类#xff0c;一种是整包更新#xff08;换包#xff09;#xff0c;一种是热更新#xff08;不换包#xff0c;通过网络下载#xff0c;动态更新资源等#xff09;。
整包更新#xff0c;是指在需要更新时#x…1热更新的概念与作用 app更新通常分为两类一种是整包更新换包一种是热更新不换包通过网络下载动态更新资源等。
整包更新是指在需要更新时需要用户手动到应用商店或官方网站下载新版本安装包并重新安装的一种更新方式该方式成本较高一般只有在无法热更新时才使用。热更新是指在不需要重新编译发布应用程序的情况下通过远程更新服务器向客户端推送程序代码、资源文件等数据的一种技术手段以修复程序漏洞、优化游戏性能、更新游戏内容等。
热更新又分资源热更新和代码热更新资源热更新较为简单一般的app都可实现而代码热更新由于考虑到安全性代码编译等问题实现起来较为困难一种实用的方法就是把代码当成资源。Unity热更新就是把代码(如Lua代码)打包成AssetBundle达到和其它资源一样的更新效果。
在当今快节奏时代app更新频繁尤其是游戏app如果每次更新都需要换包十分影响用户体验极易造成用户流失代价成本实在太高因此app热更新是十分必要的。 如果有了热更新会带来什么好处呢
提高用户体验。热更新可以实现及时修复bug和添加新功能等减少了玩家等待更新的时间和下载流量提高了用户的体验感。降低开发成本。热更新可以在不重新打包的情况下实现游戏的更新避免了频繁发布新版本的成本和风险。提高迭代效率。热更新可以快速地进行游戏内容的调整和修改加快了游戏迭代周期提高了开发效率。
2热更新原理 热更新是通过把最新的资源或代码放到网络服务器app检测到需要更新版本时通过网下载资源或代码到本地包将新的代码或资源加载到应用程序中以替换旧的代码或资源。
Unity以C#为主要开发语言如何能做到代码的热更新呢 C#是编译型语言Unity在打包后会将C#编译成一种中间代码IL后续对这些IL的编译方式不同可以分为AOT和JIT最终编译为各平台的NativeCode在没有特殊处理的情况下无法直接通过替换NativeCode来达成热更新的。 一种理想化的C#热更新流程是
把需要更新的代码编译成动态链接库游戏启动时加载新的动态链接库用反射的形式获取动态链接库中的实例或方法
这种模式在PC和Android平台是可以的但在IOS平台是不可行的。因为IOS对申请的内存禁止了可执行权限所以运行时创建/加载的NativeCode是无法执行的。 为了解决IOS上的热更新问题有两个主流方案ILRuntime 和 HybridCLR。 ILRuntime
Unity会把C#代码打包成DLLILRuntime在运行时用自己的解释器来解释IL并执行而不是直接调用.NET FrameWork或Mono虚拟机来运行代码。它借助Mono.Cecil库来读取DLL的PE信息以及当中类型的所有信息最终得到方法的IL汇编码然后通过内置的IL解译执行虚拟机来执行DLL中的代码。 但是ILRuntime会有不少限制
ILRuntime和原始的 compiler是两套东西也就是说你的热更DLL和主工程的DLL实质是不互通的如热更DLL中一个类要继承主工程DLL的一个类所以就存在跨域问题需要写委托适配器委托转换器。在发布版本后这些不能热更使用之前一定要预留好可能会使用的部分 C# 语法不支持由于 ILRuntime 是基于 Mono 实现的而 Mono 不支持所有 C# 语法所以 ILRuntime 在某些 C# 语法方面也有限制比如属性、泛型委托、可选参数等需要特殊处理的代码由于 ILRuntime 的实现方式一些特殊的代码需要进行特殊处理比如反射、LINQ、协程等性能问题由于 ILRuntime 需要动态解析和执行代码相对于编译时静态绑定的方式其性能会有一定程度的下降。同时在使用过程中也需要注意避免频繁的跨域调用和反射操作以免影响性能ILRuntime对多线程Thread不兼容在热更代码里使用多线程会导致Unity崩溃闪退
HybridCLR
是一个特性完整、零成本、高性能、低内存的近乎完美的Unity全平台原生c#热更方案。 IL2CPP是一个纯静态的AOT运行时不支持运行时加载dll因此不支持热更新。HybridCLR扩充了IL2CPP的代码使其由纯AOT Runtime变成“AOTInterpreter”混合Runtime进而原生支持动态加载Assembly使得基于IL2CPP打包的游戏不仅能在Android平台也能在IOS、Consoles等限制了JIT的平台上高效地以AOTinterpreter混合模式执行。 HybridCLR是近年来一种划时代的Unity原生C#热更新技术见https://hybridclr.doc.code-philosophy.com/
相比于直接热更新C#代码使用C#Lua脚本的热更新方案是目前最主流的实现方式。 Lua是一种跨平台的脚本语言它主要依赖解释器和虚拟机实现跨平台功能Lua是解释型语言并不需要事先编译而是运行时动态解释执行的。这样Lua就和普通的游戏资源如图片文本没有区别。由于解释器和虚拟机都是跨平台的lua脚本也就可以在不同的平台上运行了。 本质上就是利用相关插件(如ulua、slua、tolua、xlua等)提供一个Lua的运行环境虚拟机为Unity提供Lua编程的能力让C#和Lua可以相互调用和访问。
3xLua热更新方案 xLua是腾讯一个开源项目xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力借助xLua这些Lua代码可以方便的和C#相互调用。 xLua在功能、性能、易用性都有不少突破这几方面分别最具代表性的是
可以运行时把C#实现方法操作符属性事件等等替换成lua实现编辑器下无需生成代码开发更轻量出色的GC优化自定义struct枚举在Lua和C#间传递无C# gc alloc
下载地址https://github.com/Tencent/xLua 4xLua的简单使用 4.1xLua安装使用
xLua下载后将xLua文件中的Assets文件夹下的文件放到项目中的Assets文件下就完成了XLua的安装。 新建C#代码LuaManager.cs
using UnityEngine;
using XLua;public class LuaManager : MonoBehaviour
{LuaEnv m_luaEnv;void Start(){m_luaEnv new LuaEnv();m_luaEnv.DoString(print(Hello World));}
}
新建场景挂上LuaManager.cs运行看到打印 Hello World 则安装成功了
4.2自定义Lua加载器
要想执行lua文件就要用上Lua加载器了修改LuaManager.cs using System;
using System.IO;
using UnityEngine;
using XLua;public class LuaManager : MonoBehaviour
{public static string LuaDir src; // 存放lua文件的位置Assets根目录下LuaEnv m_luaEnv;Action m_startAction;Action m_updateAction;void Start(){m_luaEnv new LuaEnv();m_luaEnv.AddLoader(new LuaEnv.CustomLoader(this.LuaLoaderFromRes));// 请求执行src下的Main.lua文件m_luaEnv.DoString(require(Main), chunk);LuaTable luaTable this.m_luaEnv.Global.GetLuaTable(Main);if (luaTable ! null){m_startAction luaTable.GetAction(Start);m_updateAction luaTable.GetAction(Update);}// 执行Main.lua Start方法m_startAction?.Invoke();}void Update(){// 执行Main.lua Update方法m_updateAction?.Invoke();}private byte[] LuaLoaderFromRes(ref string filePath){filePath filePath.Replace(., /);if (!filePath.EndsWith(.lua)){filePath .lua;}#if UNITY_EDITORstring path Application.dataPath / LuaDir / filePath;if (File.Exists(path)){//读取路径下的文件的值以字节形式返回return File.ReadAllBytes(path);}
#endif// TODO// android ios 等平台读取lua文件return null;}
}
在Assets目录下新建文件夹srcsrc文件夹下新建文件Main.lua
Main {}
setmetatable(Main, {__index _G})
local _ENV Mainfunction Start()print(Lua Start)
endfunction Update()-- TODO
endreturn Main
运行看到打印 Lua Start 表示成功Update()可增加每帧的逻辑可在src下继续增加其它lua文件
4.3Lua调用C#
[LuaCallCSharp]在C#类加上标签[LuaCallCSharp]就可在Lua中访问了 新建C#代码GameTest.cs
using UnityEngine;
using XLua;namespace MyGame
{[LuaCallCSharp] // 建立Lua调用C#的映射public class GameTest : MonoBehaviour{public string Name;void Start(){Debug.Log(Name: Name);}public void CallTest(string text){Debug.Log(Lua Call: text);}}
}
修改Main.lua
Main {}
setmetatable(Main, {__index _G})
local _ENV Mainfunction Start()print(Lua Start)-- 访问C#的类使用CS 命名空间 类名local go CS.UnityEngine.GameObject(LuaGameObject)local test go:AddComponent(typeof(CS.MyGame.GameTest))test.Name Game Test-- 调用方法使用:test:CallTest(666)
endfunction Update()-- TODO
endreturn Main
如果不想在每个类中加标签[LuaCallCSharp]也可以参考XLua/Editor/ExampleConfig集中配置。 注意如果需要打包需提前生成Wrap文件执行菜单命令XLua/Generate Code
至于C#调用Lua4.2代码已有了更详细的参考官方例子
推荐一个基于xLua的Unity游戏纯lua客户端完整框架https://github.com/smilehao/xlua-framework
5xLua可热更规则 进入lua层后的一切逻辑、资源都可热更app中基本所有的资源(图片、声音、3d模型、动作、特效、文本文件)、lua代码都可热更C#层代码不可热更也不完全不能xlua.hotfix可以修改C#代码的执行替换原来的逻辑但这是lua代码不是直接修改C#需要新增或修改的代码必须是C#代码则不可热更
6热更新流程 6.1更新前准备
打包AssetBundle打包程序会比较Unity所有资源与上次打包后对比实现增量打包生成md5信息文件(assetbundlemd5.txt)版本信息文件(version.txt)递增资源版本号上传AssetBundleassetbundlemd5.txtversion.txt到网络服务器(cdn)停服或后台通知用户在线更新
6.2更新流程
启动app下载版本信息文件version.txt版本号的比较如果版本号不同才继续以下流程下载资源服务器上的md5对比文件assetbundlemd5.txt确定下载列表将最新下载的md5对比文件和本地旧md5对比文件对比记录缺少或不同的文件。assetbundlemd5.txt中的md5码实现此步骤根据下载列表下载所需的资源。一般放在Application.persistentDataPath)保证下载成功后用最新的md5对比文件覆盖本地的md5对比文件更新assetbundlemd5.txt记录最新的版本号
7Unity热更新实现
版本信息文件version.txt
{code:0,data:{isUpdateClient:0,isUpdateRes:1,version:1.0,resVersion:1.0.0.1,clientUrl:https://aa.bb.cc.com/game/client.apk,resUrl:https://aa.bb.cc.com/game/res/}
}这是version.txt的结构参考JSON格式字段说明
isUpdateClient是否强制更新整包视情况是否打开isUpdateRes是否更新资源开头无特殊情况都是打开version客户端版本号如果此版本号对比不一样需考虑更新整包resVersion资源版本号如果此版本号对比不一样则进行热更新每次打包递增clientUrl客户端整包的更新地址可根据后缀跳转网站或直接下载安装resUrl热更新资源的地址
App启动下载version.txt版本比较代码
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;public class GameStart : MonoBehaviour
{public string VersionUrl https://aa.bb.cc.com/game/version.txt; // version.txt网络服务器地址public string appVersion 1.0; // 当前客户端版本号public string currentResVersion 1.0.0.1; // 当前最新资源版本号void Start(){// app 启动前逻辑如读取客户端版本号最新资源版本号//currentResVersion PlayerPrefs.GetString(currentResVersion);StartCoroutine(RequestVersionInfo());}IEnumerator RequestVersionInfo(){// 加上时间戳确保下载的是最新文件UnityWebRequest request new UnityWebRequest(VersionUrl ?time System.DateTime.Now.Ticks);request.downloadHandler (DownloadHandler)new DownloadHandlerBuffer();yield return request.SendWebRequest();if (request.error null){string text request.downloadHandler.text;LitJson.JsonData versionInfo LitJson.JsonMapper.ToObject(text);int isUpdateClient (int)versionInfo[data][isUpdateClient];int isUpdateRes (int)versionInfo[data][isUpdateRes];string version (string)versionInfo[data][version];string clientUrl (string)versionInfo[data][clientUrl];string resUrl (string)versionInfo[data][resUrl];string resVersion (string)versionInfo[data][resVersion];if (isUpdateClient 1){if (compareResVersion(version, appVersion)){// 提示客户端更新// Application.OpenURL(clientUrl);}}if (isUpdateRes 1){if (compareResVersion(resVersion, currentResVersion)){// 进入热更新;//StartHotUpdate(resUrl);}}}request.Dispose();}public bool compareResVersion(string resVersion1, string resVersion2){var arr1 resVersion1.Split(.);var arr2 resVersion2.Split(.);for (int i 0; i arr1.Length; i){if (int.Parse(arr1[i]) int.Parse(arr2[i])){return true;}}return false;}}
热更新流程代码
IEnumerator StartHotUpdate(string resUrl)
{bool downloadFailed false;// 下载网络服务器最新md5信息文件string md5Url resUrl assetbundlemd5.txt;UnityWebRequest md5Request new UnityWebRequest(md5Url ?version currentResVersion); // 加上版本号确保下载的是最新文件md5Request.downloadHandler (DownloadHandler)new DownloadHandlerBuffer();yield return md5Request.SendWebRequest();if (md5Request.error null){AssetBundleMD5Infos remoteMd5_info new AssetBundleMD5Infos(md5Request.downloadHandler.data); // 网络服务器最新md5信息文件AssetBundleMD5Infos tmpMd5_info; // 由于出错中断暂时保存的md5信息文件string dirPath Application.persistentDataPath / Utility.GetPlatformName();if (!Directory.Exists(dirPath)){Directory.CreateDirectory(dirPath);}dirPath dirPath /;if (File.Exists(dirPath assetbundlemd5.tmp)){byte[] fileContent File.ReadAllBytes(dirPath assetbundlemd5.tmp);tmpMd5_info new AssetBundleMD5Infos(fileContent);}else{tmpMd5_info new AssetBundleMD5Infos(null);}Liststring needUpdateAbs new Liststring(); // 需要下载更新的ab文件列表foreach (var abName in remoteMd5_info.m_AssetBundleMD5.Keys){string remoteMd5 remoteMd5_info.GetAssetBundleMD5(abName);// 与网络服务器最新md5比较不同则加载下载更新列表AssetBundleManager.GetAssetBundleMD5(abName)本地最新md5if (tmpMd5_info.GetAssetBundleMD5(abName) ! remoteMd5 remoteMd5 ! AssetBundleManager.GetAssetBundleMD5(abName)){needUpdateAbs.Add(abName);}}// 下载更新的ab文件foreach (string abName in needUpdateAbs){UnityWebRequest abRequest new UnityWebRequest(resUrl abName ?version currentResVersion); // 加上版本号确保下载的是最新文件abRequest.downloadHandler (DownloadHandler)new DownloadHandlerBuffer();yield return abRequest.SendWebRequest();if (abRequest.error null){// 保存到最新的ab文件到本地File.WriteAllBytes(dirPath abName, abRequest.downloadHandler.data);tmpMd5_info.AddAssetBundleMD5(abName, remoteMd5_info.GetAssetBundleMD5(abName), remoteMd5_info.GetAssetBundleSize(abName), remoteMd5_info.GetAssetBundleMiniGameId(abName));} else{downloadFailed true;}abRequest.Dispose();}if (needUpdateAbs.Count 0){if (!downloadFailed){// 保存最新的md5文件remoteMd5_info.SerializeToFile(dirPath assetbundlemd5.txt);File.Delete(dirPath assetbundlemd5.tmp);}else{tmpMd5_info.SerializeToFile(dirPath assetbundlemd5.tmp); // 出错中断保存临时的md5避免下次更新重新下载}}}else{downloadFailed true;}md5Request.Dispose();if (downloadFailed){// 出错重新执行更新流程StartCoroutine(StartHotUpdate(resUrl));}
}