大概看了cocos和quick的更新方案,也挺不错的,但是感觉不太适合自己使用,所以就自己写一套
最早做的时候,我使用的是在c++端完全控制,把需要下载的资源以及lua文件下载完成后再调用engine->executeScriptFile("src/main.lua") lua端完全不用处理任何事情
然后这次因为重新开发,索性选了纯lua的热更方案,与c++不同的是,你要考虑到代码引用的问题,在更新之前,加载最基础的lua更新文件,然后进行更新,更新结束后再继续走lua的逻辑流程.为了方便平时使用,我是单个文件进行下载,没有打包ZIP,后续可以修改成zip下载,现在抓一些重点说一下
整体更新流程:
- 服务端制作md5比对文件生成列表(代码链接)(这个表要在客户端第一次上线的时候对客户端代码生成一次,放进客户端)
- 将md5的配置表和更新的资源,放入到文件下载服务器中(有一个傻瓜式的软件:HFS,很好用)
- 客户端启动的时候在更新界面要做的事情
- 读取本地配置表
- 下载服务器配置表,进行比对
- 检查大版本更新,如需更新直接跳转url
- 如需热更,需要两个表(1.需要删除的文件,2.需要下载的文件)
- 删除不在项目中的文件
- 下载需要的文件
- 写入文件
- 用服务器的配置表 替换掉 客户端的配置表
- 全部完成,引入lua,替换场景
这里有个地方要特别注意一下,就是一定要修改SearchPath的路径顺序,因为cocos内部在找文件的时候,是按照这个顺序,去遍历查找文件的.这里我是加了3个路径,代码如下
--先获取之前的路径
local paths = cc.FileUtils:getInstance():getSearchPaths()
--设置自己最新的路径(可写路径)
local writablePath = cc.FileUtils:getInstance():getWritablePath()
cc.FileUtils:getInstance():setSearchPaths({writablePath,writablePath .. "xxupdate/",writablePath .. "xxupdate/src/",writablePath .. "xxupdate/res/"})
--最后将之前的路径添加进去
for k,v in pairs(paths) do
cc.FileUtils:getInstance():addSearchPath(v)
end
然后需要注意的就是路径符号 ,要将所有的 '\' 修改成 '/' ,这个也是让我找了好久,因为在windows上面是没有问题,但是在android路径中带\,是无法正确查找到文件的,而且又不报错
贴入源码,就一个文件:
local winSize = cc.Director:getInstance():getWinSize()
local localConfigPath = "UpdateConfig.csv"
local serverConfigPath = "UpdateConfig.csv"
local serverUrl = "http://192.168.3.25/files/"
local panelRoot = nil
local serverConfigContent = "" --服务器配置表内容
local localConfig = nil --本地配置表table
local serverConfig = nil --服务器配置表table
local updateEngine = {}
--读取studio界面
local function XXGetRoot(name,children,parent)
local path = name .. ".csb"
local csbNode = cc.CSLoader:createNode(path)
local root = ccui.Helper:seekWidgetByName(csbNode,"root")
csbNode.XXChildren = {}
csbNode.XXChildren["root"] = root
for _,key in ipairs(children) do
local child = ccui.Helper:seekWidgetByName(csbNode,key)
if child ~= nil then
csbNode.XXChildren[key] = child
else
print("csb:" .. name .. ",child is not find <".. key ..">")
end
end
parent:addChild(csbNode)
root:setContentSize(winSize.width,winSize.height)
ccui.Helper:doLayout(root)
return csbNode
end
local function checkRequest( event )
if event.name == "progress" then return -2 end --进度条先无视
local ok = (event.name == "completed")
if not ok then
print("request fail")
return -1
end
local request = event.request
local code = request:getResponseStatusCode()
if code ~= 200 then
print("request fail")
return -1
end
return 0
end
--下载文件
updateEngine.HttpGet = function(callback,url)
local request = network.createHTTPRequest(function(event)
--请求失败
local requestFlag = checkRequest(event)
if requestFlag == -1 then
callback(-1)
return
end
if requestFlag == -2 then return end
callback(event.request:getResponseString())
end,url,"GET")
request:start()
end
--读取配置表文件
local function readConfig(content)
local lineStrs = string.split(content, '\r\n')
local ary = {}
for i = 1,#lineStrs do
local lineAry = string.split(lineStrs[i], ',')
local lineKey = lineAry[1]
ary[lineKey] = lineAry[2]
end
return ary
end
local function enterGame()
cc.FileUtils:getInstance():purgeCachedEntries() --清除文件缓存
require ("app/XXInit")
XXMVCManager:getNewView("testV1"):runScene()
end
--替换本地配置表
updateEngine.ChangeConfigFile = function()
print("替换本地配置表")
local configPath = cc.FileUtils:getInstance():getWritablePath() .. localConfigPath
local file =io.open(configPath,"wb")
local flag = file:write(serverConfigContent)
file:flush()
file:close()
print("更新完成|" .. tostring(flag) .. "|" .. configPath)
cc.FileUtils:getInstance():purgeCachedEntries() --清除文件缓存
enterGame()
end
--开始更新
updateEngine.startUpdate = function(updateList,removeList)
--5.删除无用文件
for _,v in pairs(removeList) do
local fname = string.match(v, ".+\\(.+)")
local path = cc.FileUtils:getInstance():fullPathForFilename( fname )
print("删除无用文件:"..path)
if cc.FileUtils:getInstance():isFileExist(path) then
cc.FileUtils:getInstance():removeFile(path)
panelRoot.XXChildren["updateLbl"]:setString("正在删除 " .. path)
else
print("can't find remove path:"..path)
end
end
--6.下载更新
local allCount = 0
for _,v in pairs(updateList) do
allCount=allCount+1
end
print("供需要下载文件个数:"..allCount)
if allCount == 0 then
print("无需更新,开始游戏")
enterGame()
return
end
print("开始下载")
local downCount = 0
local rootPath = cc.FileUtils:getInstance():getWritablePath()
print("下载文件路径:"..rootPath)
for _,v in pairs(updateList) do
--完整路径
local fullPath = string.gsub(rootPath .. v,'\\','/')
--文件名
local filename = string.match(v, ".+\\(.+)")
--路径
local path = string.gsub(fullPath,filename,"")
--创建路径
print("创建路径:" .. path)
if cc.FileUtils:getInstance():isDirectoryExist(path) == false then
cc.FileUtils:getInstance():createDirectory(path)
end
--下载文件
local updateUrl = serverUrl .. v
updateUrl = string.gsub(updateUrl,'\\','/')
print("开始下载文件:"..updateUrl)
local function downloadEnd(response)
if response == -1 then
print("下载失败:"..filename)
updateEngine.HttpGet(downloadEnd,updateUrl)
return
end
print("写入文件:"..fullPath)
local file =io.open(fullPath,"wb")
file:write(response)
file:flush()
file:close()
downCount = downCount+1
panelRoot.XXChildren["LoadingBar"]:setPercent(downCount/allCount*100)
if downCount == allCount then
print("文件全部下载完成:" .. allCount .."个" )
panelRoot.XXChildren["updateLbl"]:setString("进入游戏!")
updateEngine.ChangeConfigFile()
end
end
updateEngine.HttpGet(downloadEnd,updateUrl)
end
end
--检查更新
updateEngine.checkupdate = function()
print("start checkupdate")
--1.读取本地配置表
local fullpath = cc.FileUtils:getInstance():fullPathForFilename( localConfigPath )
print("本地配置表:"..fullpath)
localConfig = readConfig(cc.FileUtils:getInstance():getStringFromFile(fullpath))
--2.下载服务器配置表
local function ServerConfigResponse(response)
if response == -1 then
updateEngine.checkupdate() --重新下载
return
end
serverConfigContent = response
serverConfig = readConfig(response)
--3.检查大更新版本号
local clientVersion = localConfig["v"]
local serverVersion = serverConfig["v"]
print("client版本号:" .. clientVersion)
print("server版本号:" .. serverVersion)
localConfig["v"] = nil
serverConfig["v"] = nil
--dump(localConfig)
--dump(serverConfig)
--4.检查需要更新的文件
local updateList = {}
local removeList = {}
print("对比更新文件")
for k,v in pairs(localConfig) do
if serverConfig[k] == nil then
--需删除文件
print("需要删除:"..k)
table.insert(removeList,k)
end
end
for k,v in pairs(serverConfig) do
if localConfig[k] ~= nil then
--列表存在此文件
if localConfig[k] ~= v then
--需要更新
print("需要更新:"..k)
table.insert(updateList,k)
end
else
print("新增文件:"..k)
table.insert(updateList,k)
end
end
panelRoot.XXChildren["updateLbl"]:setString("正在更新!")
updateEngine.startUpdate(updateList,removeList)
end
updateEngine.HttpGet(ServerConfigResponse,serverUrl..serverConfigPath)
end
local function create()
local scene = cc.Scene:create()
local node = cc.Node:create()
scene:addChild(node)
panelRoot = XXGetRoot("UpdateView",{"LoadingBar","updateLbl"},node)
panelRoot.XXChildren["LoadingBar"]:setPercent(0)
panelRoot.XXChildren["updateLbl"]:setString("正在检查资源!")
cc.Director:getInstance():runWithScene(scene)
updateEngine.checkupdate()
end
create()
然后main.lua 直接调用
require "updateview"
完成!
我先开发这个的原因,是为了在游戏逻辑开发过程中方便查看手机效果的,因为不需要每次都去编译到手机,感觉很麻烦,除非有bug不然一般都是开发完一个功能,上传到服务器,手机热更新查看效果.
先看下我的使用流程图:
1.修改完代码copy到工具目录,然后执行exe
2.生成后的配置表和文件夹 拖到文件更新服务器
3.重启app进行热更
这里我就贴下windows的效果吧.
这里我就随便改了一个文件用作显示.
使用起来是不是很方便,在实际工作中,这种流程可以快速的让测试人员拿到最新的修改包,不然还要发包给别人安装,太麻烦了.
就到这了,晚安!