cocos2dx lua 熱更新

原理:

每次登陸游戲利用cocos的assetManager從服務(wù)器拉去當(dāng)前最新的兩個文件嘹吨。 一個是version.mainifest,一個project.mainifest. 這兩個文件都是xml的描述文件。一個包含了版本信息沿猜,第二個包含了游戲所有資源的MD5碼。首先通過version文件對比本地的版本是否相同夯到,如果不相同房铭,再通過跟本地的project文件對比MD5碼來判斷哪些文件需要重新下載,替換資源载慈。

步驟:

1. 有一個文件下載的熱更新服務(wù)器,將最新項目資源(res/ src/ 目錄)放入熱更新服務(wù)器中珍手,添加版本信息母文件(version_info.json)和python腳本文件eneateManifest.py(生成project.manifest办铡、version.manifest文件)。

2.version_info.json文件: 主要用來配置信息

{
    "packageUrl" : "http://ip:port/update/MyProj/assets/",
    "remoteManifestUrl" : "http://ip:port/update/MyProj/version/project.manifest",
    "remoteVersionUrl" : "http://ip:port/update/MyProj/version/version.manifest",
    "engineVersion" : "3.3",
    "update_channel" : "Android",
    "bundle" : "2018111701",
    "version" : "1.0.0",
}

3.eneateManifest.py文件: 這個文件是一個python琳要。目的是生成對應(yīng)的version和project文件寡具。project文件可以幫你給每個資源生成獨一無二的MD5碼,相當(dāng)于每個資源的標(biāo)記稚补。下面是一段python文件的代碼童叠。

#coding:utf-8
 
import os
import sys
import json
import hashlib
import subprocess
import getpass
 
username = getpass.getuser()
# 改變當(dāng)前工作目錄
#os.chdir('/Users/' + username + '/Documents/client/MyProj/')
 
assetsDir = {
    #MyProj文件夾下需要進(jìn)行熱跟的文件夾
    "searchDir" : ["src", "res"],
    #需要忽略的文件夾
    "ignorDir" : ["cocos", "framework", ".svn"],
    #需要忽略的文件
    "ignorFile":[".DS_Store"],
}
 
versionConfigFile   = "version/version_info.json"  #版本信息的配置文件路徑
versionManifestPath = "version/version.manifest"    #由此腳本生成的version.manifest文件路徑
projectManifestPath = "version/project.manifest"    #由此腳本生成的project.manifest文件路徑
# projectManifestPath = "/Users/ximi/Documents/client/MyProj/res/version/project.manifest"    #由此腳本生成的project.manifest文件路徑(mac機)
 
class SearchFile:
    def __init__(self):
        self.fileList = []
 
        for k in assetsDir:
            if (k == "searchDir"):
                for searchdire in assetsDir[k]:                 
                    self.recursiveDir(searchdire)
 
    def recursiveDir(self, srcPath):
        ''' 遞歸指定目錄下的所有文件'''
        dirList = []    #所有文件夾  
 
        files = os.listdir(srcPath) #返回指定目錄下的所有文件,及目錄(不含子目錄)
 
        for f in files:         
            #目錄的處理
            if (os.path.isdir(srcPath + '/' + f)):              
                if (f[0] == '.' or (f in assetsDir["ignorDir"])):
                    #排除隱藏文件夾和忽略的目錄
                    pass
                else:
                    #添加非需要的文件夾                                  
                    dirList.append(f)
 
            #文件的處理
            elif (os.path.isfile(srcPath + '/' + f)) and (f not in assetsDir["ignorFile"]):               
                self.fileList.append(srcPath + '/' + f) #添加文件
 
        #遍歷所有子目錄,并遞歸
        for dire in dirList:        
            #遞歸目錄下的文件
            self.recursiveDir(srcPath + '/' + dire)
 
    def getAllFile(self):
        ''' get all file path'''
        return tuple(self.fileList)
 
 
