本篇介绍热更的Lua实现
1.UI界面 UpdateLayer.lua
local LocalStorage = require("framework.storage.LocalStorage")
local UpdateLayer = class("UpdateLayer", function()
return cc.Layer:create()
end)
local director = cc.Director:getInstance()
local view = director:getOpenGLView()
function UpdateLayer:ctor(hasInitSDK, autoOpenLogin)
self._hasInitSDK = hasInitSDK
self._autoOpenLogin = autoOpenLogin
self._curProgress = 0
self._tarProgress = 0
--是否已经开始热更新
self._isUpdate = nil
self._md5DownloadStart = false
--是否开始拉取整包更新的url
self._showUpdateClient = nil
self._updateCheck = require("version.UpdateCheck").new(
handler(self, self._onCheckProgress),
handler(self, self._onCheckError),
handler(self, self._setRemoteVersion)
)
G_updateCheck = self._updateCheck
self._getUpdateUrl = require("version.UpdateUrl").new(
handler(self, self._onGetUrlProgress),
handler(self, self._onGetUrlError)
)
self:registerScriptHandler(
function(state)
if state == "enter" then
self:_onEnter()
elseif state == "exit" then
if self._updateHandler then
G_Scheduler.remove(self._updateHandler)
self._updateHandler = nil
self._curProgress = 0
end
end
end
)
G_EventManager = require("framework.event.EventManager").new()
G_EventID = require("app.event.EventID")
end
function UpdateLayer:_onEnter()
local viewsize = director:getWinSize()
local bg = cc.Sprite:create("ui/images/scene/start/start/start_bg1.png")
bg:setPosition(viewsize.width/2,viewsize.height/2)
self:addChild(bg)
local logo = cc.Sprite:create("ui/images/scene/start/start/start_logo.png")
logo:setPosition(568,377)
self:addChild(logo)
local tips = newLabelTTF("",nil,20)
tips:setPosition(viewsize.width/2,21)
self:addChild(tips)
local node = cc.Node:create()
node:setPosition(512.5,21.6)
self:addChild(node)
local flowerBg = cc.Sprite:create("ui/images/scene/start/start/start_loding_bg.png")
flowerBg:setPosition(55.93,86.72)
node:addChild(flowerBg)
local sp_progress = cc.Sprite:create("game_assets/images/start/start_loding.png")--粉色花背景
self._progress = cc.ProgressTimer:create(sp_progress)
self._progress:setType(cc.PROGRESS_TIMER_TYPE_RADIAL)--设为圆形进度条
node:addChild(self._progress)
self._progress:setPosition(flowerBg:getPositionX(), flowerBg:getPositionY())
self._progress:setPercentage(0)
local localVersion = newLabelTTF("Text_version",nil,15)
localVersion:setPosition(1126,27)
localVersion:setAnchorPoint({x=1,y=0.5})
localVersion:setString("LOCAL: " .. G_Version:getLocalVersionString())
self:addChild(localVersion)
self._remoteVersion = newLabelTTF("Text_version",nil,15)
self._remoteVersion:setPosition(1126,9)
self._remoteVersion:setAnchorPoint({x=1,y=0.5})
self._remoteVersion:setVisible(false)
self:addChild(self._remoteVersion)
self._text_per = newLabelTTF("",nil,20)
self._text_per:setPosition(568,100)
self._text_per:setString("0%")
self:addChild(self._text_per)
local ret = G_Version:removeHotDirForNewPackage()
if ret then
return
end
self.text_tips = tips
local packageIndex = G_ChannelManager.channel():getPackageIndex()
local allowPA = LocalStorage.getBool("HAS_ALLOW_PA", false, false)
local channelId = G_ChannelManager.getChannelId()
local opid = G_ChannelManager.channel():getOpId()
print("UpdateLayer:_onEnter","packageIndex:",packageIndex,"allowPA:",allowPA, "channelId:",channelId,"opid:",opid)
if packageIndex < 20211217 then
--老包
LocalStorage.setBool("HAS_ALLOW_PA", true, false) --修复老包->新包,覆盖安装卡热更的bug
self:startHotUpdateCheck()
elseif allowPA then
--新包已同意隐私条例从这里发起热更
--新包未同意隐私条例从BaseChannel:_onCheckPA发起热更
self:_buglyInit()
self:startHotUpdateCheck()
end
end
function UpdateLayer:startHotUpdateCheck()
print("*** do UpdateLayer:startHotUpdateCheck", self._updateStarted)
print(debug.traceback())
if self._updateStarted then
return
end
self._updateStarted = true
if G_ChannelManager.isUpdateOpen() then
G_updateCheck:getVersionInfo()
else
self:_enterGame()
end
end
function UpdateLayer:_startUpdateClient()
self._curProgress = 0
self._tarProgress = 0
self._progress:setPercentage(0)
self._text_per:setString("0%")
self.text_tips:setString("下载版本...(0%)")
self._getUpdateUrl:start(url)
end
function UpdateLayer:_onUpdateProgress(initPer, curIndex, notCalculate)
end
function UpdateLayer:_startFrameCheck(progress, tipWord)
self._tarProgress = math.ceil(progress)
self.text_tips:setString(tipWord)
if not self._updateHandler then
--G_Scheduler.setUpdate的回调只有在客户端空闲时会调度!
self._updateHandler = G_Scheduler.setUpdate(handler(self, self._frameUpdate))
end
end
function UpdateLayer:_frameUpdate()
if self._curProgress < self._tarProgress then
local before = self._curProgress
self._curProgress = self._curProgress + math.ceil((self._tarProgress - self._curProgress) / 10)
--print("########### _frame1Update", before, self._curProgress, self._tarProgress, os.time())
if self._progress:getPercentage() ~= self._curProgress then
self._progress:setPercentage(self._curProgress)
self._text_per:setString(self._curProgress .. "%")
end
end
if self._curProgress >= 100 then
self:_onComplete()
end
end
function UpdateLayer:_onComplete()
G_Scheduler.remove(self._updateHandler)
self._updateHandler = nil
self._curProgress = 0
local packageIndex = G_ChannelManager.channel():getPackageIndex()
--不需要更新
print("*** UpdateLayer:_onComplete()", self._updateCheck._updateFlag)
if self._updateCheck:isForceHotNoDownload() or self._updateCheck:isNotNeedUpdate() then
self:_enterGame()
--有热更新有下载
elseif self._updateCheck:isDownloadUpdate() then
if not self._md5DownloadStart then
self:_showUpdateAlert()
end
--有热更无下载
elseif self._updateCheck:isNoDownloadUpdate() then
self._updateCheck:doNoDownloadUpdate()
G_ChannelManager.channel():onGameInnerUpdateEnd(true)
G_Version:restartGame()
--热更拷贝文件中断
elseif self._updateCheck:isCopyBreak() then
G_ChannelManager.channel():onGameInnerUpdateEnd(true)
G_Version:restartGame()
--需要更新整包
elseif self._updateCheck:isNeedUpdateClient() then
if not self._showUpdateClient then
self:_showUpdatePackageAlert()
self._showUpdateClient = true
else
G_Version:updateClient()
end
end
end
function UpdateLayer:_onCheckProgress(percent, strTip)
self:_startFrameCheck(percent, strTip)--热更直接取percent
end
function UpdateLayer:_getErrMsgByIndex(index)
local msg = {
"失败,请检查网络",
"失败,请检查网络", --尝试三次仍然失败
"失败,稍后将重试,请勿退出",--前两次失败
"失败,请重试"
}
return msg[index] or ""
end
function UpdateLayer:_onCheckError(error, curIndex)
local curTask = self._updateCheck:getTaskTips(curIndex)
local errMsg = self:_getErrMsgByIndex(error)
self.text_tips:setString(string.format("%s(%s)",curTask,errMsg))
end
--整包更新进度条变化
function UpdateLayer:_onGetUrlProgress(initPercent, curIndex)
local subPercent = 0
if initPercent and initPercent ~= 0 then
initPercent = initPercent > 1 and initPercent / 100 or initPercent
subPercent = 100 / self._getUpdateUrl:getTaskCount() * initPercent
else
initPercent = 0
end
-- 进度条从0开始计算,所以需要减1
local targetProgress = (curIndex - 1) / self._getUpdateUrl:getTaskCount() * 100
if targetProgress < 0 then
targetProgress = 0
end
targetProgress = targetProgress + subPercent
--整包更新targetProgress走计算逻辑
self:_startFrameCheck(targetProgress, self._getUpdateUrl:getTaskTips(curIndex) .. "(" .. math.floor(initPercent * 100) .. "%)")
end
function UpdateLayer:_onGetUrlError(error, curIndex)
local curTask = self._getUpdateUrl:getTaskTips(curIndex)
local errMsg = self:_getErrMsgByIndex(error)
self.text_tips:setString(string.format("%s(%s)",curTask,errMsg))
end
function UpdateLayer:_enterGame()
cc.UserDefault:getInstance():setBoolForKey("MD5HOTUPDATED", true)
local packageIndex = G_ChannelManager.channel():getPackageIndex()
cc.UserDefault:getInstance():setIntegerForKey("LastLoginPkgIdx", packageIndex)
--##下面这段用做覆盖安装
local pkgVerFilePath = cc.FileUtils:getInstance():fullPathForFilename("version.data")
local pkgVerFileData = G_Version._readVersionInfoFromFile(pkgVerFilePath)
local pkgVerFileId = pkgVerFileData:getID()
cc.UserDefault:getInstance():setIntegerForKey("LastPkgId", pkgVerFileId)
--##
self:_init()
self:_setDebug()
cc.disable_global()
G_BusinessCenter.start(G_BusinessConst.LOGOUT)
if CC_SHOW_FPS then
cc.Director:getInstance():setDisplayStats(false)
end
if G_ChannelManager.channel():isAdReportOpen() then
require("app.adReport.AdReport").getInstance():onStartGame(os.time())
end
require("app.share.ShareConfig").getInstance():setPlatformsByOpId()
local sdkInitStatus = G_ChannelManager.channel():initStatus()
if sdkInitStatus == 0 then
G_ChannelManager.channel():init(handler(self, self._onSdkInitComplete), handler(self, self._onSdkInitError))
elseif sdkInitStatus == 1 then
G_ChannelManager.channel():init(handler(self, self._onSdkInitComplete), handler(self, self._onSdkInitError))
self:_onSdkInitComplete()
else
self:_onSdkInitError()
end
end
function UpdateLayer:_initSDK()
G_ChannelManager.channel():init(nil,nil)
end
function UpdateLayer:_onSdkInitComplete()
if G_SoundEffect ~= nil then
G_SoundEffect:preload()
end
self:_showTip()
end
function UpdateLayer:_onSdkInitError()
local msg = "游戏账号系统初始化异常!\n请联系工作人员进行处理。"
local Alert = require("app.scenes.common.Alert")
--可能当前场景初始化尚未完成,做个延时弹框防止程序出错。
G_Scheduler.setDelay(function()
Alert.show(msg, 2)
end, 0.001)
end
--健康游戏忠告UI
function UpdateLayer:_showTip()
local viewsize = director:getWinSize()
local scene = cc.Scene:create()
scene:addTouchEffect()
local panel = display.getPanel(viewsize.width,viewsize.height,true)
scene:addChild(panel)
local bg = cc.Sprite:create("ui/tip.png")
bg:setPosition(viewsize.width/2,viewsize.height/2)
panel:addChild(bg)
bg:setOpacity(0)
bg:runAction(cc.FadeIn:create(0.3))
local timer
local autoOpenLogin = self._autoOpenLogin
local function skip()
bg:runAction(
cc.Sequence:create(
cc.FadeOut:create(0.5),
cc.CallFunc:create(
function ()
G_SceneManager.switchSceneWithTween(G_SceneManager.SCENE_LOADING, false, autoOpenLogin)
-- 给游戏逻辑打补丁,每隔一段时间请求一次
require("patch.PatchTool"):instance():repeatPatching()
if timer then
G_Scheduler.remove(timer)
timer = nil
end
end
)
)
)
end
panel:addClickEventListenerEx(skip)
timer = G_Scheduler.setDelay(skip, 2)
cc.Director:getInstance():replaceScene(scene)
end
function UpdateLayer:_init()
require("framework.platform.init")
G_Lang = require("framework.lang.Lang")
G_Lang.add(require("lang.LangTemplate"))
G_Lang.add(require("lang.LangTemplate_1"))
G_FileCache = require("framework.cache.FileCache")
G_BusinessCenter = require("framework.business.BusinessCenter")
G_ChannelManager = require("framework.channel.ChannelManager")
G_EventManager = require("framework.event.EventManager").new()
G_EventID = require("app.event.EventID")
G_GameSettingManager = require("app.setting.GameSettingManager").new()
G_Colors = require("app.setting.Colors")
G_Setting = require("app.setting.Setting")
G_Url = require("app.setting.Url")
G_YDManager = require("app.data.YDManager")
G_DataCenter.init()
G_SceneManager = require("app.scenes.SceneManager")
G_SceneManager.collectLog()
G_SwordVoice = require("app.audio.SwordVoice")
G_SoundEffect = require("app.audio.SoundEffect")
G_Tutorial = require("app.scenes.tutorial.TutorialManager")
G_Tutorial.init()
G_AudioManager:initData(G_Url:getAudioDic())
G_Help = require("app.scenes.common.help.HelpManager")
G_NotificationMgr = require("app.scenes.common.notification.NotificationMgr")
G_ClickEffectPool = require("app.scenes.effect.ClickEffectPool").Singlton()
--
end
function UpdateLayer:_setDebug()
if device.platform == "android" or device.platform == "ios" then
__G__TRACKBACK__ =
function(msg)
local message = msg
local msg = debug.traceback(msg, 3)
print(msg)
if G_DataCenter.login and G_DataCenter then
buglySetUserId(G_DataCenter.login:getUserId())
end
buglyReportLuaException(tostring(message), debug.traceback())
return msg
end
end
--捕获前端报错用面板显示 方便机器测试
-- 判断是不是debug包
local debugPkg = false
if G_ChannelManager.channel():getPackageIndex() >= 20220530 then
if device.platform == "android" then
local ok
local luaj = require "cocos.cocos2d.luaj"
ok, debugPkg = luaj.callStaticMethod("org.cocos2dx.lua.GameUtil", "isDebugPackage", {}, "()Z")
print(tostring(ok), tostring(debugPkg))
end
end
if G_ChannelManager.isShowErrorAlert() or debugPkg then
local __G__TRACKBACK__OLD = __G__TRACKBACK__
function __G__TRACKBACK__(msg)
require("app.scenes.common.Alert").showSystemError(msg)
__G__TRACKBACK__OLD(msg)
end
end
end
--热更弹窗
function UpdateLayer:_showUpdateAlert()
local updateAlert
local function onSelect(index)
updateAlert:hideComplete() --掉hide效果不太好,停留时间有点长
self._progress:setPercentage(0)
self._text_per:setString("0%")
self._updateCheck:startDownload(index)
self._md5DownloadStart = true
end
local alert = require("version.UpdateAlert").new(onSelect, self._updateCheck)
updateAlert = require("version.AlertControl").new(alert)
updateAlert:show()
end
--整包更新弹窗
function UpdateLayer:_showUpdatePackageAlert()
local updateAlert
local alert = require("version.UpdatePackageAlert").new(
function ()
updateAlert:hide()
self:_startUpdateClient()
end
)
updateAlert = require("version.AlertControl").new(alert)
updateAlert:show()
end
function UpdateLayer:_setRemoteVersion()
self._remoteVersion:setVisible(true)
self._remoteVersion:setString("REMOTE: " .. G_Version:getRemoteVersionString())
end
--必须要在同意隐私之后才可以接入bugly,避免bugly提前收集用户信息导致不过审
function UpdateLayer:_buglyInit()
if version.Version.initBugly then
version.Version:initBugly()
print("*** buglyInit end")
end
end
return UpdateLayer
2.热更逻辑文件:UpdateCheck.lua
local cjson = require("cjson")
local UpdateCheck = class("UpdateCheck")
local downPath = cc.FileUtils:getInstance():getWritablePath() .. "down/" --新文件下载目录
local hotPath = cc.FileUtils:getInstance():getWritablePath() .. "version/" --热更文件所在目录
local midPath = "/version"
local AssetJsonPath = "res/versionAsset.json" --资源文件(获取下载内容)这里不能加斜杠:/res/versionAsset.json
function UpdateCheck:ctor(_processFunc,_errorFunc,_setRemoteUI)
self._updateFlag = 0
self._versionString = "" --远程版本号
self._maxHttpNum = 5 --最大请求数量
self._curHttpNum = 0 --当前请求数量
self._maxErrorNum = 3 --下载错误最大次数
self._errorMap = {} --下载错误次数(md5对不上)
self._downSize = 0 --已下载大小
self._totalDownSize = 0 --下载总大小
self._totalGetSize = 0 --已下载大小
self._toDownloadFiles = {} --需要下载未下载文件 --{{"res/cfg/error_msg_master.luac", 6075, md5},{...}}
self._toDeleteFiles = {} --需要删除文件
self._downloadedFileNames = {} --已下载文件
self._iosCommitMode = false --ios提审状态
self._processFunc = _processFunc
self._errorFunc = _errorFunc
self._setRemoteUI = _setRemoteUI
self._flagHotUpdateTestWIn32 = false --开发机测试用(上传svn时必须为false别忘了改)
if self._flagHotUpdateTestWIn32 then
local cltVersionObj = G_Version:getLocalVersionInfo()
cltVersionObj._id = 999999
end
self._timeTipShowed = false
self:_init()
end
function UpdateCheck:_init()
local function createDir(path)
if not cc.FileUtils:getInstance():isDirectoryExist(path) then
cc.FileUtils:getInstance():createDirectory(path)
end
end
createDir(hotPath)
createDir(hotPath..'src/')
createDir(hotPath..'res/')
end
--注意下载发起下载文件的时候用newRequest不要用sendHttpRequest
function UpdateCheck:_sendHttpRequest(fileName, fileIndex, url, successCb)
local xhr = cc.XMLHttpRequest:new()
xhr.responseType = cc.XMLHTTPREQUEST_RESPONSE_STRING--cc.XMLHTTPREQUEST_RESPONSE_ARRAY_BUFFER
xhr:setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8")
xhr:open("GET", url)
xhr:registerScriptHandler(function()
self._curHttpNum = self._curHttpNum - 1
if xhr.readyState == 4 and 200 <= xhr.status and xhr.status < 207 then
successCb(xhr.response, 1)
else
--这里做失败处理,失败了重新下载相同文件
self:_httpDownloadErrorFunc(fileName, fileIndex)
end
xhr:unregisterScriptHandler()
end)
xhr:send()
end
--通过消息获取远程版本号
function UpdateCheck:getVersionInfo()
local function completeCall()
G_Version:checkRemoteVersion(self._versionString, self._processFunc)
end
if G_ChannelManager.isUpdateOpen() then
local request = require("version.GetVersionRequest").new()
request:send(nil, function(status, data)
if 0 == status then
self._versionString = data.ver
self._iosInAppStore = tonumber(data.ver_ios_audit) or 1
local cltVersionObj = G_Version:getLocalVersionInfo()
local cltID = tonumber(cltVersionObj:getID())
print("IOS提审:","平台为IOS:",device.platform == "ios","clientId:",cltID, "下发id:",self._iosInAppStore)
if device.platform == "ios" and cltID == self._iosInAppStore then
--IOS提审功能,约定:服务端下发版号和包内版号一致时为提审状态
--举例:IOS线上版本596,IOS新包版本597,新包提审时,服务器发597,过了提审后,服务器发非597,非596
self._iosCommitMode = true
self._updateFlag = 0
self._processFunc(100, "加载中...(100%)")
else
G_Scheduler.setDelay(completeCall, 0.5, true)
end
end
end)
else
G_Scheduler.setDelay(completeCall, 0.5, true)
end
end
function UpdateCheck:startHotUpdateLogic(completeCall, errorCall, progressCall)
local cltVersionObj = G_Version:getLocalVersionInfo()
local svrVersionObj = G_Version:getRemoteVersionInfo()
local cltID = cltVersionObj:getID()
local svrID = svrVersionObj:getID()
local cltTag = cltVersionObj:getTag()
local svrTag = svrVersionObj:getTag()
self._md5HotUpdated = cc.UserDefault:getInstance():getBoolForKey("MD5HOTUPDATED", false)
print("***","startHotUpdateLogic",cltID.."."..cltTag,svrID.."."..svrTag,"已md5热更:",self._md5HotUpdated,"整包更新:",cltID < svrID,"热更新:",cltTag < svrTag)
print("***","是否白名单:", svrVersionObj:getIsWhiteList())
if not self._md5HotUpdated then
--强制热更新
self:_getServerAssetRequest()
elseif cltID < svrID then
--整包更新
self._updateFlag = 3
self._processFunc(100, "检查更新...(100%)")
elseif cltTag < svrTag then
--热更新
self:_getServerAssetRequest()
else
--不用更新
self._updateFlag = 0
self._processFunc(100, "检查更新...(100%)")
end
if self._setRemoteUI then
self._setRemoteUI()
end
end
function UpdateCheck:_getServerAssetRequest()
--获取本地资源文件
--取develop/res/versionAsset.json时,AssetJsonPath取/res/versionAsset.json时在ios设备上读不到,需要去掉/
local versionFilePath = AssetJsonPath
if cc.FileUtils:getInstance():isFileExist(hotPath .. AssetJsonPath) then
versionFilePath = hotPath .. AssetJsonPath
end
local isExist = cc.FileUtils:getInstance():isFileExist(versionFilePath)
local jsonData = cc.FileUtils:getInstance():getFileData(versionFilePath)
self._clientAssetData = cjson.decode(jsonData)
print("*** _getServerAssetRequest", self._clientAssetData == nil, jsonData == nil, isExist, versionFilePath)
self:_downloadFileAssetPath()
end
--获取服务器资源文件
function UpdateCheck:_downloadFileAssetPath()
local url = self:_getHttpUrl(1, "/" .. AssetJsonPath)
if url == nil then
return
end
print("### AssetJsonPath url:", url)
self:_sendHttpRequest(AssetJsonPath, 0, url, function(response)
--接收到资源文件大约需要3秒
self:_saveFile(downPath .. AssetJsonPath, response, AssetJsonPath)
self._processFunc(55, "检查更新...(55%)")
G_Scheduler.setDelay(handler(self,function()
self._responseAsset = response
local serverAssetData = cjson.decode(response)
--不验证md5的情况下,生成差异文件大约需要2秒
self:_getUpdateFiles(self._clientAssetData, serverAssetData)
end),0.8)
end)
end
function UpdateCheck:_getUpdateFiles(cltData, svrData)
--oneCltFile: "res/cfg/error_msg_master.luac" = {"285bbfb9c55c313fea68a6c546939ac5", 6075}
--cltData有的文件在APP中有的在hot中
local function checkDownload(fileName, size, md5)
local ret = self:_isNeedDownload(fileName, md5)
if 1 == ret then
return true
elseif true == ret then
table.insert(self._toDownloadFiles, {fileName, size, md5})
self._totalDownSize = self._totalDownSize + size
end
return false
end
local function showTip()
self._processFunc(68, "上次更新中断,正在进行文件校验,此过程可能需要较长时间...(68%)")
self._toDownloadFiles = {}
self._downloadedFileNames = {}
self._totalDownSize = 0
end
for fileName, oneSvrFile in pairs(svrData.assets) do
local oneCltFile = cltData.assets[fileName]
if oneCltFile then
--客户端有这个文件且MD5不同
if oneCltFile[1] ~= oneSvrFile[1] then
if checkDownload(fileName, oneSvrFile[2], oneSvrFile[1]) then
showTip()
break
end
end
else
--客户端没有这个文件
if checkDownload(fileName, oneSvrFile[2], oneSvrFile[1]) then
showTip()
break
end
end
end
local function calcLogic()
if self._timeTipShowed then
for fileName, oneSvrFile in pairs(svrData.assets) do
local oneCltFile = cltData.assets[fileName]
if oneCltFile then
--客户端有这个文件且MD5不同
if oneCltFile[1] ~= oneSvrFile[1] then
checkDownload(fileName, oneSvrFile[2], oneSvrFile[1])
end
else
--客户端没有这个文件
checkDownload(fileName, oneSvrFile[2], oneSvrFile[1])
end
end
end
if 0 < table.nums(self._toDownloadFiles) then
self._updateFlag = 1
elseif 0 == table.nums(self._toDownloadFiles) and not self._md5HotUpdated then
--强制热更,且没有更新文件
self._updateFlag = 5
table.insert(self._downloadedFileNames, AssetJsonPath)
self:_handleFiles()
cc.UserDefault:getInstance():setBoolForKey("MD5HOTUPDATED", true)
elseif 0 == table.nums(self._toDownloadFiles) and self._md5HotUpdated then
self._updateFlag = 2
end
for fileName, oneCltFile in pairs(cltData.assets) do
local oneSvrFile = svrData.assets[fileName]
if not oneSvrFile then
--服务端没有这个文件
table.insert(self._toDeleteFiles, fileName)
end
end
self:_checkCopyBreak()
self._processFunc(100, "检查更新...(100%)")
end
local time = self._timeTipShowed and 0.5 or 0
G_Scheduler.setDelay(calcLogic, time ,true)
end
--判断该文件是否需要下载,实现断点续传
function UpdateCheck:_isNeedDownload(name, md5)
local downloadFilePath = downPath .. name
local md5Value = md5
if version.Version.getFileMD5Hash then
md5Value = version.Version:getFileMD5Hash(downloadFilePath)
end
local fileExist = cc.FileUtils:getInstance():isFileExist(downloadFilePath)
if fileExist and md5Value == md5 then
if self._timeTipShowed then
table.insert(self._downloadedFileNames, name)
return false
else
--走这里代表上次下载中断
self._timeTipShowed = true
return 1
end
else
return true
end
end
function UpdateCheck:_copyFile(src, dest, index)
local sourceFile, errorString = io.open(src, "rb")
if sourceFile == nil then
return
end
local len = sourceFile:seek("end")
sourceFile:seek("set", 0)
local data = sourceFile:read(len)
sourceFile:close()
if not data then --不应该走进来
print("_copyFile error ", src, dest)
return
end
local pInfo = io.pathinfo(dest)
if not cc.FileUtils:getInstance():isFileExist(dest) then
cc.FileUtils:getInstance():createDirectory(pInfo.dirname)
end
local destFile = io.open(dest, "wb")
destFile:write(data)
destFile:close()
end
function UpdateCheck:_saveFile(absolutePath, response, relativePath)
if relativePath ~= AssetJsonPath then
table.insert(self._downloadedFileNames, relativePath)
end
local pInfo = io.pathinfo(absolutePath)
cc.FileUtils:getInstance():createDirectory(pInfo.dirname)
io.writefile(absolutePath, response, "wb")
end
--请求文件下载并检测完成,这里注意实际下载完成和进度条走到100%不同步,需要保证任意时间点尤其是100%时断线重连的逻辑正常
function UpdateCheck:_frameCheck()
if 0 == table.nums(self._toDownloadFiles) then
--下载完成
--将版本文件放在最后,为了让其最后被拷贝,这样在拷贝过程中断会检测出来
table.insert(self._downloadedFileNames, AssetJsonPath)
G_Scheduler.remove(self._frameHandler)
self:_handleFiles()
self:downloadComplete()
G_ChannelManager.channel():onGameInnerUpdateEnd(true)
G_Version:restartGame()
else
if self._curHttpNum < self._maxHttpNum and self._curHttpNum < table.nums(self._toDownloadFiles) then
for index, downElement in pairs(self._toDownloadFiles) do
if not downElement[4] then --[1]文件名 [2]大小 [3]md5 [4]是否正在下载(发起下载时downElement[4] = true)
self:_newRequest(index)
break
end
end
end
end
end
function UpdateCheck:_handleFiles()
--将下载目录下文件移到hotPath目录中
for k, fileName in ipairs(self._downloadedFileNames) do
self:_copyFile(downPath..fileName, hotPath..fileName, k)
end
local downPath = cc.FileUtils:getInstance():getWritablePath() .. "down/"
cc.FileUtils:getInstance():removeDirectory(downPath)
self:_replaceVersionFile()
--在hotPath下删除不需要的文件
for k, fileName in ipairs(self._toDeleteFiles) do
cc.FileUtils:getInstance():removeFile(hotPath..fileName)
end
end
function UpdateCheck:_replaceVersionFile()
--删除老版号文件
os.remove(cc.FileUtils:getInstance():getWritablePath() .. "version.data")
--生效新版号文件
local tempVersionFile = cc.FileUtils:getInstance():getWritablePath() .. "version.data.temp"
local versionFile = cc.FileUtils:getInstance():getWritablePath() .. "version.data"
os.rename(tempVersionFile, versionFile)
end
function UpdateCheck:downloadComplete()
self:_clearData()
cc.Director:getInstance():getActionManager():removeAllActions()
cc.Director:getInstance():getTextureCache():removeAllTextures()
cc.SpriteFrameCache:getInstance():removeSpriteFrames()
end
function UpdateCheck:_getHttpUrl(selIndex, fileName)
local svrVersionObj = G_Version:getRemoteVersionInfo()
local svrTag = svrVersionObj:getTag()
local postfix = "?" .. svrTag --url增加后缀可以每次请求到最新的文件(不加的话,SVN修改已有文件后上传,需要运维刷新缓存才会访问到最新的)
local remoteData = G_Version:getRemoteVersionInfo()
local whiteListStr = remoteData:getIsWhiteList() and "/white" or ""
local url = remoteData:getUpdateURL(selIndex).. midPath .. whiteListStr .. fileName .. postfix
if device.platform == "windows" then--开发机上url用固定值,因为从文件中读到的不对
url = "https://cdn-xf-studio.uuzuonline.com/171/mobile/innerup/youzu" .. midPath .. whiteListStr .. fileName .. postfix
end
return url
end
--请求文件下载
function UpdateCheck:_newRequest(index)
local downElement = self._toDownloadFiles[index]
local downFileName = "/"..downElement[1] --这里需要加正斜杠
local downFileSize = downElement[2]
local downFileMd5 = downElement[3]
local selUrlIdx = self._urlIndex or 1
local url = self:_getHttpUrl(selUrlIdx, downFileName)
downElement[4] = true
self._curHttpNum = self._curHttpNum + 1
self:_sendHttpRequest(downFileName, index, url, function(response)
self:_saveFile(downPath .. downFileName, response, downFileName)
local md5Value = downFileMd5
if version.Version.getFileMD5Hash then
md5Value = version.Version:getFileMD5Hash(downPath .. downFileName)
end
if md5Value == downFileMd5 then
--进行md5验证,因为下载的文件有可能是错误的(网络不好,被劫持,作弊等情况)
--不验证的话,文件可能下载到的内容为nil,copy文件时会报错
self._errorMap[downFileName] = nil
self._totalGetSize = self._totalGetSize + downFileSize
self._toDownloadFiles[index] = nil
local percent = math.floor(self._totalGetSize / self._totalDownSize * 100)
local mb = self._totalGetSize / 1024 / 1024
local tmb = self._totalDownSize / 1024 / 1024
local size1 = string.format("%.2f", mb)
local size2 = string.format("%.2f", tmb)
self._processFunc(percent, "下载版本...(" .. percent .. "%)")
else
print("***md5 error:"..index, downFileName,os.time())
self:_httpDownloadErrorFunc(downFileName, index)
end
end)
end
function UpdateCheck:_httpDownloadErrorFunc(downFileName, fileIndex)
if self._errorMap[downFileName] then
self._errorMap[downFileName] = self._errorMap[downFileName] + 1
else
self._errorMap[downFileName] = 1
end
local function downloadFileAgain()
if 0 == fileIndex then --versionAsset.json下载失败
self:_downloadFileAssetPath()
else
self:_newRequest(fileIndex)
end
end
if self._errorMap[downFileName] < self._maxErrorNum then
downloadFileAgain()
else
print(downFileName .. " download error "..self._maxErrorNum .. " times")
local msg = "文件" .. downFileName .. "多次下载失败,请确保网络通畅后,点击确定重新下载"
local SceneManager = require("app.scenes.SceneManager")
if not self._alertPanel then
G_Scheduler.remove(self._frameHandler)
self._alertPanel = SceneManager.alert(msg, 0, function()
G_Version:restartGame()
end)
end
end
end
--请求文件下载并检测完成
function UpdateCheck:startDownload(urlIndex)
--G_Scheduler.setUpdate的回调只有在客户端空闲时会调度!
self._urlIndex = urlIndex
self._frameHandler = G_Scheduler.setUpdate(handler(self, self._frameCheck))
end
function UpdateCheck:_clearData()
self._errorMap = {}
self._toDownloadFiles = {}
self._toDeleteFiles = {}
self._downloadedFileNames = {}
end
--无需更新
function UpdateCheck:isNotNeedUpdate()
return self._updateFlag == 0
end
--有下载的热更新
function UpdateCheck:isDownloadUpdate()
return self._updateFlag == 1
end
--无下载的热更新
function UpdateCheck:isNoDownloadUpdate()
return self._updateFlag == 2
end
--整包更新
function UpdateCheck:isNeedUpdateClient()
return self._updateFlag == 3
end
--拷贝中断
function UpdateCheck:isCopyBreak()
return self._updateFlag == 4
end
--强制热更新且没有文件需要下载
function UpdateCheck:isForceHotNoDownload()
return self._updateFlag == 5
end
--检测复制文件中断的情况
function UpdateCheck:_checkCopyBreak()
if 1 < table.nums(self._downloadedFileNames) and 0 == table.nums(self._toDownloadFiles) then
print("###################### 警告:上次拷贝中断,将继续拷贝")
table.insert(self._downloadedFileNames, AssetJsonPath)
self._updateFlag = 4
self:_handleFiles()
end
end
--检测无下载文件的更新
function UpdateCheck:doNoDownloadUpdate()
self:_copyFile(downPath..AssetJsonPath, hotPath..AssetJsonPath)
cc.FileUtils:getInstance():removeDirectory(downPath)
for k, fileName in ipairs(self._toDeleteFiles) do
cc.FileUtils:getInstance():removeFile(hotPath..fileName)
end
self:_replaceVersionFile()
self:downloadComplete()
end
function UpdateCheck:getTotalDownSize()
return self._totalDownSize
end
function UpdateCheck:getIOSCommitMode()
return self._iosCommitMode
end
return UpdateCheck
3.热更辅助文件Version.lua
require("version.tools")
local SceneManager = require("app.scenes.SceneManager")
local Version = class("Version")
Version.VERSION_FILE_NAME = "version.data" --本地版号文件名, tag(SVN的版号)用来判断热更,id用来判断整包更新
Version.VERSION_FILE_TEMP_NAME = "version.data.temp" --下载的版号文件名
Version.URL_FILE_NAME = "urlconfig"
Version.LOCALVERPATH = cc.FileUtils:getInstance():getWritablePath() .. Version.VERSION_FILE_NAME
Version.LOCALTEMPPATH = cc.FileUtils:getInstance():getWritablePath() .. Version.VERSION_FILE_TEMP_NAME
Version.NO_ERROR = 0
Version.ERROR_NET = 1
Version.ERROR_UNZIP = 2
--预启动
function Version.preSetup()
--!!!注意addSearchPath之前执行的一定是包体内代码
--为了避免覆盖安装后,热更目录中的热更之前代码错误,新包则无法修正
--要求新包第一次成功热更之前不要执行热更目录代码
local fileUtil = cc.FileUtils:getInstance()
local localVersionDir = fileUtil:getWritablePath() .. "version/"
--包体内的版号文件:develop/res/version.data
local pkgVerFilePath = fileUtil:fullPathForFilename(Version.VERSION_FILE_NAME)
local pkgVerFileData = Version._readVersionInfoFromFile(pkgVerFilePath)
local pkgVerFileId = pkgVerFileData:getID()
--热更目录下的版号文件:develop/version.data
--isVerExist为true代表经历过一次成功热更
local isVerExist = cc.FileUtils:getInstance():isFileExist(Version.LOCALVERPATH)
--isVerExist为true代表热更中途退出
local isVerTempExist = cc.FileUtils:getInstance():isFileExist(Version.LOCALTEMPPATH)
print("Version.preSetup() isVerExist=",isVerExist,"isVerTempExist=",isVerTempExist)
--lastPkgId可以理解为发起热更时记录的包内版号文件的大版本号
local lastPkgId = cc.UserDefault:getInstance():getIntegerForKey("LastPkgId", 0)
local packageChanged = false
if lastPkgId == 0 and (not isVerExist) and (not isVerTempExist) then
--新包裸装
packageChanged = false
print("Version.preSetup() A 新包裸装")
elseif lastPkgId == 0 and (not isVerExist) and isVerTempExist then
--旧包没有经历一次成功热更就覆盖安装
packageChanged = true
print("Version.preSetup() A 旧包没有经历一次成功热更就覆盖安装")
elseif lastPkgId == 0 and isVerExist then
--没有在20220411之后热更过,覆盖安装
packageChanged = true
print("Version.preSetup() A 没有在20220411之后热更过,覆盖安装")
elseif 0 < lastPkgId and lastPkgId ~= pkgVerFileId then
--在20220411之后热更过,覆盖安装
packageChanged = true
print("Version.preSetup() A 在20220411之后热更过,覆盖安装")
elseif 0 < lastPkgId and lastPkgId == pkgVerFileId then
--正常进入游戏
packageChanged = false
print("Version.preSetup() A 正常进入游戏", lastPkgId, pkgVerFileId)
end
print("Version.preSetup() B", "packageChanged =", packageChanged,"lastPkgId =", lastPkgId, "pkgVerFileId =", pkgVerFileId)
if packageChanged then
--0.覆盖安装(老包不删除,安装新包)走这里
fileUtil:removeDirectory(localVersionDir)
os.remove(fileUtil:getWritablePath() .. "version.data")--删除版号文件
print("Version.preSetup() C removeDirectory")
end
Version.addVerSearchPath()
cc.UserDefault:getInstance():setIntegerForKey("LastPkgId", pkgVerFileId)
--销毁自身缓存,便于在setup中加载最新的version.lua
package.loaded["version.Version"] = nil
package.loaded["version.VersionInfo"] = nil
fileUtil:purgeCachedEntries() --清理文件查找缓存
end
function Version.addVerSearchPath()
local localVersionDir = cc.FileUtils:getInstance():getWritablePath() .. "version/"
cc.FileUtils:getInstance():addSearchPath(localVersionDir .. "src/", true)
cc.FileUtils:getInstance():addSearchPath(localVersionDir .. "res/", true)
end
--启动:
-- 初始化自身;
-- 导入cocos\framework\config;
-- 启动游戏;
-- 警告:方法名不能修改,否则无法启动
function Version.setup(hasInitSDK, autoOpenLogin)
--- 初始化自身 ---
Version.enableGlobal()
G_Version = require("version.Version").new()
G_Version:init()
require "config"
require "cocos.init"
require "framework.Framework"
--- 启动游戏 ---
local function main()
require("app.MyApp").new():run(hasInitSDK, autoOpenLogin)
end
local status, msg =
xpcall(main,
function (msg)
local msg = debug.traceback(msg, 3)
print(msg)
return msg
end
)
if not status then
print(msg)
end
end
function Version.enableGlobal()
setmetatable(_G, {})
end
function Version:ctor()
self._localVersionInfo = nil --本地版号文件读取的对象
self._remoteVersionInfo = nil --远程版号文件读取的对象
self._versionCompareCode = 0
self._hasAddVersionPathToSearchPath = false
self._remoteSimpleVersion = 0
end
--移除非系统全局变量和非系统模块
function Version:_cleanEnv()
local needRemoveLoad = {}
for k, v in pairs(package.loaded) do
if not _originalLoaded[k] then
table.insert(needRemoveLoad, k)
end
end
for i = 1, #needRemoveLoad do
package.loaded[needRemoveLoad[i]] = nil
end
local needRemoveG = {}
for k, v in pairs(_G) do
if not _originalG[k] then
table.insert(needRemoveG, k)
end
end
for i = 1, #needRemoveG do
_G[needRemoveG[i]] = nil
end
end
--让新包覆盖安装可以进行完整热更新
--老包更新不执行,新包裸装不执行,老包不删除安装新包后只执行一次
function Version:removeHotDirForNewPackage()
local packageIndex = G_ChannelManager.channel():getPackageIndex() --2022.3出的包为20211217
if not packageIndex then return false end
print("delete version logic step 1","packageIndex", packageIndex, "go on", packageIndex and 20211217 <= packageIndex)
if 20211217 <= packageIndex then
local lastLoginPkgIdx = cc.UserDefault:getInstance():getIntegerForKey("LastLoginPkgIdx", 0)
print("delete version logic step 2", "lastLoginPkgIdx", lastLoginPkgIdx, "go on", lastLoginPkgIdx ~= 0 and lastLoginPkgIdx < packageIndex)
if lastLoginPkgIdx ~= 0 and lastLoginPkgIdx < packageIndex then
local localVersionDir = cc.FileUtils:getInstance():getWritablePath() .. "version/"
local versionDirExist = cc.FileUtils:getInstance():isDirectoryExist(localVersionDir)
print("delete version logic step 3", "versionDirExist", versionDirExist)
if versionDirExist then
print("delete version logic step 4", "remove versionDir executing")
cc.FileUtils:getInstance():removeDirectory(localVersionDir)
os.remove(cc.FileUtils:getInstance():getWritablePath() .. "version.data")--删除版号文件
cc.UserDefault:getInstance():setIntegerForKey("LastLoginPkgIdx", packageIndex)
self:restartGame()
return true
else
cc.UserDefault:getInstance():setIntegerForKey("LastLoginPkgIdx", packageIndex)
return false
end
end
end
return false
end
--还原游戏运行环境,重启游戏
function Version:restartGame(autoOpenLogin)
local node = cc.Director:getInstance():getNotificationNode()
if node then
cc.Director:getInstance():setNotificationNode(nil)
end
-- 清理延时逻辑
G_Scheduler.removeAll()
-- 清理hold纹理
G_Texture.unholdAllTexture()
-- 清理文件查找缓存
cc.FileUtils:getInstance():purgeCachedEntries()
-- 清理音效
G_AudioManager:destroy()
--清理聊天历史消息管理类
require("app.scenes.chat.tool.ChatHistoryStore").getInstance():dispose()
local scene = cc.Scene:create()
cc.Director:getInstance():replaceScene(scene)
scene:registerScriptHandler(
function(state)
if state == "enterTransitionFinish" then
self:_cleanEnv()
Version.setup(false, autoOpenLogin)
end
end
)
end
--初始化本地版本信息
function Version:init()
local fileUtil = cc.FileUtils:getInstance()
local downloadedFilePath = fileUtil:getWritablePath() .. Version.VERSION_FILE_NAME
local originalFilePath = fileUtil:fullPathForFilename(Version.VERSION_FILE_NAME)
--已下载的版号文件:develop/version.data
local downloadedFileData = Version._readVersionInfoFromFile(downloadedFilePath)
--包内自带的版号文件:develop/res/version.data
local originalFileData = Version._readVersionInfoFromFile(originalFilePath)
if downloadedFileData then
self._hasAddVersionPathToSearchPath = true
self._localVersionInfo = downloadedFileData
else
self._localVersionInfo = originalFileData
end
end
--获取本地最新版本信息
function Version:getLocalVersionInfo()
return self._localVersionInfo
end
--获取远程版本信息
function Version:getRemoteVersionInfo()
return self._remoteVersionInfo
end
--初始化远程版本地址
function Version:setRemoteURL(remoteURL)
self._remoteURL = remoteURL
end
function Version:getRemoteURL()
return self._remoteURL
end
function Version:getLocalVersionString()
return self._localVersionInfo:getID().. "." .. self._localVersionInfo:getTag()
end
function Version:getRemoteVersionString()
if self._remoteVersionInfo then
return self._remoteVersionInfo:getID().. "." .. self._remoteVersionInfo:getTag()
else
return ""
end
end
--[[
版号文件处理流程:
0.包体内存在的版号文件为develop/res/version.data
1.下载后生效的版号文件为develop/version.data
2.下载后未生效的版号文件为develop/version.data.temp
3.develop/version.data存在, 本地版号文件就取develop/version.data
4.develop/version.data不存在, 本地版号文件就取develop/res/version.data
5.下载完成后删除:develop/version.data
6.将develop/version.data.temp改名为develop/version.data
--]]
--请求服务器版号文件, 保存为develop下的version.data.temp
function Version:checkRemoteVersion(versionString, processFunc)
self._processFunc = processFunc
local game_id = G_ChannelManager.channel():getGameId()
local opgame_id = G_ChannelManager.channel():getOpgameId()
local op_id = G_ChannelManager.channel():getOpId()
self._loader = require("version.loader.Loader").new()
self._remoteUrl = string.format("%s/%d_%d_%d_%s.data?%s",self._remoteURL,game_id,opgame_id,op_id,versionString,tostring(os.time()))
self._errorTimes = 0
self._loader:add(self._remoteUrl, Version.LOCALTEMPPATH)
self._loader:load(handler(self, self._downRemoteComplete), handler(self, self._downRemoteError))
end
function Version:_downRemoteComplete()
self:_checkRemoteRight()
end
function Version:setRemoteSimpleVersion(data)
self._remoteSimpleVersion = data --这里会赋值一个number,eg:706.53456
end
function Version:getRemoteSimpleVersion()
return self._remoteSimpleVersion
end
function Version:_downRemoteError()
--已知情况:当连续调用2次loader:load()时候会出现error
print("******************!!!!!! checkRemoteVersion download error")
end
function Version:_checkRemoteRight()
self._remoteVersionInfo = Version._readVersionInfoFromFile(Version.LOCALTEMPPATH)
if self._remoteVersionInfo:getUpdateURL(1) then
self._processFunc(9, "检查更新...(9%)")
G_updateCheck:startHotUpdateLogic()
else
--网络不好时下载的self._remoteVersionInfo的各字段都为0
--这里反复下载避免玩家热更过程报错,卡0%,这里逻辑很重要
self._errorTimes = self._errorTimes + 1
if 3 <= self._errorTimes then
self._alertPanel = SceneManager.alert("下载的版号文件多次错误,请网络稳定后点击确定重进游戏", 0, function()
self:restartGame()
end)
else
self._loader:add(self._remoteUrl, Version.LOCALTEMPPATH)
self._loader:load(handler(self, self._downRemoteComplete), handler(self, self._downRemoteError))
end
end
end
--下载远程版本,并更新至最新版本
function Version:downloadRemoteVersion(remoteUrl, completeCall, progressCall, errorCall)
local loader = require("version.loader.Loader").new()
local url = string.format("%s/version/%d_%d.zip", remoteUrl, self._remoteVersionInfo:getID(), self._localVersionInfo:getID())
local localPath = string.format("%sversion/update.zip", cc.FileUtils:getInstance():getWritablePath())
loader:add(url, localPath)
local function onLoadProgress()
progressCall(loader:getContentLengthLoaded() / loader:getContentLengthTotal())
end
local function onLoadError()
errorCall(Version.ERROR_NET)
end
loader:load(completeCall, onLoadError, onLoadProgress)
end
--解压已下载版本文件
function Version:decompressVersion(completeCall, progressCall, errorCall)
local localPath = string.format("%sversion/update.zip", cc.FileUtils:getInstance():getWritablePath())
local localVersionDirTemp = cc.FileUtils:getInstance():getWritablePath() .. "version_temp/"
local localVersionDir = cc.FileUtils:getInstance():getWritablePath() .. "version/"
local function onUnCompressCall(result)
if result then
progressCall(0.6)
local function onComplete(value)
local result = cc.FileUtils:getInstance():removeDirectory(localVersionDirTemp)
if value then
--- 添加搜索
if not self._hasAddVersionPathToSearchPath then
cc.FileUtils:getInstance():addSearchPath(localVersionDir .. "src/", true)
cc.FileUtils:getInstance():addSearchPath(localVersionDir .. "res/", true)
end
progressCall(0.7)
--- 删除更新包
os.remove(localPath)
progressCall(0.8)
--- 删除老版本信息
os.remove(cc.FileUtils:getInstance():getWritablePath() .. Version.VERSION_FILE_NAME)
progressCall(0.9)
--- 生效新版本
local tempVersionFile = cc.FileUtils:getInstance():getWritablePath() .. Version.VERSION_FILE_TEMP_NAME
local versionFile = cc.FileUtils:getInstance():getWritablePath() .. Version.VERSION_FILE_NAME
os.rename(tempVersionFile, versionFile)
progressCall(1)
self._localVersionInfo = self._remoteVersionInfo
completeCall()
else
errorCall(Version.ERROR_UNZIP)
end
end
version.Version:copyDir(localVersionDirTemp, localVersionDir, onComplete)
else
errorCall(Version.ERROR_UNZIP)
end
end
version.Version:uncompressDir(localPath, localVersionDirTemp, onUnCompressCall)
end
--更新客户端
function Version:updateClient()
cc.Application:getInstance():openURL(self._updateClientURL)
end
function Version._readVersionInfoFromFile(path)
if cc.FileUtils:getInstance():isFileExist(path) then
local data = cc.FileUtils:getInstance():getFileData(path)
if data then
return Version._parserVersionInfo(data)
end
end
return nil
end
function Version._parserVersionInfo(data)
local versionInfo = require("version.VersionInfo").new()
versionInfo:setData(data)
return versionInfo
end
--获取更新整包地址
function Version:getUpdateUrl(completeCall, progressCall, errorCall)
local loader = require("version.loader.Loader").new()
local url = string.format("%s/%s?%s",
self._remoteURL,
Version.URL_FILE_NAME,
tostring(os.time()))
local localPath = cc.FileUtils:getInstance():getWritablePath() .. Version.URL_FILE_NAME
if cc.FileUtils:getInstance():isFileExist(localPath) then
os.remove(localPath)
end
loader:add(url, localPath)
local function onLoadComplete()
local size = cc.FileUtils:getInstance():getFileSize(localPath)
if size == 0 then
errorCall(Version.ERROR_NET)
else
completeCall()
end
end
local function onLoadError()
errorCall(Version.ERROR_NET)
end
loader:load(onLoadComplete, onLoadError)
end
--根据op_id和包名解析整包下载地址
function Version:parseUpdateUrl(completeCall, progressCall, errorCall)
local localPath = cc.FileUtils:getInstance():getWritablePath() .. Version.URL_FILE_NAME
if not cc.FileUtils:getInstance():isFileExist(localPath) then
errorCall(Version.ERROR_NET)
end
local str = cc.FileUtils:getInstance():getStringFromFile(localPath)
local ret,msg = loadstring(str)
local url
if ret then
local config = ret()
if config and type(config) == "table" then
local bounder_id = G_ChannelManager.channel():getPackageName()
local op_id = G_ChannelManager.channel():getOpId()
for i=1,#config do
if tostring(config[i][1]) == tostring(op_id) and tostring(config[i][2]) == tostring(bounder_id) then
url = tostring(config[i][4])
break
end
end
end
end
if not url then
errorCall(1)
else
self._updateClientURL = url
completeCall()
end
end
return Version