Cocos Creator 热更新 [Lv.1]

Posted VermillionTear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cocos Creator 热更新 [Lv.1] 相关的知识,希望对你有一定的参考价值。

摘要

承接上文 Cocos Creator 热更新 [Lv.1] (1)
在上文中对热更新进行了相关的初始化工作,本文在此基础上实现热更新的检测以及更新。

正式开始

检测新版本

首先创建一个按钮,用于检测是否有新的版本。
在这里插入图片描述在这里插入图片描述
hotUpdateController.js中实现功能。

...
...
let _isChecking = false		// 检测更新的标志。

cc.Class({
    ...
    properties: {
        ...
        checkButton: cc.Node, 	// 检测按钮。
    },

    onLoad () {
        ...
        if (!_am.getLocalManifest() || !_am.getLocalManifest().isLoaded()) {
            this.checkButton.active = false		// 如果热更新模块初始化失败,则隐藏检测按钮。
            ...
        } else {
            ...
            this.checkButton.active = true		// 热更新模块初始化成功,显示检测按钮。
            ...
        }
    },
    ...
    eventCallback: function (event) {
        ...
        switch (event.getEventCode())
        {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                ...
                _isChecking = false
                ...
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                ...
                _isChecking = false
                ...
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:		// 已经是最新版本。
                this.info.string = "Already up to date with the latest remote version."
                this.checkButton.active = false		// 已经是最新版本,检测按钮隐藏。
                _isChecking = false
                break
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:		// 发现了新版本。
            	// 获取并显示的新版本的大小。
                this.info.string = 'New version found, please try to update. (' + _am.getTotalBytes() + ')'
                this.checkButton.active = false		// 检测到了新版本,检测按钮隐藏,之后应该显示更新按钮。
                _isChecking = false
                break
            ...
        }
    }, 

    isBusy: function () {
        let busy = false

        if (_isChecking) {
            this.info.string = 'Checking ...'
            busy = true
        }

        return busy
    }, 

    checkUpdate: function () {
    	// 判断是否正在进行热更的某个步骤。
        if (this.isBusy()) {
            return
        }

        _isChecking = true
        _am.checkUpdate()		// 热更模块检测新版本。
    }, 
});

将节点挂载到脚本对应变量上。
在这里插入图片描述
指定检测按钮点击后的回调函数。
在这里插入图片描述

创建服务器

为了能检测到新版本,我们需要创建一个新版本,放到服务器上,并且启动服务器。
再次构建工程,然后使用version_generator.js脚本生成1.0.1版本的project.manifestversion.manifest

$ node version_generator.js -v 1.0.1 -u http://127.0.0.1:1234/ -s D:/build/jsb-link/ -d assets/
Version successfully generated
Manifest successfully generated

进入remote_assets目录,我们将以此作为服务器的根目录。
将构建好的assetssrc目录以及新生成的project.manifestversion.manifest均拷贝到remote_assets目录中。
这样服务器的资源就配置好了,应该是如下结构,
在这里插入图片描述
remote_assets目录下启动SimpleHTTPServer

$ python -m SimpleHTTPServer 1234
Serving HTTP on 0.0.0.0 port 1234 ...

取巧的方法

我们为了创建服务器的版本,把工程中的manifest文件也变成了1.0.1版本。而如果想要检测到更新,就需要回退到1.0.0版本。
按照正规的流程,你可以改动一下界面或是代码,然后构建工程,然后用脚本生成1.0.0版本的manifest文件。
但这里也可以采用一个取巧的方法,对现在的工程不做任何改动,直接用脚本生成1.0.0版本的manifest文件。

$ node version_generator.js -v 1.0.0 -u http://127.0.0.1:1234/ -s D:/build/jsb-link/ -d assets/
Version successfully generated
Manifest successfully generated

然后打开刚生成的prject.mainifest,会看到类似于如下的信息,

{
"packageUrl":"http://127.0.0.1:1234/",
"remoteManifestUrl":"http://127.0.0.1:1234/project.manifest",
"remoteVersionUrl":"http://127.0.0.1:1234/version.manifest",
"version":"1.0.0",
"assets":{
"assets/main/native/a8/a8027877-d8d6-4645-97a0-52d4a0123dba.png":{"size":82,"md5":"cea68f0d7cba38440224f6f74531e2d8"},
"assets/main/native/b4/b43ff3c2-02bb-4874-81f7-f2dea6970f18.png":{"size":1114,"md5":"83fcc9912e01ae5411c357651fb8b1cf"},
"assets/main/native/e8/e851e89b-faa2-4484-bea6-5c01dd9f06e2.png":{"size":1082,"md5":"90cf45d059d0408bec327f66eae5764c"}},
"searchPaths":[]
}