def CalcMD5(filepath):
    """generate a md5 code by a file path"""
    with open(filepath,'rb') as f:
        md5obj = hashlib.md5()
        md5obj.update(f.read())
        return md5obj.hexdigest()
 
 
def getVersionInfo():
    '''get version config data'''
    configFile = open(versionConfigFile,"r")
    json_data = json.load(configFile)
 
    configFile.close()
    # json_data["version"] = json_data["version"] + '.' + str(GetSvnCurrentVersion())
    json_data["version"] = json_data["version"]
    return json_data
 
 
def GenerateVersionManifestFile():
    ''' 生成大版本的version.manifest'''
    json_str = json.dumps(getVersionInfo(), indent = 2)
    fo = open(versionManifestPath,"w")  
    fo.write(json_str)  
    fo.close()
 
 
def GenerateProjectManifestFile():
    searchfile = SearchFile()
    fileList = list(searchfile.getAllFile())
    project_str = {}
    project_str.update(getVersionInfo())
    dataDic = {}
    for f in fileList:      
        dataDic[f] = {"md5" : CalcMD5(f)}
        print f
 
    project_str.update({"assets":dataDic})
    json_str = json.dumps(project_str, sort_keys = True, indent = 2)
 
    fo = open(projectManifestPath,"w")  
    fo.write(json_str)  
    fo.close()
 
if __name__ == "__main__":
    GenerateVersionManifestFile()
    GenerateProjectManifestFile()

生成version.manifest如下

{
  "packageUrl": "http://ip:port/update/MyProj/assets/", 
  "engineVersion": "3.3", 
  "version": "1.0.0", 
  "remoteVersionUrl": "http://ip:port/update/MyProj/version/version.manifest", 
  "remoteManifestUrl": "http://ip:port/update/MyProj/version/project.manifest"
}

生成project.manifest如下

{
    "assets": {
        "src/packages/mvc/init.lua": {
            "md5": "6b9173481a1300c5e737ad5885ebef00"
        }, 
        "src/protobuf.lua": {
            "md5": "f790fe35eb179a4341ff41d94e488a5d"
        }
        ...
    }, 
    "packageUrl": "http://ip:port/update/MyProj/assets/", 
    "engineVersion": "3.3", 
    "version": "1.0.0", 
    "remoteVersionUrl": "http://ip:port/update/MyProj/version/version.manifest", 
    "remoteManifestUrl": "http://ip:port/update/MyProj/version/project.manifest"
}

4.游戲客戶端: 利用cocos assetManager來從服務(wù)器獲取文件并且進(jìn)行資源的替換(這里所謂的替換并不是真正的替換孔厉,利用了Fileutils->searchPath() 設(shè)置資源文件讀取的優(yōu)先級。也就是老資源和代碼并沒有刪除帖努,而是舍棄不用撰豺。


--region *.lua
--Date
 
local AssetsManager = class("AssetsManager",function ()
    return cc.LayerColor:create(cc.c4b(20, 20, 20, 220))
end)
 
function AssetsManager:ctor()
    self:onNodeEvent("exit", handler(self, self.onExitCallback))
    self:initUI()
    self:setAssetsManage()
end
 
function AssetsManager:onExitCallback()
    self.assetsManagerEx:release()
end
 
function AssetsManager:initUI()
 
    local hintLabel = cc.Label:createWithTTF("正在更新...", CONFIG.TTF_FONT_2, 20)
        :addTo(self)
        :move(600, 80)
 
    local progressBg = display.newSprite("sprites/hyd_progress_bg.png")    
        :addTo(self)
        :move(600, 40)
 
    self.progress = cc.ProgressTimer:create(display.newSprite("sprites/hyd_progress.png"))
        :addTo(progressBg)
        :move(380, 19)
    self.progress:setType(cc.PROGRESS_TIMER_TYPE_BAR)
    self.progress:setBarChangeRate(cc.p(1, 0))
    self.progress:setMidpoint(cc.p(0.0, 0.5))
    self.progress:setPercentage(0) 
 
    --觸摸吞噬
    self.listener = cc.EventListenerTouchOneByOne:create()
    self.listener:setSwallowTouches(true)
    local onTouchBegan = function (touch, event)
        return true
    end
 
    self.listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN)
    cc.Director:getInstance():getEventDispatcher():addEventListenerWithSceneGraphPriority(self.listener, self)   
