版本:2.4.2

 

参考:

cocos教程:热更新范例教程

cocos教程:热更新管理器

csdn:Cocos Creator 热更新(动态修改热更地址)

 

demo下载:

hotUpdateDemo

 

这里用cocos2.4.2版本,从零实现android热更新,从1.0.0版本热更到2.0.0版本的demo操作流程。从而了解热更新的基本环境搭建和原理。

 

一 看官方教程

二 从零新建项目,实现android热更新

三 版本从1.0.0热更新到2.0.0

四 遇到的问题

五 动态热更

六 强更新

一 看官方教程

首先浏览下官方的教程热更新范例教程热更新管理器。知道热更新大概的原理流程。

更新流程大致如下:

  1. 基于原生打包目录中的 assets 和 src 目录生成本地 Manifest 文件。
  2. 创建一个热更新组件来负责热更新逻辑。
  3. 游戏发布后,若需要更新版本,则生成一套远程版本资源,包含 assets 目录、src 目录和 Manifest 文件,将远程版本部署到服务端。
  4. 当热更新组件检测到服务端 Manifest 版本不一致时,就会开始热更新

 

本地构建发布android文件和热更新远程文件如下:

 

assets和src:代码和图片等资源

project.manifest:版本配置文件

version.manifest:project.manifest的一部分,因为project.manifest保存了资源配置导致过大,每次加载影响体验,所以抽离了远程资源地址和版本号到version.manifest中,以方便快速加载比对版本。

 

project.manifiest文件:

 version.manifest

 

 

二 从零新建项目,实现android热更新

1.  搭建原生开发环境,新建空项目,并构建-编译-运行android。

 参考:cocos原生开发环境配置

 

2. 下载官网Demo

下载地址:https://github.com/cocos-creator/tutorial-hot-update

下载下来就有了疑问,demo是什么年代的版本?都几年几个月没更新了,要配置什么环境怎么样才能跑起来?要用到当下新版本里要怎么修改?

api过时了怎么办?跑起来各种报错怎么整?我用的TS但是demo是JS怎么办?

所以demo我就不跑了,新建一个项目跑。

 

3. 复制热更新插件

新建一个TS项目后,复制demo里的packages到新项目中相同位置。packages里是热更新插件,会在构建项目时,在main.js里插入一段热更新相关代码。

 

4. 本地搭建远程资源目录

在新项目中创建remote-assets文件夹,用于存放远程版本资源。

 

安装python2.7.5+版本,我用的是2.7.13,在新项目中创建一个python_server.bat文件

 

文件内容如下:

python -m SimpleHTTPServer 8000

 

双击bat,相当于以新项目为根目录搭建了一个简易服务器。

 

浏览器输入如下,则相当于访问新项目远程资源remote-assets文件夹 (192.168.0.60替换成你电脑本地IP)

http://192.168.0.60:8000/remote-assets/

 

 

5. 复制并修改热更新组件

从demo中找到HotUpdate.js,cocos提供的是js的,里面是热更新UI和逻辑的代码,我新项目用的是ts。

把demo的HotUpdate.js的代码复制到新项目的HotUpdate.ts里,并做相应的修改。

 

修改1:

demo里的版本文件ManifestStr是直接写死在HotUpdate.js里,这肯定是不行的。

 

修改将customManifestStr值从版本文件manifest获取

 

修改2:

cc.loader已经过时,将两处使用cc.loader转换md5的地方都注释掉。

 

修改3:

将demo里用到的组件UI和变量都重新写过,新项目用的UI丑点无所谓,组件重命名了也没关系,反正只要和官方demo的匹配上就行。

 

另外的修改可以参考Demo源码,把HotUpdate.ts组件挂到新项目场景组件上,这样新项目加载场景后就可以执行热更新了。

 

6. 复制版本生成器

复制version_generator.js到新项目中相同位置,这个版本生成文件会生成当前版本的project.manifest和version.manifest配置文件。

 

新项目中新建热更新.bat文件,这个用于执行version_generator.js。

 

热更新.bat内容如下:

@ECHO OFF
@node version_generator.js -v 1.0.0 -u http://192.168.0.60:8000/remote-assets/ -s build/jsb-link/ -d assets/
pause

参数说明:

  • -v 指定 Manifest 文件的主版本号。
  • -u 指定服务器远程包的地址,这个地址需要和最初发布版本中 Manifest 文件的远程包地址一致,否则无法检测到更新。
  • -s 本地原生打包版本的目录相对路径。
  • -d 保存 Manifest 文件的地址。

 

总结下新项目现在都做了什么:

1. 新建项目,并能够正常构建-编译-运行,在真机上可以跑起来这个空项目。

2. 下载官方热更新demo

3. 复制了热更新插件package

4. 本地搭建了远程版本资源目录remote-assets

5. 热更新组件HotUpdate.js改成了ts

6. 复制了版本生成器version_generator.js

 

做完以上的操作,基本环境就准备好了。

 

三  版本从1.0.0热更新到2.0.0

大致操作流程如下:

1. 构建2.0.0项目

