最近研究quick-cocos2dx的热更新机制,看到它主要是通过AssetsManagerEx来实现热更新,而网上大多数介绍的AssetsManager类已经在quick-cocos2dx3.3中被禁用了,所以写一下对AssetsManagerEx的认识 1.热更新基本原理 这里先说一下热更新的基本原理 1)不能更新主程序,只能更新资源、lua等
文件 c++
生成的主程序如果变化只能通过下载
升级包安装,而其它
文件可以通过http动态下载到
用户手机上,然后程序内部重新执行入口
函数达到更新
代码逻辑和资源
图片的
效果。所以c++那部分
代码在程序上线前最好
最完善。 2)下载的
文件会放到手机的可写入目录下,而且该目录中
文件的优先级会高于程序原始安装目录 http方式下载后的
文件并不能直接覆盖安装程序所在
文件夹中的同名
文件,因为权限不足。但是lua属于动态加载,只要在
搜索路径中把可写入目录的优先级设置为最高,那么如果两个目录中都有同名
文件,程序也会用最新下载的 AssetsManagerEx类的create
函数中会把
用户定义的可写入目录设置为最高优先级 3)lua
文件和资源
文件被载入后,即便在程序运行中,
文件也可以直接
删除 这种机制才确保
文件可以被动态替换掉 4)手机本地和服务器中保存
一个程序中所有
文件的md5码列表,通过比较二者
文件md5码列表的不同过滤出需要更新的
文件进行下载 传统PC更新方式一般是把差异
文件打包成
一个压缩包,然后客户端根据大版本差异把对应的压缩包下载到本地,再解压覆盖,这样做的好处是下载
一个文件会比较快,缺点第一是当客户端版本比较多的时候,
升级压缩包会变得很多,很难维护。第二是如果压缩包比较大,解压时间会比较长,而且不太容易做进度条,导致程序感觉被卡住了 AssetsManagerEx采用的是比较所有
文件是否一致的
方法(需要先写个工具把程序下所有
文件的md5码计算出来,并且
生成对应格式的manifest
文件才行),这点更象是网页加载的方式,也就是web
显示时如果本地有缓存就比较缓存和服务器是否一样,如果一样就用缓存的,不一样就下载。这样服务器只需要保存
一个完整的最新客户端,如果更新了某个
文件,只需要在服务器上替换对应
文件,然后
修改配置文件中的版本号和那个
文件的md5码即可。优点是没有解压缩的过程,缺点是更新
文件比较多时略慢,而且有一定几率下载失败,好在现在手机网速都比较高,失败了重新下载即可,即使偶尔
升级失败进不去也关系不大。 2.AssetsManagerEx实现热更新的基本步骤 AssetsManagerEx类实现的
功能还是比较完善和复杂的,
包括多线程同时下载、总体下载进度
通知、版本比较、设置
文件夹路径等等,所以能用这个就没必要自己重新写
一个热更新类了 1)开发
一个可以遍历项目中所有
文件生成对应的version.manifest和project.manifest
文件的工具 (1)version.manifest和project.manifest的格式本质上是一致的,version.manifest中只包含大的版本号信息,而project.manifest中包含version.manifest中所有
内容+所有项目
文件信息,这样做的好处是当项目
文件很多时project.manifest会比较大,所以单独分割出来
一个version.manifest来比较大的版本,如果大版本一致就不用下载project.manifest了 (2)对于src下的cocos、frameworks这些库
文件,如果确定不会
修改,可以不用
生成到project.manifest
文件中。如果确实需要改某个
文件,可以手动加入到project.manifest
文件中,但以后维护就比较麻烦了,所以建议不要改 (3)version.manifest示例 { "packageUrl" : "http://192.168.3.8/update/files/","remoteVersionUrl" : "http://192.168.3.8/update/version/version.manifest","remoteManifestUrl" : "http://192.168.3.8/update/version/project.manifest","version" : "1.0.0","engineVersion" : "Cocos2d-lua v3.3 Final" } packageUrl:服务器上存放完整最新版程序的目录位置,对于quick-cocos2dx来说,就是工程
默认
生成的src、res目录那级,不
包括c++产生的主程序 remoteVersionUrl:服务器存放version.manifest
文件的
URL地址 remoteManifestUrl:服务器存放project.manifest
文件的
URL地址 version:程序版本号,为了直观,可以采用"大版本.日期.小版本"的形式,例如"2.20150618.01" engineVersion:引擎版本,这个只是自己看,没什么实际用处 (4)project.manifest示例 { "packageUrl" : "http://192.168.3.8/update/files/","engineVersion" : "Cocos2d-lua v3.3 Final","assets" : { "res/Images/sp.png" : { "md5" : "e6aed0272011da3039ccc1008040cbce" },"res/Images/same.png" : { "md5" : "65434fec9183f328a65895067c43516c" },"res/Images/x.zip" : { "md5" : "e49cd1a2da3687869b7191955969901f","com
pressed":true },"res/defineTable.zip" : { "md5" : "1c1ae7a67ea745be20e6923949bba4d4","src/app/sce
nes/GameScene.lua" : { "md5" : "7ba03121d1d970ca9357a81f5e32e711" },"res/a.msi" : { "md5" : "6a912882f376549f8d92e76fc07eb342" },"res/b.msi" : { "md5" : "6a912882f376549f8d92e76fc07eb342" } } } 前面的
内容和version.manifest一致,后面assets就是所有
文件的
内容 首先是
文件路径,是和res、src目录平齐的 md5:该
文件的md5码,AssetsManagerEx根据md5码判断
文件是否有变更,其实这个只是做比较用,所以像第
一个版本填1,以后有变化+1也行,但md5码适合于程序
自动生成manifest
文件 com
pressed:如果是true,并且
文件是zip
文件,那么更新完毕后会再执行解压缩。由于zip
文件要单独打包计算md5,以后的比较也是以压缩包整体作比较,所以实际维护起来比较麻烦,一般情况下不建议使用 2)把project.manifest放到主程序的某个目录中制作安装
文件 例如放在src/version/目录下 3)把version.manifest放到服务器remoteVersionUrl对应的url位置上 4)把project.manifest放到服务器remoteManifestUrl对应的url位置上 5)把完成最新程序目录放到packageUrl对应的
文件夹下,注意要与assets下的路径对应 6)客户端中热更新相关
代码如下(只是简单示例,完整的还会涉及到界面进度条等
内容) 例如放到在MainScene:ctor()中 --设置新
文件保存的位置 local writablePath = cc.FileUtils:getInstance():getWritablePath() local storagePath = writablePath.."new_version" --创建AssetsManagerEx对象 local assetsManagerEx = cc.AssetsManagerEx:create("src/version/project.manifest",storagePath) assetsManagerEx:retain() --设置下载消息listener local function handleAssetsManagerEx(event) if (cc.EventAssetsManagerEx.EventCode.ALREADY_UP_TO_DATE == event:getEventCode()) then print("已经是最新版本了,进入游戏主界面") app:enterScene("GameScene") end if (cc.EventAssetsManagerEx.EventCode.NEW_VERSION_FOUND == event:getEventCode()) then print("发现新版本,开始
升级") end if (cc.EventAssetsManagerEx.EventCode.UPDATE_PROGRESSION == event:getEventCode()) then print("更新进度="..event:getPercent()) end if (cc.EventAssetsManagerEx.EventCode.UPDATE_FINISHED == event:getEventCode()) then print("更新完毕,重新启动") app:run() end if (cc.EventAssetsManagerEx.EventCode.ERROR_NO_LOCAL_MANIFEST == event:getEventCode()) then print("发生
错误:本地找不到manifest
文件") end if (cc.EventAssetsManagerEx.EventCode.ERROR_DOWNLOAD_MANIFEST == event:getEventCode()) then print("发生
错误:下载manifest
文件失败") end if (cc.EventAssetsManagerEx.EventCode.ERROR_PARSE_MANIFEST == event:getEventCode()) then print("发生
错误:解析manifest
文件失败") end if (cc.EventAssetsManagerEx.EventCode.ERROR_UPDATING == event:getEventCode()) then print("发生
错误:更新失败") end end local
dispatcher = cc.Director:getInstance():getEvent
dispatcher() local eventListenerAssetsManagerEx = cc.EventListenerAssetsManagerEx:create(assetsManagerEx,handleAssetsManagerEx)
dispatcher:addEventListenerWithFixedPriority(eventListenerAssetsManagerEx,1) --检查版本并
升级 assetsManagerEx:update() 注意点 (1)storagePath路径一旦确定不能更换,否则多个版本会产生混乱 (2)assetsManagerEx:retain()操作别忘了,否则会下载失败 7)客户端可以通过以下方式获得当前本地版本 local localManifest = assetsManagerEx:getLocalManifest() print(localManifest:getVersion()) 3.其它注意点 1)一定要注意
文件的只读
属性,如果是只读
文件,会造成更新失败,而且很难
解决,所以每次发布安装包和在服务器上部署程序
文件时一定要确保
文件不是只读的