android cocoscreator 热更新 超详细篇(五)

前言

前面 学了 android cocoscreator 相互调用(三)
这章 主要学习在 android 里 热更 js 代码及资源(cocoscreator 官方也有介绍,不过不是很详细,也有用热插件的,商店里有),这章介绍手动操作脚本生成热更新文件及测试热更新

1: 准备
win7 64位
cocoscreator2.0.10 (新版本如 2.4.7应该也是可以的)
node 版本 13.14 版 (再高好像不支持win7了)

2:创建个helloworld 工程 再生成APK
1:增加2个按钮 check hotupdate 分别绑定 checkfun hotupdatefun 函数

android cocoscreator 热更新 超详细篇(五)_第1张图片
2: HelloWorld.js 代码如下

cc.Class({
    extends: cc.Component,

    properties: {
        label: {
            default: null,
            type: cc.Label
        },
        // defaults, set visually when attaching this script to the Canvas
     //   text: 'Hello, World!',
		/eh///
		//包名 暂定 org.cocos2d.helloworld
		//project.mainfest  文件存放地
		manifestUrl: {
            type: cc.Asset,     // use 'type:' to define Asset object directly
            default: null,      // object's default value is null
          },
        _updating: false,		//是否正在更新中
        _canRetry: false,		//能否重试
        _storagePath: ''		//android /data/data/包名/files/ 下就是这个存放目录
		/
    },

    // use this for initialization
    onLoad: function () {
      //  this.label.string = this.text;
	  /
        this.outputlog("onLoad");
        // Hot update is only available in Native build
        if (!cc.sys.isNative) {
            this.outputlog("jsb is not isNative");
            return;
        }
        if(!jsb){
            this.outputlog("jsb is null");
            return ;
        }

        this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'my-remote-asset');// /data/data/包名/files/my-remote-asset
        this.outputlog('Storage path for remote asset : ' + this._storagePath);

        // Setup your own version compare handler, versionA and B is versions in string
        // if the return value greater than 0, versionA is greater than B,
        // if the return value equals 0, versionA equals to B,
        // if the return value smaller than 0, versionA is smaller than B.
        var self = this;
        this.versionCompareHandle = function (versionA, versionB) {
            self .outputlog("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;
            }
        };

        // Init with empty manifest url for testing custom manifest
        this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle);
        // if (!cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {  // this._am.retain(); 没有这个函数  eh
        //     this._am.retain();
        // }
        // Setup the verification callback, but we don't have md5 check function yet, so only print some message
        // Return true if the verification passed, otherwise return false
        this._am.setVerifyCallback(function (path, asset) {
            // When asset is compressed, we don't need to check its md5, because zip file have been deleted.
            var compressed = asset.compressed;
            // Retrieve the correct md5 value.
            var expectedMD5 = asset.md5;
            // asset.path is relative path and path is absolute.
            var relativePath = asset.path;
            // The size of asset file, but this value could be absent.
            var size = asset.size;
            if (compressed) {
                self .outputlog("Verification passed : " + relativePath);
                return true;
            }
            else {
                self .outputlog("Verification passed : " + relativePath + ' (' + expectedMD5 + ')');
                return true;
            }
        });
        this.outputlog("Hot update is ready, please check or directly update.");
        this.label.string = "Hot update is ready, please check or directly update.";

        if (cc.sys.os === cc.sys.OS_ANDROID) {
            // Some Android device may slow down the download process when concurrent tasks is too much.
            // The value may not be accurate, please do more test and find what's most suitable for your game.
            this._am.setMaxConcurrentTask(2);
            this.outputlog("Max concurrent tasks count have been limited to 2");
        }
        // this.checkfun();
        ///
    },

    // called every frame
    update: function (dt) {

    },
	/eh///
	//日志输出
    outputlog:function(){//可变参数  arguments
        let strlog="";
		for (var i=0; i<arguments.length; i++) {
			strlog = strlog + arguments[i];
		}
		console.log(strlog);
    },
	
	showlablestr:function(str){
		this.label.string = str ;
	},
	button 绑定函数
	checkfun:function(){
		if (this._updating) {
            this.label.string = 'Checking or updating ...';
            return;
        }
        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
            // Resolve md5 url
            var url = this.manifestUrl.nativeUrl;
            if (cc.loader.md5Pipe) {
                url = cc.loader.md5Pipe.transformURL(url);
            }
            this._am.loadLocalManifest(url);
        }
        if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
            this.label.string = 'Failed to load local manifest ...';
            return;
        }
        this._am.setEventCallback(this.checkCb.bind(this));

        this._am.checkUpdate();
        this._updating = true;
	},
	
	button 绑定函数
	hotupdatefun:function(){
		if (this._am && !this._updating) {
            this._am.setEventCallback(this.updateCb.bind(this));

            if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
                // Resolve md5 url
                var url = this.manifestUrl.nativeUrl;
                if (cc.loader.md5Pipe) {
                    url = cc.loader.md5Pipe.transformURL(url);
                }
                this._am.loadLocalManifest(url);
            }

            this._am.update();
            this._updating = true;
        }
	},
	/热更代码 参考官方///
	checkCb: function (event) {
        this.outputlog('Code: ' + event.getEventCode());
        switch (event.getEventCode())
        {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.showlablestr("No local manifest file found, hot update skipped.");
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
            this.showlablestr("Fail to download manifest file, hot update skipped.");
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
            this.showlablestr( "Already up to date with the latest remote version.");
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                this.showlablestr('New version found, please try to update.');
                break;
            default:
                return;
        }
        
        this._am.setEventCallback(null);
        this._checkListener = null;
        this._updating = false;
    },

    updateCb: function (event) {
        var needRestart = false;
        var failed = false;
        switch (event.getEventCode())
        {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.showlablestr( 'No local manifest file found, hot update skipped.');
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                 this.showlablestr('Fail to download manifest file, hot update skipped.');
                failed = true;
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this.showlablestr('Already up to date with the latest remote version.');
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                this.showlablestr( 'Update finished. ' + event.getMessage());
                needRestart = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                this.showlablestr( 'Update failed. ' + event.getMessage());
                this._updating = false;
                this._canRetry = true;
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                this.showlablestr( 'Asset update error: ' + event.getAssetId() + ', ' + event.getMessage());
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                this.showlablestr( event.getMessage());
                break;
            default:
                break;
        }

        if (failed) {
            this._am.setEventCallback(null);
            this._updateListener = null;
            this._updating = false;
        }

        if (needRestart) {
            this._am.setEventCallback(null);
            this._updateListener = null;
            // Prepend the manifest's search path
            var searchPaths = jsb.fileUtils.getSearchPaths();
            var newPaths = this._am.getLocalManifest().getSearchPaths();
            this.outputlog(JSON.stringify(newPaths));
            Array.prototype.unshift.apply(searchPaths, newPaths);
            // This value will be retrieved and appended to the default search path during game startup,
            // please refer to samples/js-tests/main.js for detailed usage.
            // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect.
            //eh explain 跟main.js var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths'); 
            //必须一致  必须一致 必须一致
            cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
            jsb.fileUtils.setSearchPaths(searchPaths);

            cc.audioEngine.stopAll();
            cc.game.restart();
        }
    },

    loadCustomManifest: function () {
        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
            var manifest = new jsb.Manifest(customManifestStr, this._storagePath);
            this._am.loadLocalManifest(manifest, this._storagePath);
            this.showlablestr( 'Using custom manifest');
        }
    },
    
    retry: function () {
        if (!this._updating && this._canRetry) {
            this._canRetry = false;
            
            this.showlablestr( 'Retry failed Assets...');
            this._am.downloadFailedAssets();
        }
    },
   
    onDestroy: function () {
        if (this._updateListener) {
            this._am.setEventCallback(null);
            this._updateListener = null;
        }
    },
	
	/
});

