文档章节

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

SunLightJuly
 SunLightJuly
发布于 2013/12/02 00:12
字数 2151
阅读 9389
收藏 13

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

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

1.更新原理

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

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

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

其实很简单,loadChunksFromZIP是可以多次调用的,而且如果第二次载入的包中的代码模块与之前载入的模块有重名,新的模块会覆盖旧的模块。

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

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

下面我们通过调试一个新的更新情景来说明这个问题及解决方法。

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

假设现在我们希望更新一下界面上的图片。

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

如果是更新纹理图,仍然可以使用上述明确指定资源路径的方式,同时也是建议的方式。因此,对于我们之前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)

© 著作权归作者所有

共有 人打赏支持
SunLightJuly
粉丝 59
博文 15
码字总数 12480
作品 0
成都
私信 提问
加载中

评论(50)

SunLightJuly
SunLightJuly

引用来自“qq626928256”的评论

知道问题在哪了,七月大神辛苦了
0
问题解决了就好
q
qq626928256
知道问题在哪了,七月大神辛苦了
0
q
qq626928256

引用来自“qq626928256”的评论

但我还是有一事不明,在哪里可以获取到当前热更新的进度,因为毕竟都是用一个压缩文件进行更新,没有了更新的数量问题,那当前下载的进度就十分必要了,不知道博主怎么处理的,我看到CCHTTPRequest里面有onProgress函数,不知道该怎么使用

引用来自“SunLightJuly”的评论

elseif status == "inprogress" then printf("REQUEST %d - dlnow/dltotal: %d/%d", index, event.dlnow, event.dltotal) printf("REQUEST %d - ulnow/ultotal: %d/%d", index, event.ulnow, event.ultotal)
我看过,里面是没有inprogress的,而是有progress,而event.dlnow是没有该值的,event.dltotal值也一直为0....
q
qq626928256

引用来自“qq626928256”的评论

个人认为直接把新资源路径直接加入addSearchPath比较好,可以将最新的目录放在第一位,这样依据cocos底层的判断,只要在最开始的路径里面找到了同名资源就会调用,就不会调用以前的资源了

引用来自“SunLightJuly”的评论

可以用。不过实际应用时你会发现仍然有一些工作要做。
比如呢,七月大神好厉害
SunLightJuly
SunLightJuly

引用来自“qq626928256”的评论

但我还是有一事不明,在哪里可以获取到当前热更新的进度,因为毕竟都是用一个压缩文件进行更新,没有了更新的数量问题,那当前下载的进度就十分必要了,不知道博主怎么处理的,我看到CCHTTPRequest里面有onProgress函数,不知道该怎么使用
elseif status == "inprogress" then printf("REQUEST %d - dlnow/dltotal: %d/%d", index, event.dlnow, event.dltotal) printf("REQUEST %d - ulnow/ultotal: %d/%d", index, event.ulnow, event.ultotal)
SunLightJuly
SunLightJuly

引用来自“qq626928256”的评论

个人认为直接把新资源路径直接加入addSearchPath比较好,可以将最新的目录放在第一位,这样依据cocos底层的判断,只要在最开始的路径里面找到了同名资源就会调用,就不会调用以前的资源了
可以用。不过实际应用时你会发现仍然有一些工作要做。
q
qq626928256
但我还是有一事不明,在哪里可以获取到当前热更新的进度,因为毕竟都是用一个压缩文件进行更新,没有了更新的数量问题,那当前下载的进度就十分必要了,不知道博主怎么处理的,我看到CCHTTPRequest里面有onProgress函数,不知道该怎么使用
q
qq626928256
个人认为直接把新资源路径直接加入addSearchPath比较好,可以将最新的目录放在第一位,这样依据cocos底层的判断,只要在最开始的路径里面找到了同名资源就会调用,就不会调用以前的资源了
q
qq626928256

引用来自“qq626928256”的评论

我尝试了您的更新方法,发现本质即对比md5进行下载替换,在本地的开发环境下我使用正常,但我个人认为本地也需要一开始即留存当前版本文件,现在遇到的问题是打包到安卓后,更新模块发现不能正常运行,本地的文件列表存放在res目录了,可是在安卓上总是读取失败,不知您看是什么原因呢0

