444 lines
16 KiB
TypeScript
Raw Permalink Normal View History

2025-04-24 17:03:28 +08:00
import Report from "../net/Report";
import SKDataUtil from "./SKDataUtil";
import SKLogger from "./SKLogger";
import SKTimeUtil from "./SKTimeUtil";
/**
*
* SKHotUpdate 1.0.0
*/
export default class SKHotUpdate {
public static hasNewBlock: () => void;
public static changeBlock: (info: string) => void;
public static progressBlock: (info: string) => void;
public static endBlock: () => void;
static shared: SKHotUpdate = new SKHotUpdate();
static isDetails: boolean = false;
// dir: string = "remote-asset";
dir: string = "fixed";
manifest: cc.Asset;
storagePath: string;
assetsManager: jsb.AssetsManager;
updating: boolean = false;
version: string = "";
remoteVesion: string = "";
checkPercent: string;
failCount: number;
maxFailCount: number;
percent: number = 0;
checkTimer: number = 0;
// 定時檢查更新
static delayCheck() {
this.shared.delayCheck();
}
// 檢查熱更新
static checkUpdate() {
this.shared.checkHot();
}
// 修改熱更新地址
static modify(updateURL: string) {
this.shared.modify(updateURL);
}
// 修改熱更新地址
private modify(updateURL: string) {
if (!cc.sys.isNative) {
SKLogger.debug("H5不能修改熱更新地址!");
return;
}
if (this.updating) {
SKLogger.debug("正在熱更中,不能修改熱更新地址!");
return;
}
let manifest = `${this.rootPath()}/project.manifest`;
let hasUpdate = jsb.fileUtils.isFileExist(manifest);
if (!hasUpdate) {
if (this.manifest == null) {
cc.loader.loadRes("project", (err, result) => {
if (err) {
SKLogger.warn(`加載本地清單文件失敗:${err}`);
return;
}
this.manifest = result;
this.modifyManifest(this.manifest.nativeUrl, updateURL);
})
} else {
this.modifyManifest(this.manifest.nativeUrl, updateURL);
}
} else {
this.modifyManifest(manifest, updateURL);
}
}
// 修改project.manifest文件
private modifyManifest(manifestPath: string, updateURL: string) {
let loadManifest = jsb.fileUtils.getStringFromFile(manifestPath);
let data = SKDataUtil.jsonBy(loadManifest);
if (data.packageUrl == updateURL) {
SKLogger.debug(`熱更清單[${manifestPath}]包地址已經是[${updateURL}]!`);
return;
}
let lastURL = data.packageUrl;
data.packageUrl = updateURL;
data.remoteManifestUrl = `${updateURL}/project.manifest`;
data.remoteVersionUrl = `${updateURL}/version.manifest`;
let afterString = SKDataUtil.toJson(data);
let isWritten = jsb.fileUtils.writeStringToFile(afterString, manifestPath);
if (isWritten) {
SKLogger.debug(`熱更清單[${manifestPath}]包地址[${lastURL}->${updateURL}]修改成功`);
} else {
SKLogger.debug(`熱更清單[${manifestPath}]包地址[${lastURL}->${updateURL}]修改失敗`);
}
}
// 結束
private end() {
if (!this.updating) {
return;
}
this.updating = false;
if (this.assetsManager) {
this.assetsManager.setEventCallback(null);
}
if (SKHotUpdate.hasNewBlock) {
let self = this;
this.checkTimer = SKTimeUtil.delay(() => {
self.checkHot();
}, 60 * 5);
return;
}
if (SKHotUpdate.endBlock) {
SKHotUpdate.endBlock();
}
}
// 檢查更新
private checkHot() {
if (!cc.sys.isNative) {
SKLogger.debug("H5無需檢查熱更新!");
if (SKHotUpdate.endBlock) {
SKHotUpdate.endBlock();
}
return;
}
SKLogger.debug("開始檢查更新...");
if (this.manifest == null) {
cc.loader.loadRes("project", (err, result) => {
if (err) {
SKLogger.warn(`加載本地清單文件失敗:${err}`);
this.end();
return;
}
this.manifest = result;
this.checkUpdate();
})
} else {
this.checkUpdate();
}
}
rootPath(): string {
let result = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + this.dir);
if (cc.sys.os === cc.sys.OS_IOS) {
result = result + "Documents/";
}
return result;
}
// 檢查更新
checkUpdate() {
if (this.updating) {
SKLogger.debug("正在檢查更新...");
return;
}
if (!this.assetsManager) {
this.storagePath = this.rootPath();
SKLogger.debug(`檢查本地緩存,存儲路徑:${this.storagePath}`);
this.assetsManager = new jsb.AssetsManager("", this.storagePath, this.compareVersion.bind(this));
this.assetsManager.setVerifyCallback((path: string, asset: any) => {
let compressed = asset.compressed;
let expectedMD5 = asset.md5;
let relativePath = asset.path;
let size = asset.size;
SKLogger.debug(`版本驗證:是否壓縮=${compressed},MD5=${expectedMD5},相對路徑=${relativePath},大小=${size}`);
if (compressed) {
return true;
} else {
return true;
}
});
if (cc.sys.os === cc.sys.OS_ANDROID) {
// @ts-ignore
this.assetsManager.setMaxConcurrentTask(5);
}
}
// 加載本地manifest
if (this.assetsManager.getState() === jsb.AssetsManager.State.UNINITED) {
let url = this.manifest.nativeUrl;
SKLogger.debug(`加載本地清單:${url}`);
this.assetsManager.loadLocalManifest(url);
}
let localManifest = this.assetsManager.getLocalManifest();
if (!this.assetsManager.getLocalManifest() || !localManifest.isLoaded()) {
SKLogger.warn("加載本地清單失敗!");
this.end();
return;
}
this.version = localManifest.getVersion();
SKLogger.debug(`本地版本:${this.version}`);
Report.version = this.version;
this.assetsManager.setEventCallback(this.checkEvent.bind(this));
this.assetsManager.checkUpdate();
this.updating = true;
}
// 檢查事件
private checkEvent(event: jsb.EventAssetsManager) {
let code = event.getEventCode();
let msg = event.getMessage();
let hasUpdate: boolean = false;
switch (code) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
SKLogger.warn(`檢查:未找到本地清單文件[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
SKLogger.warn(`檢查:下載清單文件錯誤[code=${code},msg=${msg}]`)
this.end();
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
SKLogger.debug(`檢查:解析清單文件錯誤[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
SKLogger.debug(`檢查:已經是最新的版本:${this.version}[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
SKLogger.debug(`檢查:正在進行中...[code=${code},msg=${msg}]`);
return;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
let info = `檢查:有新版本:${this.version}->${this.remoteVesion}`;
// @ts-ignore
let size = (this.assetsManager.getTotalBytes() / 1024 / 1024).toFixed(1);
info += ` ${size}KB`;
SKLogger.debug(info);
hasUpdate = true;
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
SKLogger.warn(`檢查:錯誤的更新...`);
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
SKLogger.debug(`檢查:更新完成`);
this.end();
break;
default:
SKLogger.warn(`檢查:未處理的檢查事件[code=${code},msg=${msg}]`);
return;
}
this.assetsManager.setEventCallback(null);
this.updating = false;
if (hasUpdate) {
let info = `檢查:有新版本:${this.version}->${this.remoteVesion}`;
// @ts-ignore
let size = Math.ceil(this.assetsManager.getTotalBytes() / 1024);
info += ` ${size}KB`;
if (SKHotUpdate.hasNewBlock) {
SKHotUpdate.hasNewBlock();
return;
}
if (SKHotUpdate.changeBlock) {
SKHotUpdate.changeBlock(info);
}
this.startUpdate();
}
}
// 更新事件
private updateEvent(event: jsb.EventAssetsManager) {
let code = event.getEventCode();
let msg = event.getMessage();
let assetId = event.getAssetId();
if (!assetId) {
assetId = "";
}
switch (code) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
SKLogger.debug(`更新:沒有本地清單[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
SKLogger.debug(`更新:下載清單失敗[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
SKLogger.debug(`更新解析清單失敗[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
SKLogger.debug(`更新:有新的版本[${this.remoteVesion}]`);
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
SKLogger.debug(`更新:已經是最新版本[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
let percent = event.getPercent();
if (isNaN(percent)) {
if (this.checkPercent.length < 3) {
this.checkPercent += ".";
} else {
this.checkPercent = "";
}
let show = this.checkPercent;
for (let i = this.checkPercent.length; i < 3; i++) {
show += " ";
}
SKLogger.debug(`更新:進行中[code=${code},msg=${msg}]`);
this.percent = 0;
if (SKHotUpdate.progressBlock) {
let info = `更新:正在進行中${show}`;
SKHotUpdate.progressBlock(info);
}
} else {
this.percent = percent * 100;
let result = (Math.min(1, percent) * 100).toFixed(2) + "%";
let loadedFiles = event.getDownloadedFiles();
let totalFiles = event.getTotalFiles();
let loadedBytes = (event.getDownloadedBytes() / 1024 / 1024).toFixed(1);
let totalBytes = (event.getTotalBytes() / 1024 / 1024).toFixed(1);
let info = `更新:進度...${result}`;
if (SKHotUpdate.isDetails) {
info += `-文件(${loadedFiles}/${totalFiles})大小(${loadedBytes}MB/${totalBytes}MB)`;
}
SKLogger.debug(`${info}:${msg}`);
if (SKHotUpdate.progressBlock) {
SKHotUpdate.progressBlock(info);
}
}
break;
case jsb.EventAssetsManager.ASSET_UPDATED:
SKLogger.debug(`更新:ASSET更新[assetId=${assetId}]`);
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
SKLogger.debug(`更新:完成[code=${code},msg=${msg}]`);
this.restart();
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
SKLogger.debug(`更新:錯誤[code=${code},msg=${msg}]`);
this.end();
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
SKLogger.debug(`更新:失敗[assetId=${assetId}]`);
this.failCount++;
if (this.failCount < this.maxFailCount) {
this.assetsManager.downloadFailedAssets();
let info = `更新失敗重試第${this.failCount}次...`;
if (SKHotUpdate.progressBlock) {
SKHotUpdate.progressBlock(info);
}
} else {
this.failCount = 0;
this.end();
}
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
SKLogger.debug(`更新:解壓錯誤[code=${code},msg=${msg}]`);
this.end();
break;
default:
SKLogger.debug(`更新:未處理的更新事件[code=${code},msg=${msg}]`);
this.end();
break;
}
}
// 重啟
private restart() {
SKLogger.debug("更新完成,重啟遊戲...");
this.version = this.remoteVesion;
Report.version = this.version;
this.assetsManager.setEventCallback(null);
// @ts-ignore
let searchPaths = jsb.fileUtils.getSearchPaths();
let newPaths = this.assetsManager.getLocalManifest().getSearchPaths();
Array.prototype.unshift(searchPaths, newPaths);
cc.sys.localStorage.setItem("HotUpdateSearchPaths", JSON.stringify(searchPaths));
jsb.fileUtils.setSearchPaths(searchPaths);
cc.audioEngine.stopAll();
cc.game.restart();
}
// 版本比較
private compareVersion(versionA: string, versionB: string): number {
SKLogger.debug(`版本比較:${versionA}->${versionB}`);
this.remoteVesion = versionB;
let vA = versionA.split('.');
let vB = versionB.split('.');
for (let i = 0; i < vA.length; ++i) {
let a = parseInt(vA[i]);
let b = parseInt(vB[i] || '0');
if (a === b) {
continue;
} else {
return a - b;
}
}
if (vB.length > vA.length) {
return -1;
}
return 0;
}
// 開始更新
startUpdate() {
if (this.updating) {
SKLogger.debug(`正在更新中...`);
return;
}
if (this.assetsManager == null) {
SKLogger.debug("沒有下載管理器,無法更新!");
this.end();
return;
}
this.assetsManager.setEventCallback(this.updateEvent.bind(this));
// 加載本地manifest
if (this.assetsManager.getState() === jsb.AssetsManager.State.UNINITED) {
let url = this.manifest.nativeUrl;
if (cc.loader.md5Pipe) {
url = cc.loader.md5Pipe.transfromURL(url);
}
this.assetsManager.loadLocalManifest(url);
}
this.assetsManager.update();
this.updating = true;
this.checkPercent = "";
this.failCount = 0;
this.maxFailCount = 3;
let info = "開始更新...";
SKLogger.debug(info);
if (SKHotUpdate.changeBlock) {
SKHotUpdate.changeBlock(info);
}
}
delayCheck(): void {
if (!cc.sys.isNative) {
SKLogger.debug("H5無需檢查熱更新!");
if (SKHotUpdate.endBlock) {
SKHotUpdate.endBlock();
}
return;
}
if (this.updating) {
SKLogger.debug("正在更新中,不能重複檢查!");
return;
}
let self = this;
cc.game.on(cc.game.EVENT_HIDE, function () {
SKLogger.debug("熱更:遊戲進入後台");
self.checkTimer = SKTimeUtil.cancelLoop(self.checkTimer);
self.checkHot();
}, this);
cc.game.on(cc.game.EVENT_SHOW, function () {
SKLogger.debug("熱更:重新返回游戲");
self.checkTimer = SKTimeUtil.cancelLoop(self.checkTimer);
self.checkHot();
}, this);
this.checkTimer = SKTimeUtil.delay(() => {
self.checkHot();
}, 60 * 5);
}
}