前言
前面 学了 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 函数
2: HelloWorld.js 代码如下
cc.Class({
extends: cc.Component,
properties: {
label: {
default: null,
type: cc.Label
},
manifestUrl: {
type: cc.Asset,
default: null,
},
_updating: false,
_canRetry: false,
_storagePath: ''
},
onLoad: function () {
this.outputlog("onLoad");
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');
this.outputlog('Storage path for remote asset : ' + this._storagePath);
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;
}
};
this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle);
this._am.setVerifyCallback(function (path, asset) {
var compressed = asset.compressed;
var expectedMD5 = asset.md5;
var relativePath = asset.path;
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) {
this._am.setMaxConcurrentTask(2);
this.outputlog("Max concurrent tasks count have been limited to 2");
}
},
update: function (dt) {
},
outputlog:function(){
let strlog="";
for (var i=0; i<arguments.length; i++) {
strlog = strlog + arguments[i];
}
console.log(strlog);
},
showlablestr:function(str){
this.label.string = str ;
},
checkfun:function(){
if (this._updating) {
this.label.string = 'Checking or updating ...';
return;
}
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
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;
},
hotupdatefun:function(){
if (this._am && !this._updating) {
this._am.setEventCallback(this.updateCb.bind(this));
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
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;
var searchPaths = jsb.fileUtils.getSearchPaths();
var newPaths = this._am.getLocalManifest().getSearchPaths();
this.outputlog(JSON.stringify(newPaths));
Array.prototype.unshift.apply(searchPaths, newPaths);
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文件用 2> 把 version_generator.js 放到工程根目录下
version_generator.js内容如下
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var manifest = {
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: []
};
var dest = 'assets/';
var src = 'build/jsb-link/';
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 = 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;
}
}
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 文件
如果没配置或跟文件里的不一样可以用 这里第一次版本号先用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 一一对应的 执行 node version_generator.js 或 node version_generator.js -v -u -s -d 带参数这种(个人觉得直接在文件里写好省事)
为什么会生成在这,看 version_generator.js 里配置 或看 -d 的参数 把project.manifest 文件 拖过去 保存场景 再次构建**(第二次)** , 构建完了,修改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');
}
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 (根据机子好坏等段时间) 编译完成得到APK,路径 这里选择的是link模板,选择default 路径有点区别的 以上是生成APK的工程,可以保存起来,以后修改都是在这个基础上的进行的
3:修改工程测试热跟新 1: 第一次测试,先修改 icon 再加个 lable ,简单修改下代码 目标:热更新完了,左下角新增加了lable 显示 原lable内容+[测试] 2个字 cocos 图标 会增加一条竖的红线 修改如下构建,构建就可以了,构建完了,修改 version_generator.js里的 版本号 这里修改为1.0.1 修改 再执行 node version_generator.js 生成新的 2个manifest 把生成的project.manifest version.manifest ,这里在assets 目录下,不确定是最新的话,看修改时日期 同时把 build\jsb-link 目录下 res,src 目录放到 packageUrl 写的目录下, 这里是本地测试,192.168.1.4是本机IP,装的是xampp ,apk 装在模拟器里测试的 安装完APK 的显示 点击check
点击hotupdate 结果如下图 ,重新启动APK,结果也如下图,这样代表 热更新OK了 最后看看在android 里的热更新 下载代码与资源存放目录 运行在夜神模拟器7.0.0.5里,路径 /data/data/包名/files 下
4 热更新及测试结束(记得保存一份生成APK的工程代码,后面任何改动可以以此为基础) 1: project.manifest 一定要在 manifestUrl:{ type: cc.Asset,… } 上,不需要删,执行 node version_generator.js 会覆盖的 2: 只需要构建 3: 修改版本号 (version_generator.js 里) 4: 执行 node version_generator.js 5: 把相应文件及目录放入远端服务器目录下就行了 工程代码如有需要后续再上传
|