游戏更新教程(包括整包更新,热更新,以及项目中出现的问题)

一直想写一个关于游戏更新的教程,自己的对更新的理解,以及项目中遇到的问题和解决方案分享给大家。希望兄弟们遇到其他问题的来补充一下。共同交流进步。
大部分问题都能通过热更新解决,但是一些需要修改原生代码的功能,和引擎相关的修复,只能通过整包更新完成。所以整包更新在所难免。

整包更新流程


1.获取app版本信息以及热更版本信息(如需要)
2.在app启动时,调用更新接口,把版本信息传递给服务器
3.接口返回是否需要整包更新,以及下载地址,如果需要更新就跳转下载地址,或者自动下载app。

获取应用版本信息:


android和IOS都是用2个字段来标志版本信息。
安卓时在AndroidManifest.xml里配置。
android:versionCode=“4”
android:versionName=“1.1.1”

IOS是CFBundleShortVersionString 和 CFBundleVersion。
具体用法请自行百度,不再赘述

Android的app版本信息获取:

public static String getAppVersion()
    {
        try
        {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            assert(info.versionName != null);
            return info.versionName;
        } catch (NameNotFoundException e)
        {
            e.printStackTrace();
        }
        return null;
    }

public static String getAppVerCode()
{
	PackageManager packageManager = context.getPackageManager();        
	PackageInfo packageInfo;        
	String versionCode="";        
	try {
		packageInfo=packageManager.getPackageInfo(context.getPackageName(),0);       
		versionCode=packageInfo.versionCode+"";
		} catch (PackageManager.NameNotFoundException e)
		{            
			e.printStackTrace();        
		}        
	return versionCode;
}

IOS获取方式:
//获取CFBundleVersion
std::string DeviceModule::getAppVerCode()
{
NSString *versionCode = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey];
return [versionCode UTF8String];
}
//获取CFBundleShortVersionString
std::string DeviceModule::getAppVersion()
{
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@“CFBundleShortVersionString”];
return [version UTF8String];
}

热更新

获取热更新版本信息:
1.自己在js代码中定义版本信息 每次打更新包之前,自行修改
2.通过 jsb.AssetsManager获取
this._am = new jsb.AssetsManager(this.manifestUrl, this._storagePath, this.versionCompareHandle);
let srcVer= this._am.getLocalManifest().getVersion();
两种方式正常情况下都可以获取到版本信息。

热更新流程

参考官方教程
https://docs.cocos.com/creator/1.9/manual/zh/advanced-topics/assets-manager.html