引用来自“SunLightJuly”的评论

在res目录下的文件,是不能用普通的Lua的io接口来打开的,请使用CCFileUtils提供的读文件接口
博主好快,谢谢博主
SunLightJuly
SunLightJuly

引用来自“qq626928256”的评论

我尝试了您的更新方法,发现本质即对比md5进行下载替换,在本地的开发环境下我使用正常,但我个人认为本地也需要一开始即留存当前版本文件,现在遇到的问题是打包到安卓后,更新模块发现不能正常运行,本地的文件列表存放在res目录了,可是在安卓上总是读取失败,不知您看是什么原因呢0
在res目录下的文件,是不能用普通的Lua的io接口来打开的,请使用CCFileUtils提供的读文件接口
quick-cocos2d-x基于源码加密打包功能的更新策略(3)

前篇: quick-cocos2d-x基于源码加密打包功能的更新策略(1) quick-cocos2d-x基于源码加密打包功能的更新策略(2) 三、更新流程说明及特性分析 A.更新流程 加载初始安装包,载入旧资源列表 取最...

SunLightJuly
2013/12/05
4.5K
8
quick-cocos2d-x基于源码加密打包功能的更新策略(1)

Quick-cocos2d-x增加了编译及加密源代码的功能(具体可参考这篇文章)。以此功能为基础,我实现了一个版本更新模块,解决了自己项目中的版本更新需求。现抛砖引玉,与大家分享。 从基本原理和方...

SunLightJuly
2013/12/01
0
70
quick-cocos2d-x平台Lua源码打包后运行出错的一种情况

quick-cocos2d-x平台的Lua源码打包及加密功能是其提供的方便功能之一(具体可参考lonewolf的这篇文章)。今天Quick-x群里的一位朋友在使用时遇到了点问题,经过我们的探讨找到了原因。虽然是个...

SunLightJuly
2013/12/31
0
5
浅析Android手游lua脚本的加密与解密

本文转载自 梦幻西游手游美术资源加密分析 浅析android手游lua脚本的加密与解密 lua、luac、luaJIT三种文件的关系 lua手游过程中有三种文件:lua、luac、luaJIT。lua是明文代码,直接用记事本...

qq_32400847
2017/11/12
0
0
quick-cocos2d-x源文件编译及加密详解

quick-cocos2d-x是用lua脚本来写的,而lua是明文形式,如果不对脚本进行处理,那么我们所写的代码将可能暴露给别人(apk和ipa都是简单的zip包装)。 quick-cocos2d-x框架为我们提供了一个可编...

lonewolf
2013/11/23
0
43

没有更多内容

加载失败,请刷新页面

加载更多

搜索引擎(Solr-索引详解)

时间字段类型特别说明 Solr中提供的时间字段类型( DatePointField, DateRangeField,废除的TrieDateField )是以时间毫秒数来存储时间的。 要求字段值以ISO-8601标准格式来表示时间:YYYY-MM...

这很耳东先生
26分钟前
0
0
Java成神之路

1、基础篇 01、面向对象 → 什么是面向对象 面向对象、面向过程 面向对象的三大基本特征和五大基本原则 → 平台无关性 Java 如何实现的平台无关 JVM 还支持哪些语言(Kotlin、Groovy、JRuby...

asdf08442a
56分钟前
2
0
dubbo源码分析-服务导出

简介 dubbo框架spring Schema扩展机制与Spring集成,在spring初始化时候加载dubbo的配置类。 dubbo服务导出的入口类是ServiceBean的onApplicationEvent方法 ServiceBean的继承关系如下 publ...

王桥修道院副院长
今天
0
0
QQ音乐的动效歌词是如何实践的?

本文由云+社区发表 作者:QQ音乐技术团队 一、 背景 1. 现状 歌词浏览已经成为音乐app的标配,展示和动画效果也基本上大同小异,主要是单行的逐字染色的卡拉OK效果和多行的滚动效果。当然,我...

腾讯云加社区
今天
4
0
idea里配置springboot项目打热部署

首先添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional></dependency> 然后添......

shatian
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部