当然,如果你直接打开,肯定是没有换行的,密密麻麻一大片。我这里为了方便讲解,做了一些换行。
看到每个资源后面都有个md5字段了吗,他就是使用资源内存储的数据进行md5运算得来的,同时他也标识着当前资源的版本。如果资源的md5发生变化,热更模块就会认定该资源有新的版本。
所以基于这点,我们只需要修改某个资源的md5值,就能骗过热更模块,让其认定有新的热更版本。
这里就找最后一个png后面的md5,修改其中的一个字符就可以了。
比如我这里把90cf45d059d0408bec327f66eae5764c修改成了91cf45d059d0408bec327f66eae5764c
把第二个字符从0改成了1。
接下来运行Demo,点击检查更新按钮,就会发现检测到了新的版本。
在这里插入图片描述
使用这种方法测试,一是为了方便,二是为了避免一些没必要的错误。
不然每次部署服务器资源后,为了回退到原先的版本,都需要改动工程,重新构建,才能使用version_generator.js脚本生成本地的manifest文件。
改动的步骤越多,出错的几率越大。

检测到新版本后,writablePath下的变动

热更模块检测到新版本的同时,在writablePath目录下创建了一些文件。

hotUpdate		// 我们指定的热更目录,也就是 Storage Path。
hotUpdate_temp		// 热更的临时目录。在热更成功之前,所有下载到的资源都存储在这里。
	|- project.manifest.temp
	|- version.manifest

打开project.manifest.temp看看,实际上就是服务器上的project.manifest
为什么后面要有个.temp,是因为实际需要热更的文件还没有下载下来,也就是热更还没有完全成功。等到热更新成功,这个project.manifest.temp就会被重命名为project.manifest,然后放到hotUpdate目录下。project.manifest.temp目录中所有热更下来的资源也会被放到hotUpdate目录下,hotUpdate_temp目录随即会被删除。

更新新版本

创建一个按钮,用于更新。
在这里插入图片描述在这里插入图片描述
hotUpdateController.js中实现按钮的功能。

...
let _isUpdating = false		// 更新的标志。

cc.Class({
    ...
    properties: {
        ...
        updateButton: cc.Node, 		// 更新按钮。
    },

    onLoad () {
        ...
        this.updateButton.active = false	// 更新按钮默认不显示,只有检测到新版本才显示。
    },
    ...
    eventCallback: function (event) {
        ...
        switch (event.getEventCode())
        {
            ...
            case jsb.EventAssetsManager.ERROR_UPDATING:
                this.info.string = 'Asset update error: ' + event.getAssetId() + ', ' + message
                _isUpdating = false
                break
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                this.info.string = message
                _isUpdating = false
                break
            ...
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:		// 发现新版本。
                ...
                this.updateButton.active = true		// 显示更新按钮。
                ...
                break
            case jsb.EventAssetsManager.UPDATE_FINISHED:	// 更新成功。
                this.info.string = 'Update finished. ' + message
                this.updateButton.active = false	// 更新成功,隐藏更新按钮。之后会显示重启按钮。
                _isUpdating = false
                break
            case jsb.EventAssetsManager.UPDATE_FAILED:		// 更新失败。
                this.info.string = 'Update failed. ' + message
                this.updateButton.active = false	// 更新失败,隐藏更新按钮。之后会显示重试按钮。
                _isUpdating = false
                break
            default:
                return
        }
    }, 

    isBusy: function () {
        ...
        if (_isChecking) {
            ...
        } else if (_isUpdating) {
            this.info.string = 'Updating ...'
            busy = true
        }
        ...
    }, 
    ...
    doUpdate: function () {
        if (this.isBusy()) {
            return
        }

        _isUpdating = true
        _am.update()	// 热更模块更新版本。
    },
	...
});

将节点挂载到脚本对应变量上。
在这里插入图片描述
指定更新按钮点击后的回调函数。
在这里插入图片描述
运行起来,检测到更新后点击立即更新按钮。一瞬间,发现已经更新成功了。
在这里插入图片描述

更新了新版本后,writablePath下的变动

hotUpdate
	|- assets
		|- ...
	|- project.manifest