3: 构建再执行 脚本生成 manifest 文件
1> 先构建**(第一次)**
构建是为了生成 build\jsb-link(或jsb_default)\ 这里选择的是link 模板
主要就是为了res 和 src 这两个目录 为后面生成manifest文件用android cocoscreator 热更新 超详细篇(五)_第2张图片
2> 把 version_generator.js 放到工程根目录下
android cocoscreator 热更新 超详细篇(五)_第3张图片

version_generator.js内容如下

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');

var manifest = {
	//记得先装个node 啊
    //packageUrl 服务器更新数据包根目录  hotupdate 目录下有 src res 加 2个 manifest(project,version) 如下
	//version  版本号
    packageUrl: 'http://192.168.1.4/hotupdate/',
    remoteManifestUrl: 'http://192.168.1.4/hotupdate/project.manifest',
    remoteVersionUrl: 'http://192.168.1.4/hotupdate/version.manifest',
    version: '1.0.0',
    assets: {},
    searchPaths: []
};

//生成的manifest文件存放目录 就是用node version_generator.js 生成2个manifest 存放的路径
var dest = 'assets/';
//项目构建后资源的目录
var src = 'build/jsb-link/'; //这里选择的是link模板,选择default 需要修改下

// Parse arguments
var i = 2;
while ( i < process.argv.length) {
    var arg = process.argv[i];

    switch (arg) {
    case '--url' :
    case '-u' :
        var url = process.argv[i+1];
        manifest.packageUrl = url;
        manifest.remoteManifestUrl = url + 'project.manifest';
        manifest.remoteVersionUrl = url + 'version.manifest';
        i += 2;
        break;
    case '--version' :
    case '-v' :
        manifest.version = process.argv[i+1];
        i += 2;
        break;
    case '--src' :
    case '-s' :
        src = process.argv[i+1];
        i += 2;
        break;
    case '--dest' :
    case '-d' :
        dest = process.argv[i+1];
        i += 2;
        break;
    default :
        i++;
        break;
    }
}


