平台ios:
概率必现。
用一个update.fire场景挂载update脚本,启动场景也设为update.fire。假如第一次进去更新成功后调用cc.game.restart,此时游戏热重启又来到update场景,这时候assetManager判断已是最新版本。于是调用cc.loader.loadScene加载主场景main.fire。这时候就会报
ERROR: TypeError: split[1].split is not a function. (In ‘split[1].split("&")’, ‘split[1].split’ is undefined), location: src/cocos2d-jsb.js:21202:37
_parseUrlParam@src/cocos2d-jsb.js:21202:37
createItem@src/cocos2d-jsb.js:21216:35
append@src/cocos2d-jsb.js:21363:30
load@src/cocos2d-jsb.js:20223:21
loadAsset@src/cocos2d-jsb.js:24773:22
_loadSceneByUuid@src/cocos2d-jsb.js:6964:36
loadScene@src/cocos2d-jsb.js:6934:34
这时候强杀进程退出游戏,再进游戏就没问题,并且更新也成功了。
代码如下:
import { FileUtils } from "../common/native/fileutils";
import { AssetMgr } from "../logic/core/controller/assetmgr";
import { SceneMgr, SCENE_NAME } from "../common/ui/scene_mgr";
import { VersionCmpFunc } from "../const";
const verify_func = (path:string, asset) => {
// When asset is compressed, we don't need to check its md5, because zip file have been deleted.
let compressed:boolean = asset.compressed;
// Retrieve the correct md5 value.
let expectedMD5:string = asset.md5;
// asset.path is relative path and path is absolute.
let relativePath:string = asset.path;
// The size of asset file, but this value could be absent.
let size:number = asset.size;
if (compressed)
{
return true;
}
else
{
return true;
}
}
const LOCAL_MANIFEST = "project.manifest"; //放在build根目录。此目录默认在searchPath中
const {ccclass, property} = cc._decorator;
@ccclass
export class HotUpdate extends cc.Component {
@property(cc.Label)
txt_version: cc.Label = null;
@property(cc.Label)
txt_info: cc.Label = null;
@property(cc.Label)
txt_progress: cc.Label = null;
@property(cc.Node)
progressbar: cc.Node = null;
private _am:jsb.AssetsManager;
private _fileUtils:FileUtils;
onLoad()
{
// Hot update is only available in Native build
if (!cc.sys.isNative)
{
cc.log("is not native platform, startGame directly");
this.startGame();
return;
}
//版本文件不存在,直接进入游戏
this._fileUtils = FileUtils.getInst();
const localManifestExist = this._fileUtils.isFileExist(LOCAL_MANIFEST);
if(!localManifestExist)
{
cc.log("localManifest file not exist, startGame directly");
this.startGame();
return;
}
//加载本地版本文件
const storagePath = this._fileUtils.getWritablePath() + 'pianotap-remote-asset';
cc.log('Storage path for remote asset : ' + storagePath);
this._am = new jsb.AssetsManager("", storagePath, VersionCmpFunc);
this._am.setVerifyCallback(verify_func);
const manifestStr = this._fileUtils.getStringFromFile(LOCAL_MANIFEST);
const manifest = new jsb.Manifest(manifestStr, storagePath);
this._am.loadLocalManifest(manifest, storagePath);
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);
cc.log("Max concurrent tasks count have been limited to 2");
}
this.checkUpdate();
}
onDestroy()
{
if(this._am)
{
this._am.setEventCallback(null);
this._am = null;
}
}
private updateCb(event)
{
cc.log("hotupdate updateCb", event.getEventCode())
let isUpdateSuccess = false;
let failed = false;
switch (event.getEventCode())
{
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
cc.log('No local manifest file found, hot update skipped.');
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
const bytePercent = event.getPercent();
const msg = event.getMessage();
if(msg)
{
this.txt_info.string = `updating...`;
}
this.txt_progress.string = `loading:${(Math.min(1, bytePercent) * 100).toFixed(2)}%`;
this.progressbar.width = bytePercent * this.node.width;
break;
case jsb.EventAssetsManager.ASSET_UPDATED:
cc.log("ASSET_UPDATED=>", event.getAssetId())
this.txt_info.string = `asset updated ${event.getAssetId()}`;
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
cc.log('Fail to download manifest file, hot update skipped.');
failed = true;
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
cc.log('Already up to date with the latest remote version.');
failed = true;
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
cc.log('New version found');
this.txt_info.string = "new version found";
this.txt_progress.node.active = true;
this.progressbar.width = 0;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
cc.log('Update finished. ' + event.getMessage());
isUpdateSuccess = true;
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
cc.log('Retry failed Assets...');
this.txt_info.string = `update failed, ${event.getMessage()}`;
this._am.downloadFailedAssets();
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
cc.log("Asset update error:", event.getAssetId(), event.getMessage());
this.txt_info.string = `asset update error: ${event.getAssetId()}, ${event.getMessage()}`;
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
cc.log("ERROR_DECOMPRESS", event.getMessage());
break;
default:
break;
}
//更新成功重启游戏
if(isUpdateSuccess)
{
this._am.setEventCallback(null);
// Prepend the manifest's search path
let searchPaths = this._fileUtils.getSearchPaths();
let newPaths = this._am.getLocalManifest().getSearchPaths();
Array.prototype.unshift.apply(searchPaths, newPaths);
searchPaths.forEach((element, i) => {
cc.log(element, i);
});
this._fileUtils.setSearchPaths(searchPaths);
//保存searchPaths到本地存储供main.js读取。
const searchPathStr = JSON.stringify(searchPaths);
cc.sys.localStorage.setItem('HotUpdateSearchPaths', searchPathStr);
cc.log("hotupdate setSearchPaths->", searchPathStr);
//重启游戏
cc.audioEngine.stopAll();
cc.game.restart();
}
else if(failed)
{
this.startGame();
}
}
private checkUpdate()
{
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
cc.warn("!!!am.getState() === jsb.AssetsManager.State.UNINITED");
return;
}
this._am.setEventCallback(this.updateCb.bind(this));
this._am.update();
}
private startGame()
{
if(cc.sys.isNative)
{
AssetMgr.getInst().checkRemoteAssetVersion();
}
SceneMgr.getInst().loadScene(SCENE_NAME.main);
}
}