Cocos2d-x Lua中的热更机制主要是通过AssetsManagerEx
来实现的。
传统的热更方式
传统PC的方式是将差异文件打包成一个压缩包,客户端根据大版本差异,将对应的压缩包下载到本地后解压覆盖。这种做法的好处在于下载一个文件会比较快。缺点在于当客户端版本比较多的时候,升级压缩包会变得很多,进而难以维护。其次是如果压缩包体积过大,解压时间会比较长,而且不容易制作进度条,导致程序感觉被卡住了。
AssetsManagerEx
采用比较所有文件是否一致的方式,首先需要将所有文件的md5散列表计算出来,然后生成对应格式的manifest
文件。这种方式更类似网页加载的方式,即网页显示时若本地有缓存则比对缓存与服务器是否一致,若相同则使用本地缓存,若不一直则下载更新。这样服务器只需要保存一个完整的最新客户端。如果更新了某个文件,只需要替换服务器上对应的文件,然后修改文件中的版本号和文件的md5散列码。此种做法的优点在于没有解压缩的过程,缺点是更新文件比较多的时候会略慢,而且有一定几率的下载失败。
热更新基本原理
- 不能更新主程序,只能更新资源和脚本文件
C++生成的主程序只能通过升级安全包,其他文件可通过HTTP动态下载到手机中,然后程序内部重新执行入口函数,以达到更新代码逻辑和资源图片的效果,所以C++代码在程序上线前必须保证最完善。 - 下载的文件会存放到手机的可写入目录中,该目录中文件的优先级必须高于程序原始安装目录。
以HTTP方式下载后的文件并不能直接覆盖安装程序所在目录中的同名文件,因为权限不足。由于Lua使用动态加载,只要在搜索路径中将可写入目录的优先级设置为最高,那么即使两个目录中存在同名文件,程序也会优先使用最新下载的文件。AssetsManagerEx
类的create()
方法中会见用户定义的可写入目录设置为最高优先级。 - 资源文件和脚本文件被加载后,即使程序在运行中文件也可以直接被删除,以确保文件可以被动态替换。
- 手机本地和服务器中保存了程序中所有文件的md5散列码列表,通过比对两个文件中md5散列码中,过滤出需要更新的文件,并进行下载。
热更新的实现流程
- 开发可以遍历项目中所有文件并生成对应版本的
version.manifest
和project.manifest
文件的工具
version.manifest
和project.manifest
的格式本质上是一致的,version.manifest
中只包含大版本号信息,而project.manifest
包含version.manifest
中所有内容和所有项目文件信息,这样做的好处在于当项目文件很多的时候,project.manifest
会比较大,所以单独分割出来一个version.manifest
来比较大版本,如果大版本不一致就不用下载project.manifest
。
对于项目src
目录下的cocos
、framework
等库文件,如果确定不会修改就不用生成到project.manifest
文件中,如果确实需要修改某个文件,可以手动加入到project.manifest
文件中。建议不要修改,避免日后维护困难。
/*version.manifest*/
{
//服务器中存放完整最新版本程序的目录位置,即项目的根目录。
"packageUrl":"http://192.168.0.164/update/files/",
//服务器存放version.manifest文件的URL地址
"remoteVersionUrl":"http://192.168.0.164/update/version/version.manifest",
//服务器存放project.manifest文件的URL地址
"remoteManifestUrl":"http://192.168.0.164/update/version/project.manifest",
//程序版本号,采用 "大版本.日期.小版本" 格式
"version":"1.20190112.01",
//客户端引擎版本
"engineVersion":"Cocos2d-lua v3.3 Final"
}
/*project.manifest*/
{
//服务器中存放完整最新版本程序的目录位置,即项目的根目录。
"packageUrl":"http://192.168.0.164/update/files/",
//服务器存放version.manifest文件的URL地址
"remoteVersionUrl":"http://192.168.0.164/update/version/version.manifest",
//服务器存放project.manifest文件的URL地址
"remoteManifestUrl":"http://192.168.0.164/update/version/project.manifest",
//程序版本号,采用 "大版本.日期.小版本" 格式
"version":"1.20190112.01",
//客户端引擎版本
"engineVersion":"Cocos2d-lua v3.3 Final",
//资源文件
"assets":{
"res/images/test.png":{
"md5":"e6aed0272011da3039ccc1008040cbce"
},
//资源文件为zip且compress为true表示,更新完毕后进行解压。
//由于zip文件是独立打包计算md5,日后的比较必须以zip包做比较,实际维护起来比较麻烦,不建议使用。
"res/zip/test.zip":{
"md5":"e6aed0272011da3039ccc1008040cbce",
"compressed":true
}
}
}
- 将
project.manifest
文件存放到主程序某个目录(version)中并制作安装包。
srv/version/project.manifest
- 将
version.manifest
存放到remoteVersionUrl
对应服务器指定目录下 - 将
project.manifest
存放到remoteManifestUrl
对应服务器指定目录下 - 将最新的代码存放到
packageUrl
对应服务器指定目录下,注意要与assets下的路径对应。 - 客户端执行更新代码
-- 设置新文件保存路径
local writablePath = cc.FileUtils:getInstance():getWritablePath()
local storagePath = writablePath.."version"
-- 创建资源管理器
local file = "src/version/project.manifest"
local assetsManagerEx = cc.AssetsManagerEx:create(file, storagePath)
assetsManagerEx:retain()
-- 监听下载消息
local function assetsManagerExHandler(event)
local eventCode = event:getEventCode()
local assetManagerExCode = cc.EventAssetsManagerEx.EventCode
if eventCode == assetManagerExCode.ALREADY_UP_TO_DATE then
print("当前已是最新版本")
-- 进入游戏主界面
end
if eventCode == assetManagerExCode.NEW_VERSION_FOUND then
print("发现新版本,开始升级...")
end
if eventCode == assetManagerExCode.PROGRESSION then
print("当前更新进度为"..event.getPercent())
end
if eventCode == assetManagerExCode.UPDATE_FINISHED then
print("更新完毕,准备重启...")
app:run()
end
if eventCode == assetManagerExCode.ERROR_NO_LOCAL_MANIFEST then
print("热更失败:本地不存在manifest文件")
end
if eventCode == assetsManagerExCode.ERROR_DOWNLOAD_MANIFEST then
print("热更失败:manifest文件下载失败")
end
if eventCode == assetsManagerExCode.ERROR_PARSE_MANIFEST then
print("热更失败:manifest文件解析失败")
end
if eventCode == assetManagerExCode.ERROR_UPDATING then
print("热更失败:文件更新失败")
end
end
local dispatcher = cc.Director:getInstance():getEventDispatcher()
local eventListenerAssetsManagerEx = cc.EventListenerAssetsManagerEx:create(assetsManagerEx, assetsManagerExHandler)
dispatcher:addEventListenerWithFixedPriority(eventListenerAssetsManagerEx, 1)
-- 检查版本并升级
assetsManagerEx:update()
使用注意
-
storagePath
路径一旦确认不能更换否则会造成多版本混乱 -
assetsManagerEx:retain()
缺失会造成下载失败 - 如果文件是只读的,会造成更新失败。
- 客户端获取当前本地版本
local localManifest = assetsManagerEx:getLocalManifest()
local version = localManifest:getVersion()