function readDir (dir, obj) {
    var stat = fs.statSync(dir);
    if (!stat.isDirectory()) {
        return;
    }
    var subpaths = fs.readdirSync(dir), subpath, size, md5, compressed, relative;
    for (var i = 0; i < subpaths.length; ++i) {
        if (subpaths[i][0] === '.') {
            continue;
        }
        subpath = path.join(dir, subpaths[i]);
        stat = fs.statSync(subpath);
        if (stat.isDirectory()) {
            readDir(subpath, obj);
        }
        else if (stat.isFile()) {
            // Size in Bytes
            size = stat['size'];
            md5 = crypto.createHash('md5').update(fs.readFileSync(subpath, 'binary')).digest('hex');
            compressed = path.extname(subpath).toLowerCase() === '.zip';

            relative = path.relative(src, subpath);
            relative = relative.replace(/\\/g, '/');
            relative = encodeURI(relative);
            obj[relative] = {
                'size' : size,
                'md5' : md5
            };
            if (compressed) {
                obj[relative].compressed = true;
            }
        }
    }
}

var mkdirSync = function (path) {
    try {
        fs.mkdirSync(path);
    } catch(e) {
        if ( e.code != 'EEXIST' ) throw e;
    }
}

// Iterate res and src folder
readDir(path.join(src, 'src'), manifest.assets);
readDir(path.join(src, 'res'), manifest.assets);

var destManifest = path.join(dest, 'project.manifest');
var destVersion = path.join(dest, 'version.manifest');

mkdirSync(dest);

fs.writeFile(destManifest, JSON.stringify(manifest), (err) => {
  if (err) throw err;
  console.log('Manifest successfully generated');
});

delete manifest.assets;
delete manifest.searchPaths;
fs.writeFile(destVersion, JSON.stringify(manifest), (err) => {
  if (err) throw err;
  console.log('Version successfully generated');
});

这里已经把远程路径配好了,所以只需要 执行 node version_generator.js 会产生manifest 文件
android cocoscreator 热更新 超详细篇(五)_第4张图片