我总结了下,就是以下6个步骤:
1.对比本地和远程服务器的mainfest的版本信息(version.manifest)
2.下载远程服务器资源mainfest到临时文件路径
3.对比远程服务器资源mainfest和临时文件路径mainfest的差异,生成下载文件清单
4.下载清单文件,放到临时文件路径
如果热更路径是((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : ‘/’) + ‘game-remote-asset’,
那么临时文件路径就是(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : ‘/’) + ‘qygame-remote-asset_temp’
5.下载完成后,拷贝临时文件到热更文件路径
6.用remote mainfest替换local mainfest

由于creator的资源uuid是在引擎启动的时候加载的,所以热更之后,需要把热更路径的搜索优先级放在前面,然后重启引擎。保证新资源在使用。


生成热更mainfest文件


creator引擎的构建,会把纹理合图,以及游戏引用到的资源生成到res目录下。js代码也会合成,并加密脚本(项目选择加密脚本),生成在src目录下。
而这些生成的文件在项目路径下的build\jsb-link路径下。如果是jsb-defalut模式,也是在相应的目录下。
生成热更mainfest文件的脚本,就是实现把这两个路径下的资源统计,并生成对应的md5标志。

参照官方热更例子做了修改。包含一个js脚本和一个执行的bat。
hotupdate.rar (1.8 KB)

热更新常见错误和解决方案

所有热更新出错的问题,最终都是一个问题,那就是mainfest文件清单,和实际应用内的文件不匹配(脚本和资源)。
根据以往项目中的经验,总结了以下几个可能出现不匹配的原因,以及如何解决这些问题。

一,生成安装包时,mainfest文件和资源不匹配。
这也是新手或者对热更机制不熟悉的人最容易出现的问题。
解决方案,打安卓和ios包之前,先构建项目然后生成一份mainfest文件,并拷贝到项目下,然后再次构建项目,再去打安卓和ios包。以保证初始包中2者一致。

二,http下载中,文件出现不一致。
解决方案:MD5校验。
md5校验方法
c++实现:本人是通过c++实现的。然后通过jsb调用来验证文件。
js实现:md5 = crypto.createHash(‘md5’).update(fs.readFileSync(subpath)).digest(‘hex’);
脚本中是这样实现的,到项目中需要修改一下,由于js实现调试的时候多了很多引用文件,所以我是c++实现的。

三,整包更新后,mainfest文件和资源不匹配
安卓和ios整包更新,会把安装包的内容替换掉,但是会保留应用程序的可写路径资源,所以整包更新也会导致mainfest文件和资源不匹配。
解决方案:本地记录上次app运行时的版本信息,检查app版本信息不一致时,删掉热更路径和临时路径文件夹。
流程是在c++实现的,在js引擎初始化之前检查,这样少了重启引擎的步骤。
相关代码:
//检查程序版本信息
void CPlatformModule::checkAppVersion()
{
std::string perVersion = UserDefault::getInstance()->getStringForKey(“UserAppVersion”,"");
std::string curVersion = getAppVersion();
if (perVersion != curVersion)
{
//当前版本和记录版本不一样
std::string writePath = FileUtils::getInstance()->getWritablePath();

	//删除热更路径文件
	std::string assetPath = writePath.append("qygame-remote-asset/");
	FileUtils::getInstance()->removeDirectory(assetPath);	

	//删除热更缓存路径文件
	std::string tmpAssetPath = writePath.append("qygame-remote-asset_temp/");
	FileUtils::getInstance()->removeDirectory(tmpAssetPath);

	UserDefault::getInstance()->setStringForKey("UserAppVersion", curVersion);
}

}

四,用户热更过程中,服务器替换热更版本。
五,临时路径拷贝到资源路径过程中,app被强制退出。(闪退或者用户自己退出)

一,二,三都处理之后,还是有一些用户热更失败,所以猜测可能是四,五导致的。四经过验证确实是会出现,我们服务器资源更新是直接把原来文件删除,然后替换新的热更文件。
如果这时候用户正在进行热更,也是会出现资源不一致情况。所以现在更新脚本都是在用户少的情况。

针对一些未知的情况,以下还有2个终极解决方案。

终极解决方案一:app启动时,校验文件以及客户端完整性。流程就是读取mainfest文件清单,然后依次做md5校验,检查文件完整性。
这个功能我并没有实现,我用了第二种比较简单点的方式。

终极解决方案二:一键修复。流程和整包更新一样,删除掉临时路径和热更路径的资源,然后重启引擎。
参考代码:

let storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : ‘/’) + ‘game-remote-asset’);
let tmpPath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : ‘/’) + ‘game-remote-asset_temp’);
jsb.fileUtils.removeDirectory(tmpPath);
jsb.fileUtils.removeDirectory(storagePath);
gg.audio.stopMusic();
cc.game.restart();

23赞

一楼mark

二楼 mark

三楼卖矿泉水

经空气大佬指正,问题四,用户热更过程中,服务器替换热更版本。可以通过动态热更规避。用户通过调用接口获取最新的热更版本号和热更路径。然后再去走热更新流程。这样把热更文件放在不同的目录下。就不会出现此问题。马上去实现相关功能,回头测试完成就贴代码。

mark!

mark

如果是最后拷贝manifest文件,那么问题五就不会出现,但是刚看了源码然后调试,并不是最后拷贝。
更新下载完成之后,首先


把临时路径的manifest.tmp改成manifest
然后获取临时路径的文件列表,然后遍历执行替换操作。


然后再window下调试代码。project.manifest在列表的最前面。也就是说,会先替换manifest文件。
如果拷贝过程中应用被强制退出。这时候manifest已被替换,就会出现资源不一致情况。

先mark, 希望能有需要看的一天

先回复一下,下次用到的时候在仔细看,估计对我而言可能有点难度了。

继续卖矿泉水

mark

mark

mark

mark 买水

mark

mark

mark

照着搞了一个竟然成功了,没有使用cdn所以没出啥太多问题。把报错解决就OK了。:blush: 附上我的测试工程test.rar (950.9 KB)

请问searchPahts是在那里设置的啊。 我看了一下manifest里的path是空的呀