项目选择C++ 和 lua 混合, 看中的就是lua代码的热更新优势,想想之前客户端出现了bug,需要玩家重新下载安装包,这带来的流失率是挺高的; 而随着现在app体积日益增大,动辄几十兆、上百兆的安装包重复下载,对用户体验是非常不友好的;另外appstore的审核也是严格、标准可变的,很容易审核被拒,导致应用的问题无法及时修复。而选择lua做为开发语言,就能做到几乎无感知的修复、增加应用的新功能,对于开发者可以及时修复问题,而对于玩家来说则能获得更好的游戏体验。从某狐刚推出某耀客户端项目,客户端购买、咨询量激增来看,这个功能还是很有吸引力的。
热更新方案,一开始的设计是单文件更新,思路是:
1、打包的时候利用脚本对全部的资源文件:包含lua脚本、游戏的图片、音频资源生成一份版本文件
2、客户端启动的时候从服务器下载版本文件,跟本地的版本文件进行对比,生成更新列表,然后进行更新
上述方案的优势在于:
1、资源服务器只需要存一套资源文件、
2、不用考虑客户端版本与服务器版本之间差异过大,导致数据不对等的问题
3、方案实现简单,使用的成本也低,可以实现一些取巧操作(资源服务器直接改某个文件的差异值,让客户端直接更新等)
但是在实际的使用过程中也发现了该方案存在不足:如果待更新的资源过多,那么就会产生大量的http请求,严重的影响了下载体验,而且单个文件的更新失败,需要一个非常复杂的断点续传的方案来控制整体更新。
在这里提一下一般客户端实现的文件下载功能:利用curl库,发起请求进行文件下载。这就会引发两个问题:请求量及请求响应。上述方案的核心点是根据版本文件生成差异列表,然后再单独下载差异列表中的每个文件,所以每下载一个文件,就会发起一次请求,如果某次更新有上千个文件,那么单个客户端更新所发起的请求量将是巨大的。上千次请求,引发上千次的响应等待,对带宽及玩家的等待时间都将产生巨大的影响!
因此我们需要新的更新方案:更新是对差异文件进行更新,之前将差异对比放在了客户端处理阶段,那么可以将差异对比放在更新包生成的阶段,将版本间的差异文件提取出来,合并成一个压缩包,客户端直接下载这一个压缩包再解压,实现功能的更新。我们将这个方案称为差异包更新。
实现这个方案,需要考虑到:
1、客户端每次只下载一个文件,需要严格保证下载到正确的资源
2、客户端更新存在版本延时的问题,需要有跨版本的差异包
1、严格保证资源的正确下载
(1)网络不稳定的处理:
手机客户端考虑到移动网络的不稳定,容易导致断网、超时的问题。前面提到的单文件更新方案,在更新的过程中单个文件下载失败,需要一套非常复杂的断点续传方案,才能控制好整体的功能更新,我们是非常粗鲁的直接重新下载。。。调整到下载压缩包之后,就可以很方便的利用curl库提供的功能,来实现断点续传功能:
curl_easy_setopt(_curl, CURLOPT_RESUME_FROM, localLen)
核心就是上述代码,CURLOPT_RESUME_FROM, 表示从参数3的位置来写入本地文件,而参数3的值也很容易获取,直接使用引擎的getDataFromFile方法getSize就能拿到当前阶段本地文件的大小。
(2)差异包更新失败的处理:
之前的单文件更新方案,假如存在错误,可能也只是单个文件或单独几个文件出现问题,修复比较方便。但是在差异包更新方案中,每次的更新是单个压缩包的更新,就会存在:
a.压缩包下载失败导致客户端出现重大功能异常
b.版本与版本之间生成了错误的差异列表导致更新异常
c.客户端资源版本不统一,如何下载到统一的更新包
我们的应对方案是:保留单文件更新功能,在压缩包下载失败时进行单文件更新;采用更可靠的生成文件差异值的方案,由一台设备统一生成更新包,针对不同版本生成不同的差异包。
2、更可靠的差异生成方案:
在上面有提到,差异列表生成错误、客户端版本有差异两个问题,在这里具体谈一下采用一套更可靠的差异生成方案:
(1)差异值的生成:
在单文件更新方案里,我们是简单的对文件大小进行MD5处理,得到一个差异值,然后更新对比中对md5值进行对比,生成差异列表。这个方案就有很大的概率出现md5值一致,但是实际却需要更新的情况,因此我们改用获取文件的修改时间,这样就大大降低了上述情况出现的概率了。
(2)差异包的生成:
差异值的获取,差异包的生成, 在这里讲一下思路。这里举个例子说明客户端版本差异:客户端A的当前版本为1, 客户端B的当前版本号为2,此时发布版本3的更新,这个时候客户端A和客户端B的更新列表是存在差异的,该如何处理?在单文件更新方案下,这不存在问题,A和B都是以3的版本文件,在客户端本地直接生成差异列表,去下载对应的文件即可。但是在差异包更新方案中,就需要生成如1-3,2-3这样的版本差异包。而且游戏客户端的更新是比较频繁的,就表示每次更新,所需要的差异包是非常多的,因此考虑到不必要的工作复杂度提升,需要提出一个基准包的概念。每次进行了底包(玩家重新安装了客户端)更新,底包对应的资源版本就是一个基准的,无论玩家多久不更新,他都有一个基准的包,因此生成差异包的时候,可以针对这个基准包生成一份差异包,这样就能保证功能的正常更新了。因此我们的差异包就包含了:版本间的差异包,与基准版本的基准差异包。
我们控制一个版本跨度,比如5,设定一个基准版本0,假如客户端A的版本号是6,资源服务器的版本号是7,版本跨度是1,在我们设定的跨度之间,客户端A更新的时候就下载6-7之间的差异包;客户端B的版本号是5,与资源服务器的版本跨度是2,下载5-7的包;客户端C的版本号是1,与资源服务器的版本跨度是6,大于我们设定的限值了,这个时候就下载0-7的基准差异包。 这样就能保证不同版本的客户端,也能下载到统一的更新资源。
解决了上述问题,我们实现的差异包更新方案,在实际的体验中,就是一个字:快!在带宽一致的情况下,大概算了一下,之前需要10s才能完成的更新,现在只要1s不到的时间就能下载好,再根据设备配置的差异存在一定的解压时间,但是总时间是小于之前那个方案的。这个方案不涉及到引擎相关(最多就是用引擎的接口拿获取文件大小),因此可以加入到使用到了热更新方案的不同开发项目中去。