这里第一次版本号先用1.0.0 (下次为个 1.0.1 或下次版本自己随意增加)
如果没配置或跟文件里的不一样可以用 命令
node version_generator.js -v 1.0.0 -u http://192.168.1.4/hotupdate/ -s build/jsb-link/ -d assets/
就4个附加参数 -v -u -s -d 知道为什么? 跟 version_generator.js 一一对应的
android cocoscreator 热更新 超详细篇(五)_第5张图片
执行 node version_generator.js 或 node version_generator.js -v -u -s -d 带参数这种(个人觉得直接在文件里写好省事)
android cocoscreator 热更新 超详细篇(五)_第6张图片

会在assets目录下生成2个manifest文件,为什么会生成在这,看 version_generator.js 里配置 或看 -d 的参数android cocoscreator 热更新 超详细篇(五)_第7张图片
把project.manifest 文件 拖过去
android cocoscreator 热更新 超详细篇(五)_第8张图片
保存场景 再次构建**(第二次)** ,
构建完了后,修改build\jsb-link\main.js 在 window.boot(); 前 (因为选择的是link 模板)

var isRuntime = (typeof loadRuntime === 'function');
    if (isRuntime) {
        require('src/settings.js');
        require('src/cocos2d-runtime.js');
        require('jsb-adapter/engine/index.js');
    }
    else {
        require('src/settings.js');
        require('src/cocos2d-jsb.js');
        require('jsb-adapter/jsb-engine.js');
    }
	///eh add/
	//HelloWorld.js  updateCb 函数 cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
	var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths'); //必须一致
	 if (hotUpdateSearchPaths) {
		  window.jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
	 }
	 require('src/settings.js'); //重新加载一次 或上面的就不加载
	 

    cc.macro.CLEANUP_IMAGE_CACHE = true;
    window.boot();

保存main.js,再点编译生成APK (根据机子好坏等段时间)
android cocoscreator 热更新 超详细篇(五)_第9张图片
编译完成得到APK,路径 这里选择的是link模板,选择default 路径有点区别的
android cocoscreator 热更新 超详细篇(五)_第10张图片
以上是生成APK的工程,可以保存起来,以后修改都是在这个基础上的进行的

3:修改工程测试热跟新
1: 第一次测试,先修改 icon 再加个新的 lable ,简单修改下代码
目标:热更新完了,左下角新增加了lable 显示 原lable内容+[测试] 2个字
cocos 图标 会增加一条竖的红线android cocoscreator 热更新 超详细篇(五)_第11张图片
修改完后保存,构建就可以了,构建完了,修改 version_generator.js里的 版本号 这里修改为1.0.1
android cocoscreator 热更新 超详细篇(五)_第12张图片
修改完后保存, 再执行 node version_generator.js 生成新的 2个manifest ,
把生成的 project.manifest version.manifest(这里在assets 目录下,不确定是最新的话,看修改时日期)
及 build\jsb-link 目录下 res,src 这两个目录一起放到 packageUrl 表示的服务器目录下,
android cocoscreator 热更新 超详细篇(五)_第13张图片
这里是本地测试,192.168.1.4是本机IP,装的是xampp ,apk 装在模拟器里测试的
android cocoscreator 热更新 超详细篇(五)_第14张图片
安装完APK 的显示
android cocoscreator 热更新 超详细篇(五)_第15张图片
点击check
android cocoscreator 热更新 超详细篇(五)_第16张图片

点击hotupdate 结果如下图 ,重新启动APK,结果也如下图,这样代表 热更新OK了
android cocoscreator 热更新 超详细篇(五)_第17张图片
最后看看在android 里的热更新 下载代码与资源存放目录
运行在夜神模拟器7.0.0.5里,路径 /data/data/包名/files 下android cocoscreator 热更新 超详细篇(五)_第18张图片

4 热更新及测试结束(记得保存一份生成APK的工程代码,后面任何改动可以以此为基础)
1: project.manifest 一定要在 manifestUrl:{ type: cc.Asset,… } 上,不需要删,执行 node version_generator.js 会覆盖的
2: 只需要构建
3: 修改版本号 (version_generator.js 里)
4: 执行 node version_generator.js
5: 把相应文件及目录放入远端服务器目录下就行了
工程代码如有需要后续再上传

你可能感兴趣的:(android,android,cocos-creator,javascript)