2. 将2.0.0项目资源和版本配置放到远程资源文件夹remote-assets

3. 构建1.0.0项目

4. 运行1.0.0项目,热更新到2.0.0

 

1. 构建2.0.0项目

发布原生不需要勾选md5

 

2.执行热更新.bat

将热更新.bat的-v修改成2.0.0,双击执行。这是根据当前2.0.0项目,生成了2.0.0版本文件project.manifest和version.manifest,将这两个文件复制放到resource下。

在MainScene上挂载HotUpdate.ts组件,并将project.manifest拖动赋值给组件HotUpdate的manifestUrl属性。

 

 

3. 再次构建项目

这是将上一步生成的project.manifest和version.manifest打包进去。将project.manifest和version.manifest复制到remote-assets下

 

 将build/js-link下assets和src文件夹复制到remote-assets下

 

这样就在remote-assets中有个2.0.0版本的热更包

 

 

 

4. 修改场景,构建项目

这是构建1.0.0版本项目,任意修改项目场景,比如增加一个图片,增加一个label文本等,能看出来和2.0.0不一样。

 

5. 执行热更新.bat

将热更新.bat的-v修改成1.0.0,双击执行,这是根据项目生成1.0.0版本的project.manifest和version.manifest。

将1.0.0版本的这两个文件覆盖掉项目resouces下2.0.0版本的。

 

6. 构建项目

这是将上一步生成的1.0.0版本文件project.manifest和version.manifest打包进去

 

7. androd studio 真机运行项目(也可以cocos模拟器先跑跑)

真机运行时,是1.0.0版本

 

点击检查更新,提示有新版本

 

点击立即更新,则更新到2.0.0版本

 

四  遇到的问题

1. 发布原生不要勾选md5 Cache

发布原生不需要勾选md5,勾选了以后热更新会找不到文件。

例如不勾选md5时发布文件名为abc.json,勾选md5后文件名变成abc.aaa.json,会多出一串字符。但是热更新仍然会去找abc.json,导致找不到。

 

2. 真机每次测试需要删除一次app

运行项目时,需要删除手机上app,这样才能删除原apk的版本文件缓存.

 

3.热更能从1.0.0更新到2.0.0,能不能从2.0.0回退到1.0.0?

