quick-cocos2d-x基于源码加密打包功能的更新策略(2)

前一篇:quick-cocos2d-x基于源码加密打包功能的更新策略(1) 安全

2、更新原理讨论及更复杂的更新功能

1.更新原理

在前面的更新过程当中,从服务器取文件列表,并根据文件列表再更新相关的文件,这都是很好理解的。固然其中还有些流程细节关系到健壮性、续传、文件版本分发等,咱们能够后面再讨论。 服务器

对于一些刚开始学习Quick-x的朋友来讲,可能但愿了解的是,这一更新机制的替换原理是什么,为何新的文件下载后可以替代原来的代码生效呢? app

从前面咱们参考的源文件编译及加密的相关文章,能够清楚的看到,初始代码打包成的game.zip文件,是在AppDelegate.ccp中,在程序启动前经过loadChunksFromZIP载入的。这一载入工做实际上已经将全部代码都加载了,以后调用require时,将会直接 调用而不会再去找代码文件来载入。也正由于如此,一些朋友会感到迷惑,即便下载了新的代码文件,又如何让它生效呢? 框架

其实很简单,loadChunksFromZIP是能够屡次调用的,并且若是第二次载入的包中的代码模块与以前载入的模块有重名,新的模块会覆盖旧的模块。 学习

在Quick-x的Lua代码中,对应的调用接口是CCLuaLoadChunksFromZIP。有了这个接口,咱们下载新的代码包后,就能够本身加载了。在update.lua里,在下载完成后,会自动将act标记为load的文件加载一次,这样,模块的新代码就取代了旧的代码,也就实现了咱们以前看到的更新功能。 测试

原理很简单,彷佛没问题了?惋惜问题没这么简单。这是由于require的机制问题。若是在第二次载入包以前,某一个模块文件已经被require过,那么,虽然第二次载入后,这一模块的代码已经被更新了,但再次require时,因为此模块不会被真正调用而是直接取原来的调用结果,将会形成更新不生效。 ui

下面咱们经过调试一个新的更新情景来讲明这个问题及解决方法。 加密

2.已经require过的模块的更新

假设如今咱们但愿更新一下界面上的图片。 lua

若是只是更换个别图片,这是很简单的,只须要在代码里newSprite里在图片文件名前加上完整的下载目录路径,而后将新的图片放到服务器上,在flist文件中添加图片文件名便可。不建议在要更新的代码里使用addSearchPath来添加搜索路径,由于在图片同名的状况下并不能保证优先查找到的是新的文件。 spa

若是是更新纹理图,仍然可使用上述明确指定资源路径的方式,同时也是建议的方式。所以,对于咱们以前MainScene.lua的代码,能够作如下修改(假设device.writablePath是下载路径):

display.addSpriteFramesWithFile(device.writablePath..GAME_TEXTURE_DATA_FILENAME, device.writablePath..GAME_TEXTURE_IMAGE_FILENAME)
self.bg = display.newSprite("#logo.png", display.cx-200, display.cy-200)
self:addChild(self.bg)

固然这不是惟一的改法。为了说明主题,咱们换成修改config模块的方式。此次不修改MainScene.lua,而是在config.lua中,修改原来的纹理图定义以下:

GAME_TEXTURE_DATA_FILENAME = device.writablePath.."game.plist"
GAME_TEXTURE_IMAGE_FILENAME = device.writablePath.."game.png"

要注意的是,若是不是更新代码,这样写是有问题的。一般config模块都是最先被require的,甚至还在framework.init模块以前,此时device都尚未定义,因此若是是正常流程就会出错。但这里是更新代码,在被调用以前,update模块已经调用过framework.init,因此device已经被初始化过了。

这就引出了咱们的问题,既然config模块在早期已经被调用,那么,当新的config下载到客户端后被加载时,再require实际上已经不能让它生效了!

最直接的解决办法是,在从新require以前,调用如下的代码:

package.loaded["config"] = nil

即将config的调用标记清除。这样,再次require时就能让Lua程序从新去执行新的config模块了。咱们将这条语句加到appentry模块的开头,这是最合适的地方了。

咱们仍然将修改后的config.lua和appentry.lua打包成update.bin,和新的图片资源文件一块儿放到服务器上,此次服务器的flist文件以下:

local list = {
  ver = "1.0.3",
  stage = {
   {name="game.plist", code="29d103e9831720c1be12d8b33a1ea762", },
   {name="game.png", code="e9dd2797018cad79186e03e8c5aec8dc", },
   {name="update.bin", code="a681c6a002989832645ed26b766c7afa", act="load"},
  },
  remove = {
  },
}
return list
在客户端启动程序,版本自动被更新,进入MainScene显示的图片变成了新的,成功了!

你们能够自行验证不清除调用标记的状况。要注意的是若是是在player上,显示的图片同样会变成新的,这是由于device.writablePath在player上恰好是最优先的搜索路径,因此下载后的图片会被先搜到。所以这个测试最好下载到其余目录下,或者在新的config模块里加一条调试输出语句,这样就很容易确认新的模块是否真的被执行了。

由上述例子能够看到,更新模块时,困难的地方就在于判断重加载前有没有已经被调用的模块。特别是做在应用运行过程当中动态更新的方案时,更须要对流程有清楚的把握。

即便是如今实现的这个预更新的方案,在解决了config模块的重载调用问题后,仍然要面对一个相对更困难的问题:若是framework模块须要更新,应该怎么办?

3.深层逻辑更新的处理方式:强制覆盖式调用

framework模块是Quick-x的核心之一,旧版本里是在main.lua里载入,如今的版本更是将它的载入位置提早到了Lua程序入口被调用以前的C++代码中,而一般framework的init模块也是早早会被调用。即便是update模块,也是先调用了init才能方便的实现更新功能。虽然这一模块更新的机率很小,但做为解决方案,仍是要考虑清楚应该如何对它进行更新。

首先会想到的,是象以前处理config模块同样,清除调用标记。然而仔细考虑,会以为不太行得通。由于看代码能够知道,init模块还调用了framework里不少的其余模块,在修改以后要理清哪些模块须要重调用是很繁琐的,而繁琐就容易出错。所以这不是最好的解决方案。

通过考虑,我以为最方便的是这样一种解决方式:若是须要更新framework模块,那么,在打包时,不将代码放在framework目录下,而是其余的目录,如“framework1”。这样,全部的模块就变成了framework1.init、framework1.device等等,在新的代码里调用“require "framework1.int"”,将会真正从新运行框架代码,而没必要担忧有哪一个模块没有从新运行生效。

这就是“强制覆盖式调用”的含义,它在不少情形下能够避免过于复杂的更新逻辑的判断,推荐给你们。

再举个例子,若是更新模块自己须要更新,那应该怎么办呢?若是要为此在更新模块中预留更新逻辑,会很是复杂难解。然而,若是跳出来看,其实彻底能够在修改完update模块后,打包成一个updateNew模块放到服务器,让原更新模块下载后强制调用,这样一来,整个处理就简单了。

经过上面的讨论,如今咱们已经能确认,update模块的更新功能是有效的。接下来咱们还要讨论更新方案的另外一个要点:安全性。

若是由于更新失败形成客户端没法启动,那无疑是致命的。幸运的是,Quick-x的打包机制能让咱们保有一个原始版本,即便是在最不幸的出错状况下,程序仍然能回到最初的版本而不至于崩渍。

后一篇:

quick-cocos2d-x基于源码加密打包功能的更新策略(3)

相关文章
相关标签/搜索