end
 
function AssetsManager:setAssetsManage()
    --創(chuàng)建可寫目錄與設(shè)置搜索路徑
    local storagePath = cc.FileUtils:getInstance():getWritablePath() .. "NewRes/" 
    local resPath = storagePath.. '/res/'
    local srcPath = storagePath.. '/src/'
    if not (cc.FileUtils:getInstance():isDirectoryExist(storagePath)) then         
        cc.FileUtils:getInstance():createDirectory(storagePath)
        cc.FileUtils:getInstance():createDirectory(resPath)
        cc.FileUtils:getInstance():createDirectory(srcPath)
    end
    local searchPaths = cc.FileUtils:getInstance():getSearchPaths() 
    table.insert(searchPaths, 1, storagePath)  
    table.insert(searchPaths, 2, resPath)
    table.insert(searchPaths, 3, srcPath)
    cc.FileUtils:getInstance():setSearchPaths(searchPaths)
 
    self.assetsManagerEx = cc.AssetsManagerEx:create("version/project.manifest", storagePath)    
    self.assetsManagerEx:retain()
 
    local eventListenerAssetsManagerEx = cc.EventListenerAssetsManagerEx:create(self.assetsManagerEx, 
       function (event)
           self:handleAssetsManagerEvent(event)
       end)
 
    local dispatcher = cc.Director:getInstance():getEventDispatcher()
    dispatcher:addEventListenerWithFixedPriority(eventListenerAssetsManagerEx, 1)
 
    --檢查版本并升級
    self.assetsManagerEx:update()
end
 
function AssetsManager:handleAssetsManagerEvent(event)    
    local eventCodeList = cc.EventAssetsManagerEx.EventCode    
 
    local eventCodeHand = {
 
        [eventCodeList.ERROR_NO_LOCAL_MANIFEST] = function ()
            print("發(fā)生錯誤:本地資源清單文件未找到")
        end,
 
        [eventCodeList.ERROR_DOWNLOAD_MANIFEST] = function ()
            print("發(fā)生錯誤:遠(yuǎn)程資源清單文件下載失敗")  --資源服務(wù)器沒有打開,
            self:downloadManifestError()
        end,
 
        [eventCodeList.ERROR_PARSE_MANIFEST] = function ()
             print("發(fā)生錯誤:資源清單文件解析失敗")
        end,
 
        [eventCodeList.NEW_VERSION_FOUND] = function ()
            print("發(fā)現(xiàn)找到新版本")
        end,
 
        [eventCodeList.ALREADY_UP_TO_DATE] = function ()
            print("已經(jīng)更新到服務(wù)器最新版本")            
            self:updateFinished()
        end,
 
        [eventCodeList.UPDATE_PROGRESSION]= function ()
            print("更新過程的進(jìn)度事件")
            self.progress:setPercentage(event:getPercentByFile())
        end,
 
        [eventCodeList.ASSET_UPDATED] = function ()
            print("單個資源被更新事件")
        end,
 
        [eventCodeList.ERROR_UPDATING] = function ()
            print("發(fā)生錯誤:更新過程中遇到錯誤")
        end,
 
        [eventCodeList.UPDATE_FINISHED] = function ()
            print("更新成功事件")
            self:updateFinished()
        end,
 
        [eventCodeList.UPDATE_FAILED] = function ()
            print("更新失敗事件")
        end,
 
        [eventCodeList.ERROR_DECOMPRESS] = function ()
            print("解壓縮失敗")
        end
    }
    local eventCode = event:getEventCode()    
    if eventCodeHand[eventCode] ~= nil then
        eventCodeHand[eventCode]()
    end  