官方提供的版本比较函数,只有服务端版本>客户端版本时,才会进行更新。所以不能从2.0.0回退到1.0.0版本。

    /**
     * 版本对比
     * @param versionA 客户端版本
     * @param versionB 服务端版本
     * @returns 客户端>服务端返回正数; 客户端=服务端返回0; 客户端<服务端返回负数
     */
    private versionCompareHandle (versionA, versionB) {
        console.log("[HotUpdate] JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
        var vA = versionA.split('.');
        var vB = versionB.split('.');
        for (var i = 0; i < vA.length; ++i) {
            var a = parseInt(vA[i]);
            var b = parseInt(vB[i] || 0);
            if (a === b) {
                continue;
            }
            else {
                return a - b;
            }
        }
        if (vB.length > vA.length) {
            return -1;
        }
        else {
            return 0;
        }
    };

 需要修改如下,只要服务端和客户端版本号不一致,就进行更新,这样可以进行版本回退。

    /**
     * 版本对比
     * @param versionA 客户端版本
     * @param versionB 服务端版本
     * @returns 客户端>服务端返回正数; 客户端=服务端返回0; 客户端<服务端返回负数
     */
    private versionCompareHandle (versionA, versionB) {
        console.log("[HotUpdate] JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
        var vA = versionA.split('.');
        var vB = versionB.split('.');
        //长度不相等,则进行更新
        if(vA.length != vB.length){
            return -1;
        }
        for (var i = 0; i < vA.length; ++i) {
            var a = parseInt(vA[i]);
            var b = parseInt(vB[i] || 0);
            //数字相同,则跳过
            if (a === b) {
                continue;
                //数字不同,则进行更新
            }else {
                return -1;
            }
        }
        //长度相等且数字相等,则不更新
        return 0;
    };

 

五 动态更新

为什么需要动态热更地址?

 cocos的热更新地址是放在project.manifest文件里的。如果这个地址固定的话,当游戏有新版本,需要打包测试热更时,就必须放在这个地址下测试,而线上用户也是这个地址,势必会影响用户的使用。

所以热更的地址理想存放一个版本一个地址

game01/version1.0.0

game01/version1.0.1

game01//version2.0.0

 

需要修改哪里才能实现动态地址热更?

热更新的代码位置,具体可以查看源码

 

热更新中manifest文件的作用如下,动态指定热更地址需要修改游戏内resouces下manifest以及原生热更目录下manifest两个地方的文件。

 

 

在HotUpdate.ts中加入修改manifest文件代码。

当第一次热更时,本地热更目录下是没有manfiest文件的,需要创建一个并修改该文件热更地址。

当第二次或之后热更新,本地热更目录下存在manifest文件,则修改该文件热更地址。

hotUpdateUrlCache用于判断manifest是否已修改为最新地址,如果已修改为最新地址,则不需要再次修改。

    /**
     * 修改.manifest文件
     * @param {新的升级包地址} newAppHotUpdateUrl       http://192.168.0.119:8000/remote-assets-new/
     * @param {修改manifest文件后回调} resultCallback 
     */
    public modifyAppLoadUrlForManifestFile(newAppHotUpdateUrl, resultCallback) {
        //如果新地址和缓存地址一致,表示manifest已经被修改成最新地址,不需要再次修改manifest文件
        let hotUpdateUrlCache = cc.sys.localStorage.getItem(this.hotUpdateUrlCacheKey);
        if (hotUpdateUrlCache && hotUpdateUrlCache == newAppHotUpdateUrl) {
            resultCallback();
            return;
        }
        //本地热更目录下已存在project.manifest,则直接修改已存在的project.manifest
        if (jsb.fileUtils.isFileExist(this._storagePath + "/project.manifest")) {
            console.log("[HotUpdate] modifyAppLoadUrlForManifestFile: 有下载的manifest文件,直接修改热更地址");
            //修改project.manifest
            let projectManifest = jsb.fileUtils.getStringFromFile(this._storagePath + "/project.manifest");
            let projectManifestObj = JSON.parse(projectManifest);
            projectManifestObj.packageUrl = newAppHotUpdateUrl;
            projectManifestObj.remoteManifestUrl = newAppHotUpdateUrl + 'project.manifest';
            projectManifestObj.remoteVersionUrl = newAppHotUpdateUrl + 'version.manifest';
            let afterString = JSON.stringify(projectManifestObj);
            let isWrittenProject = jsb.fileUtils.writeStringToFile(afterString, this._storagePath + "/project.manifest");
            //更新数据库中的新请求地址,下次如果检测到不一致就重新修改 manifest 文件
            if (isWrittenProject) {
                cc.sys.localStorage.setItem(this.hotUpdateUrlCacheKey, newAppHotUpdateUrl);
            }
            console.log("[HotUpdate] 修改是否成功,project.manifest:", isWrittenProject);
            console.log("[HotUpdate] 修改后文件:", projectManifestObj.packageUrl, projectManifestObj.remoteManifestUrl, projectManifestObj.remoteVersionUrl);
            resultCallback();
            //不存在热更文件夹,则新建一个热更文件夹和project.manifest
        } else {
            console.log("[HotUpdate] modifyAppLoadUrlForManifestFile: 不存在热更文件夹,新建一个,然后修改热更地址");
            if (!jsb.fileUtils.isDirectoryExist(this._storagePath)) jsb.fileUtils.createDirectory(this._storagePath);
            //修改原始project文件
            let projectManifest = jsb.fileUtils.getStringFromFile(this.projectManifest.nativeUrl);
            let projectManifestObj = JSON.parse(projectManifest);
            projectManifestObj.packageUrl = newAppHotUpdateUrl;
            projectManifestObj.remoteManifestUrl = newAppHotUpdateUrl + 'project.manifest';
            projectManifestObj.remoteVersionUrl = newAppHotUpdateUrl + 'version.manifest';
            let afterString = JSON.stringify(projectManifestObj);
            let isWrittenProject = jsb.fileUtils.writeStringToFile(afterString, this._storagePath + '/project.manifest');
            if (isWrittenProject) {
                cc.sys.localStorage.setItem(this.hotUpdateUrlCacheKey, newAppHotUpdateUrl);
            }
            console.log("[HotUpdate] 修改是否成功,project.manifest:", isWrittenProject);
            console.log("[HotUpdate] 修改后文件:", projectManifestObj.packageUrl, projectManifestObj.remoteManifestUrl, projectManifestObj.remoteVersionUrl);
            resultCallback();
        }
    }

  

 

 游戏开始后,先访问后台获取最新热更地址,这里假设新地址是"http://192.168.0.119:8000/remote-assets/version2.0.1"。

然后调用modifyApploadUrlForManifestFile修改manifest文件的热更地址,修改后走正常热更流程。

        this.hotUpdate.modifyAppLoadUrlForManifestFile("http://192.168.0.119:8000/remote-assets/version2.0.1", ()=>{
            this.hotUpdate.checkUpdate();
        });

 

六 强更新

打开游戏后,从后台获取是否需要强更新,如果需要强更新,则打开浏览器下载新的app。

apkUrl是apk所在地址,比如http://www.test.com/test.apk

AppActivity.java:

    /**
     * 打开浏览器更新下载新版本apk
     * @param apkUrl    apk地址
     */
    public static void openBrowserUpdate(String apkUrl) {
        Intent intent = new Intent();
        intent.setAction("android.intent.action.VIEW");
        Uri apk_url = Uri.parse(apkUrl);
        intent.setData(apk_url);
        AppActivity.context.startActivity(intent); //startActivity无法在静态方法中使用,所以提前保存content
    }

 

因为startActivity不能在静态方法中使用,所以定义了一个context变量保存当前Activity。 

 

 

 

 

 

 

 

posted on 2020-08-31 10:16  gamedaybyday  阅读(3698)  评论(0编辑  收藏  举报