热更新成功了,hotUpdate_temp目录下的文件都放到了hotUpdate目录中,同时hotUpdate_temp目录被删除。
如果此时重新运行程序,会发现提示已经是最新版本。
在这里插入图片描述
这是因为hotUpdate目录中存在project.manifest文件,并且与服务器的进行比对,发现版本号一致,都是1.0.1
如果此时想重新测试,需要删除掉hotUpdate目录。
显示出来的Storage Path就是为了方便找到热更目录。

显示进度

创建文本框以及进度条。
在这里插入图片描述在这里插入图片描述
hotUpdateController.js中实现功能。

...
cc.Class({
    ...
    properties: {
        ...
        fileProgress: cc.ProgressBar, 	// 总文件数进度条。
        filePercent: cc.Label, 	// 总文件数百分比。
        byteProgress: cc.ProgressBar, 	// 总字节数进度条。
        bytePercent: cc.Label, 	// 总字节数百分比。
    },

    onLoad () {
        ...
        this.fileProgress.progress = 0
        this.byteProgress.progress = 0
        this.filePercent.string = ''
        this.bytePercent.string = ''
    },
	...
    eventCallback: function (event) {
        ...
        switch (event.getEventCode())
        {
            ...
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                this.byteProgress.progress = event.getPercent()		// 更新总字节数进度。
                this.fileProgress.progress = event.getPercentByFile()	// 更新总文件数进度。

				// 已下载的文件数 / 总文件数
                this.filePercent.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles()
                cc.log('filePercent.string: ' + this.filePercent.string)
                // 已下载的字节数 / 总字节数
                this.bytePercent.string = event.getDownloadedBytes() + ' / ' + event.getTotalBytes()
                cc.log('bytePercent.string: ' + this.bytePercent.string)

                if (message) {
                    this.info.string = 'Updated file: ' + message
                    cc.log(event.getPercent() / 100 + '% : ' + message)
                }
                break
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                this.byteProgress.progress = 1
                this.fileProgress.progress = 1
                this.filePercent.string = ''
                this.bytePercent.string = ''
                ...
            ...
        }
    }, 
	...
});

将节点挂载到脚本对应变量上。
在这里插入图片描述
运行起来,一瞬间进度就满了。
在这里插入图片描述

重试更新新版本

如果在热更新的过程中,由于网络等原因导致热更新失败。热更新模块也提供了相应的函数重新尝试更新。
首先创建一个重试按钮。
在这里插入图片描述在这里插入图片描述
hotUpdateController.js中实现按钮的功能。

...
cc.Class({
    ...
    properties: {
        ...
        retryButton: cc.Node, 		// 重试按钮。
    },

    onLoad () {
        ...
        this.retryButton.active = false		// 重试按钮默认不显示,只有更新失败才显示。
    },
    ...
    eventCallback: function (event) {
        ...
        switch (event.getEventCode())
        {
            ...
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                ...
                this.retryButton.active = false		// 更新成功,隐藏重试按钮。
                ...
                break
            case jsb.EventAssetsManager.UPDATE_FAILED:
                ...
                this.retryButton.active = true		// 更新失败,显示重试按钮。
                ...
                break
            ...
        }
    }, 
	...
    retry: function () {
        if (this.isBusy()) {
            return
        }

        _isUpdating = true
        _am.downloadFailedAssets()		// 热更模块重试更新。
    }, 
	...
});

将节点挂载到脚本对应变量上。
在这里插入图片描述
指定重试按钮点击后的回调函数。
在这里插入图片描述
运行起来,先检测更新。然后关闭服务器,再点立即更新。
等待一段时间后,会发现提示更新失败,与此同时重试按钮也显示了出来。
在这里插入图片描述
重启服务器,再点重试按钮,提示更新成功。

划重点

  • 检测新版本AssetsManager.checkUpdate()
  • 更新新版本AssetsManager.update()
  • 显示进度event.getPercent()event.getPercentByFile()event.getDownloadedFiles()event.getTotalFiles()event.getDownloadedBytes()event.getTotalBytes()
  • 重试更新AssetsManager.downloadFailedAssets()

以上是关于Cocos Creator 热更新 [Lv.1] 的主要内容,如果未能解决你的问题,请参考以下文章

Cocos Creator 热更新 [Lv.1]

Cocos Creator 热更新 [Lv.1]

Cocos Creator 热更新 [Lv.1]

Cocos Creator 热更新 [Lv.1]

Cocos Creator 热更新文件MD5计算和需要注意的问题

Cocos Creator 热更新 [Lv.2]