end
 
function AssetsManager:updateFinished()
    self:setVisible(false)
    self.listener:setEnabled(false)
end
 
function AssetsManager:downloadManifestError()
    self:setVisible(false)
    self.listener:setEnabled(false)
end
 
return AssetsManager
 
 
--endregion

Android apk 安裝后在手機中還是以apk存在拼余,apk 不可寫入和刪除污桦,所以熱更新下載的最新資源都存在緩存中,并添加緩存目錄為最高優(yōu)先級搜索目錄匙监,加載資源時從最高優(yōu)先級目錄中加載從而起到替換更新的作用凡橱。

cocos2dx中有一個熱更新類AssetsManagerEx小作,用這個類實現(xiàn)熱更功能時需要有兩個文件,project.manifest以及version.manifest稼钩。這里主要是project.manifest文件

Cocos自身也封裝了熱更新的模塊AssetsManager顾稀、AssetsManagerEx。

AssetsManager采用的是升級包的管理方式坝撑,首先進(jìn)行版本號對比静秆,然后根據(jù)URL獲取對應(yīng)的升級包,解壓升級包巡李,設(shè)置資源加載路徑抚笔,通過加載writepath目錄下最新文件的方式來實現(xiàn)更新。問題是當(dāng)涉及跳版本更新侨拦,或只有一個文件被改動時殊橙,用戶就要下載前面全部的升級內(nèi)容,升級包會越來越大狱从。

AssetsManagerEx是AssetsManager的加強版膨蛮,不同的是不再使用升級包的方式,而是采用單個文件拉取的方式矫夯。首先獲取本地更新配置鸽疾,之后與服務(wù)器的更新配置比對,得出差異文件训貌,之后單個拉取差異文件制肮。當(dāng)本地版本大于服務(wù)器版本時,會清理掉本地更新緩存递沪。AssetsManagerEx也有尚未解決的問題豺鼻,例如多個更新序列無法并行,只能順序啟動款慨。另外版本后期隨著項目龐大配置文件幾乎包含了所有的文件信息儒飒,對比文件時間的耗時會越來越長。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末檩奠,一起剝皮案震驚了整個濱河市桩了,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埠戳,老刑警劉巖井誉,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異整胃,居然都是意外死亡颗圣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來在岂,“玉大人萌壳,你說我怎么就攤上這事饲窿∩粒” “怎么了茧妒?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長祠丝。 經(jīng)常有香客問我疾呻,道長,這世上最難降的妖魔是什么写半? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任岸蜗,我火速辦了婚禮,結(jié)果婚禮上叠蝇,老公的妹妹穿的比我還像新娘璃岳。我一直安慰自己,他們只是感情好悔捶,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布铃慷。 她就那樣靜靜地躺著,像睡著了一般蜕该。 火紅的嫁衣襯著肌膚如雪犁柜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天堂淡,我揣著相機與錄音馋缅,去河邊找鬼。 笑死绢淀,一個胖子當(dāng)著我的面吹牛萤悴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播皆的,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼覆履,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了费薄?” 一聲冷哼從身側(cè)響起硝全,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎楞抡,沒想到半個月后伟众,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡拌倍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年赂鲤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柱恤。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡数初,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梗顺,到底是詐尸還是另有隱情泡孩,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布寺谤,位于F島的核電站仑鸥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏变屁。R本人自食惡果不足惜眼俊,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粟关。 院中可真熱鬧疮胖,春花似錦、人聲如沸闷板。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遮晚。三九已至性昭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間县遣,已是汗流浹背糜颠。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留艺玲,地道東北人括蝠。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像饭聚,于是被迫代替她去往敵國和親忌警。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容