mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-04-22 11:28:40 +00:00
热更新工具修改
This commit is contained in:
parent
80558e51fe
commit
be4e48e8a0
326
src/hotupdate/HotUpdate.ts
Normal file
326
src/hotupdate/HotUpdate.ts
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* @Author: Gongxh
|
||||||
|
* @Date: 2025-04-19
|
||||||
|
* @Description: 热更新实例
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { game, native, sys } from "cc";
|
||||||
|
import { ICheckUpdatePromiseResult, IPromiseResult } from "../interface/PromiseResult";
|
||||||
|
import { ReadNetFile } from "../net/nettools/ReadNetFile";
|
||||||
|
import { debug, log } from "../tool/log";
|
||||||
|
import { Utils } from "../tool/Utils";
|
||||||
|
import { HotUpdateManager } from "./HotUpdateManager";
|
||||||
|
|
||||||
|
interface IHotUpdateConfig {
|
||||||
|
packageUrl: string;
|
||||||
|
remoteManifestUrl: string;
|
||||||
|
remoteVersionUrl: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IManifestResult extends IPromiseResult {
|
||||||
|
manifest?: IHotUpdateConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HotUpdateCode {
|
||||||
|
/** 成功 */
|
||||||
|
Succeed = 0,
|
||||||
|
/** 出错了 */
|
||||||
|
Error = -1,
|
||||||
|
/** 平台不支持 不需要热更新 */
|
||||||
|
PlatformNotSupported = -1000,
|
||||||
|
/** 未初始化 */
|
||||||
|
NotInitialized = -1001,
|
||||||
|
/** 是最新版本 */
|
||||||
|
LatestVersion = -1002,
|
||||||
|
/** 更新中 */
|
||||||
|
Updating = -1003,
|
||||||
|
/** 加载本地manifest失败 */
|
||||||
|
LoadManifestFailed = -1004,
|
||||||
|
/** 下载manifest文件失败 */
|
||||||
|
ParseManifestFailed = -1005,
|
||||||
|
|
||||||
|
/** 下载version.manifest失败 */
|
||||||
|
LoadVersionFailed = -1006,
|
||||||
|
/** 解析version.manifest失败 */
|
||||||
|
ParseVersionFailed = -1007,
|
||||||
|
|
||||||
|
|
||||||
|
/** 更新失败 需要重试 */
|
||||||
|
UpdateFailed = -1008,
|
||||||
|
/** 更新错误 */
|
||||||
|
UpdateError = -1009,
|
||||||
|
/** 解压错误 */
|
||||||
|
DecompressError = -1010,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAG = "hotupdate:";
|
||||||
|
export class HotUpdate {
|
||||||
|
/** 资源管理器 */
|
||||||
|
private _am: native.AssetsManager = null;
|
||||||
|
/** 更新进度回调 */
|
||||||
|
private _progress: (kb: number, total: number) => void = null;
|
||||||
|
private _complete: (code: HotUpdateCode, message: string) => void = null;
|
||||||
|
|
||||||
|
public get resVersion(): string {
|
||||||
|
return this._am?.getLocalManifest()?.getVersion() || "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取 version.manifest 文件的远程地址 */
|
||||||
|
private get versionUrl(): string {
|
||||||
|
return this._am?.getLocalManifest()?.getVersionFileUrl() || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let writablePath = HotUpdateManager.getInstance().writablePath;
|
||||||
|
let manifestUrl = HotUpdateManager.getInstance().manifestUrl;
|
||||||
|
|
||||||
|
// 创建 am 对象
|
||||||
|
this._am = new native.AssetsManager(manifestUrl, writablePath, Utils.compareVersion);
|
||||||
|
this._am?.setVerifyCallback(this._verifyCallback);
|
||||||
|
HotUpdateManager.getInstance().resVersion = this.resVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重试失败的资源 */
|
||||||
|
public retryUpdate(): void {
|
||||||
|
this._am.downloadFailedAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否存在热更新
|
||||||
|
* 提供一个对外的方法检查是否存在热更新
|
||||||
|
* @return {Promise<ICheckUpdatePromiseResult>}
|
||||||
|
*/
|
||||||
|
public checkUpdate(): Promise<ICheckUpdatePromiseResult> {
|
||||||
|
let localManifest: IHotUpdateConfig = null;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.readLocalManifest().then(res => {
|
||||||
|
log(`${TAG} 读取本地manifest文件结果:${JSON.stringify(res)}`);
|
||||||
|
if (res.code === HotUpdateCode.Succeed) {
|
||||||
|
localManifest = res.manifest;
|
||||||
|
return this.loadRemoteVersionManifest();
|
||||||
|
} else {
|
||||||
|
throw res;
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
log(`${TAG} 读取远程version.manifest文件结果:${JSON.stringify(res)}`);
|
||||||
|
// 获取远程version.manifest文件内容的结果
|
||||||
|
if (res.code === HotUpdateCode.Succeed) {
|
||||||
|
return this.refreshLocalManifest(localManifest, res.manifest);
|
||||||
|
} else {
|
||||||
|
throw res;
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
log(`${TAG} 刷新本地manifest文件结果:${JSON.stringify(res)}`);
|
||||||
|
if (res.code === HotUpdateCode.Succeed) {
|
||||||
|
return this.startCheckUpdate();
|
||||||
|
} else {
|
||||||
|
// 已经是最新版本了
|
||||||
|
throw res;
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
log(`${TAG} 检查更新结果:${JSON.stringify(res)}`);
|
||||||
|
resolve(res);
|
||||||
|
}).catch(res => {
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始热更新
|
||||||
|
* @param res.skipCheck 是否跳过检查更新
|
||||||
|
* @param res.progress 更新进度回调 kb: 已下载的资源大小, total: 总资源大小 (kb)
|
||||||
|
* @param res.complete 更新结束回调 根据错误码判断 跳过还是重试失败资源
|
||||||
|
*/
|
||||||
|
public startUpdate(res: { skipCheck?: boolean, progress: (kb: number, total: number) => void, complete: (code: HotUpdateCode, message: string) => void }): void {
|
||||||
|
this._progress = res.progress;
|
||||||
|
this._complete = res.complete;
|
||||||
|
|
||||||
|
if (res.skipCheck) {
|
||||||
|
this.startUpdateTask();
|
||||||
|
} else {
|
||||||
|
this.checkUpdate().then((res) => {
|
||||||
|
if (res.code === HotUpdateCode.Succeed) {
|
||||||
|
this.startUpdateTask();
|
||||||
|
} else {
|
||||||
|
this._complete(res.code, res.message);
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
this._complete(HotUpdateCode.Error, JSON.stringify(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startUpdateTask(): void {
|
||||||
|
this._am.setEventCallback((event: native.EventAssetsManager) => {
|
||||||
|
let eventCode = event.getEventCode();
|
||||||
|
debug(`${TAG} 更新回调code:${eventCode}`);
|
||||||
|
switch (eventCode) {
|
||||||
|
case native.EventAssetsManager.UPDATE_PROGRESSION: {
|
||||||
|
let bytes = event.getDownloadedBytes() / 1024;
|
||||||
|
let total = event.getTotalBytes() / 1024;
|
||||||
|
this._progress(bytes, total);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case native.EventAssetsManager.UPDATE_FINISHED: {
|
||||||
|
// 更新完成 自动重启
|
||||||
|
this._am.setEventCallback(null);
|
||||||
|
|
||||||
|
// Prepend the manifest's search path
|
||||||
|
let searchPaths = native.fileUtils.getSearchPaths();
|
||||||
|
log(`${TAG} 当前搜索路径:${JSON.stringify(searchPaths)}`);
|
||||||
|
|
||||||
|
let newPaths = this._am.getLocalManifest().getSearchPaths();
|
||||||
|
log(`${TAG} 新搜索路径:${JSON.stringify(newPaths)}`);
|
||||||
|
|
||||||
|
Array.prototype.unshift.apply(searchPaths, newPaths);
|
||||||
|
sys.localStorage.setItem('hotupdate::version', HotUpdateManager.getInstance().version);
|
||||||
|
sys.localStorage.setItem('hotupdate::searchpaths', JSON.stringify(searchPaths));
|
||||||
|
native.fileUtils.setSearchPaths(searchPaths);
|
||||||
|
|
||||||
|
// 0.5秒后 自动重启游戏
|
||||||
|
setTimeout(() => { game.restart(); }, 500);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case native.EventAssetsManager.UPDATE_FAILED: {
|
||||||
|
// 更新失败了, 等待重试
|
||||||
|
this._complete(HotUpdateCode.UpdateFailed, event.getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case native.EventAssetsManager.ERROR_UPDATING: {
|
||||||
|
// 更新出错了, 一般是开发中的问题, 重启游戏
|
||||||
|
this._complete(HotUpdateCode.UpdateError, event.getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case native.EventAssetsManager.ERROR_DECOMPRESS: {
|
||||||
|
// 解压出错了, 一般是开发中的问题, 重启游戏
|
||||||
|
this._complete(HotUpdateCode.DecompressError, event.getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._am.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 验证资源 */
|
||||||
|
private _verifyCallback(path: string, asset: native.ManifestAsset): boolean {
|
||||||
|
// 资源是否被压缩, 如果压缩我们不需要检查它的md5值
|
||||||
|
let compressed = asset.compressed;
|
||||||
|
if (compressed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 预期的md5
|
||||||
|
let expectedMD5 = asset.md5;
|
||||||
|
// 资源大小
|
||||||
|
let size = asset.size;
|
||||||
|
// 验证资源md5
|
||||||
|
log(`${TAG} 记录的md5:${expectedMD5} 文件大小:${size} 文件相对路径:${asset.path} 绝对路径:${path}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取本地的project.manifest文件 */
|
||||||
|
private readLocalManifest(): Promise<IManifestResult> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this._am) {
|
||||||
|
reject({ code: HotUpdateCode.LoadManifestFailed, message: "读取本地project.manifest文件失败" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let content = native.fileUtils.getStringFromFile(HotUpdateManager.getInstance().manifestUrl);
|
||||||
|
if (content) {
|
||||||
|
resolve({ code: HotUpdateCode.Succeed, message: "读取本地project.manifest文件成功", manifest: JSON.parse(content) });
|
||||||
|
} else {
|
||||||
|
reject({ code: HotUpdateCode.LoadManifestFailed, message: "读取本地project.manifest文件失败" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取远程version.manifest文件内容 */
|
||||||
|
private loadRemoteVersionManifest(): Promise<IManifestResult> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
new ReadNetFile({
|
||||||
|
url: this.versionUrl,
|
||||||
|
timeout: 5,
|
||||||
|
responseType: "text",
|
||||||
|
onComplete: (data: string) => {
|
||||||
|
log(`${TAG} 下载hotconfig文件成功`);
|
||||||
|
if (Utils.isJsonString(data)) {
|
||||||
|
resolve({ code: HotUpdateCode.Succeed, message: "读取远程version.manifest文件成功", manifest: JSON.parse(data) });
|
||||||
|
} else {
|
||||||
|
log(`${TAG} 远程version.manifest文件格式错误`);
|
||||||
|
resolve({ code: HotUpdateCode.ParseVersionFailed, message: "远程version.manifest文件格式错误" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (code: number, message: string) => {
|
||||||
|
log(`${TAG} 读取远程version.manifest文件失败`, code, message);
|
||||||
|
resolve({ code: HotUpdateCode.LoadVersionFailed, message: "读取远程version.manifest文件失败" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 替换project.manifest中的内容 并刷新本地manifest */
|
||||||
|
private refreshLocalManifest(manifest: IHotUpdateConfig, versionManifest: IHotUpdateConfig): Promise<IPromiseResult> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (Utils.compareVersion(manifest.version, versionManifest.version) >= 0) {
|
||||||
|
resolve({ code: HotUpdateCode.LatestVersion, message: "已是最新版本" });
|
||||||
|
} else {
|
||||||
|
// 替换manifest中的内容
|
||||||
|
manifest.remoteManifestUrl = versionManifest.remoteManifestUrl;
|
||||||
|
manifest.remoteVersionUrl = versionManifest.remoteVersionUrl;
|
||||||
|
manifest.packageUrl = versionManifest.packageUrl;
|
||||||
|
|
||||||
|
// 注册本地manifest根目录
|
||||||
|
let manifestRoot = "";
|
||||||
|
let manifestUrl = HotUpdateManager.getInstance().manifestUrl;
|
||||||
|
let found = manifestUrl.lastIndexOf("/");
|
||||||
|
if (found === -1) {
|
||||||
|
found = manifestUrl.lastIndexOf("\\");
|
||||||
|
}
|
||||||
|
if (found !== -1) {
|
||||||
|
manifestRoot = manifestUrl.substring(0, found + 1);
|
||||||
|
}
|
||||||
|
this._am.getLocalManifest().parseJSONString(JSON.stringify(manifest), manifestRoot);
|
||||||
|
log(TAG + "manifest root:" + this._am.getLocalManifest().getManifestRoot());
|
||||||
|
log(TAG + "manifest packageUrl:" + this._am.getLocalManifest().getPackageUrl());
|
||||||
|
log(TAG + "manifest version:" + this._am.getLocalManifest().getVersion());
|
||||||
|
log(TAG + "manifest versionFileUrl:" + this._am.getLocalManifest().getVersionFileUrl());
|
||||||
|
log(TAG + "manifest manifestFileUrl:" + this._am.getLocalManifest().getManifestFileUrl());
|
||||||
|
resolve({ code: HotUpdateCode.Succeed, message: "更新热更新配置成功" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 调用cc的接口检测更新 */
|
||||||
|
private startCheckUpdate(): Promise<ICheckUpdatePromiseResult> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// 设置回调
|
||||||
|
this._am.setEventCallback((event: native.EventAssetsManager) => {
|
||||||
|
let eventCode = event.getEventCode();
|
||||||
|
log(`${TAG} 检查更新回调code:${eventCode}`);
|
||||||
|
switch (eventCode) {
|
||||||
|
case native.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
|
||||||
|
this._am.setEventCallback(null);
|
||||||
|
resolve({ code: HotUpdateCode.LoadManifestFailed, message: "检查更新时下载manifest文件失败", needUpdate: false, size: 0 });
|
||||||
|
return;
|
||||||
|
case native.EventAssetsManager.ERROR_PARSE_MANIFEST:
|
||||||
|
this._am.setEventCallback(null);
|
||||||
|
resolve({ code: HotUpdateCode.ParseManifestFailed, message: "检查更新时解析manifest文件失败", needUpdate: false, size: 0 });
|
||||||
|
return;
|
||||||
|
case native.EventAssetsManager.ALREADY_UP_TO_DATE:
|
||||||
|
this._am.setEventCallback(null);
|
||||||
|
resolve({ code: HotUpdateCode.LatestVersion, message: "已是最新版本", needUpdate: false, size: 0 });
|
||||||
|
return;
|
||||||
|
case native.EventAssetsManager.NEW_VERSION_FOUND:
|
||||||
|
// 发现新版本
|
||||||
|
this._am.setEventCallback(null);
|
||||||
|
resolve({ code: HotUpdateCode.Succeed, message: "发现新版本", needUpdate: true, size: this._am.getTotalBytes() / 1024 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._am.checkUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,11 @@
|
|||||||
* @Description: 热更新管理器
|
* @Description: 热更新管理器
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Asset, game, native, sys } from "cc";
|
import { native } from "cc";
|
||||||
import { Platform } from "../global/Platform";
|
import { ICheckUpdatePromiseResult } from "../interface/PromiseResult";
|
||||||
import { log, warn } from "../tool/log";
|
import { Platform } from "../kunpocc";
|
||||||
import { Utils } from "../tool/Utils";
|
import { log } from "../tool/log";
|
||||||
|
import { HotUpdate, HotUpdateCode } from "./HotUpdate";
|
||||||
|
|
||||||
const TAG = "hotupdate:";
|
const TAG = "hotupdate:";
|
||||||
|
|
||||||
@ -19,37 +20,73 @@ export class HotUpdateManager {
|
|||||||
}
|
}
|
||||||
return HotUpdateManager.instance;
|
return HotUpdateManager.instance;
|
||||||
}
|
}
|
||||||
|
/** 是否初始化了 */
|
||||||
|
private _isInitialized: boolean = false;
|
||||||
|
/** 本地manifest路径 */
|
||||||
|
private _manifestUrl: string = '';
|
||||||
/** 版本号 */
|
/** 版本号 */
|
||||||
private _version: string = '';
|
private _version: string = '';
|
||||||
|
|
||||||
|
/** 资源版本号 */
|
||||||
|
private _resVersion: string = null;
|
||||||
/** 可写路径 */
|
/** 可写路径 */
|
||||||
private _writablePath: string = '';
|
private _writablePath: string = '';
|
||||||
/** 资源管理器 */
|
|
||||||
private _am: native.AssetsManager = null;
|
|
||||||
/** 是否正在更新 或者 正在检查更新 */
|
/** 是否正在更新 或者 正在检查更新 */
|
||||||
private _updating: boolean = false;
|
private _updating: boolean = false;
|
||||||
|
|
||||||
/** 检查更新的回调 */
|
/** 更新实例 只有更新的时候初始化 检查更新不赋值 */
|
||||||
private _checkSucceed: (need: boolean, size: number) => void = null;
|
private _hotUpdate: HotUpdate = null;
|
||||||
private _checkFail: (code: number, message: string) => void = null;
|
|
||||||
|
/**
|
||||||
|
* 热更新文件存放的可写路径
|
||||||
|
*/
|
||||||
|
public get writablePath(): string {
|
||||||
|
return this._writablePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地manifest路径
|
||||||
|
*/
|
||||||
|
public get manifestUrl(): string {
|
||||||
|
return this._manifestUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入的游戏版本号
|
||||||
|
*/
|
||||||
|
public get version(): string {
|
||||||
|
return this._version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取资源版本号, 须初始化成功后再使用
|
||||||
|
* @return 资源版本号 默认值 ‘0’
|
||||||
|
*/
|
||||||
|
public get resVersion(): string {
|
||||||
|
if (this._resVersion === null) {
|
||||||
|
this._resVersion = new HotUpdate().resVersion;
|
||||||
|
}
|
||||||
|
return this._resVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set resVersion(version: string) {
|
||||||
|
if (this._resVersion === null) {
|
||||||
|
this._resVersion = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 更新回调 */
|
|
||||||
private _updateProgress: (kb: number, total: number) => void = null;
|
|
||||||
private _updateFail: (code: number, message: string) => void = null;
|
|
||||||
private _updateError: (code: number, message: string) => void = null;
|
|
||||||
/**
|
/**
|
||||||
* 1. 初始化热更新管理器
|
* 1. 初始化热更新管理器
|
||||||
* @param manifest 传入manifest文件
|
* @param manifestUrl 传入本地manifest文件地址 资源的assets.nativeUrl
|
||||||
* @param version 传入游戏版本号 eg: 1.0.0
|
* @param version 游戏版本号 eg: 1.0.0
|
||||||
*/
|
*/
|
||||||
public init(manifest: Asset, version: string): void {
|
public init(manifestUrl: string, version: string): void {
|
||||||
if (!Platform.isNativeMobile) {
|
if (this._isInitialized) {
|
||||||
return;
|
log(`${TAG} 热更新管理器不需要重复初始化`);
|
||||||
}
|
|
||||||
if (this._am) {
|
|
||||||
warn(`${TAG}请勿重复初始化`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._isInitialized = true;
|
||||||
|
this._manifestUrl = manifestUrl;
|
||||||
this._version = version;
|
this._version = version;
|
||||||
|
|
||||||
let writablePath = native?.fileUtils?.getWritablePath() || "";
|
let writablePath = native?.fileUtils?.getWritablePath() || "";
|
||||||
@ -58,184 +95,74 @@ export class HotUpdateManager {
|
|||||||
}
|
}
|
||||||
this._writablePath = `${writablePath}hot-update/${version}/`;
|
this._writablePath = `${writablePath}hot-update/${version}/`;
|
||||||
log(`${TAG}可写路径:${this._writablePath}`);
|
log(`${TAG}可写路径:${this._writablePath}`);
|
||||||
|
|
||||||
// 创建 am 对象
|
|
||||||
this._am = native.AssetsManager.create("", this._writablePath);
|
|
||||||
this._am.setVersionCompareHandle(Utils.compareVersion);
|
|
||||||
this._am.setVerifyCallback(this._verifyCallback);
|
|
||||||
// 加载本地的 manifest
|
|
||||||
log(`${TAG} 加载本地的 manifest:${manifest.nativeUrl}`);
|
|
||||||
this._am.loadLocalManifest(manifest.nativeUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2. 检查是否有新的热更版本
|
* 检查是否存在热更新
|
||||||
* @param res.succeed.need 是否需要更新
|
* 提供一个对外的方法检查是否存在热更新
|
||||||
* @param res.succeed.size 需要更新的资源大小 (KB)
|
* @return {Promise<ICheckUpdatePromiseResult>}
|
||||||
*
|
|
||||||
* @param res.fail 检查失败的回调
|
|
||||||
* @param res.fail.code
|
|
||||||
* -1000: 未初始化
|
|
||||||
* -1001: 正在更新或者正在检查更新
|
|
||||||
* -1002: 本地manifest文件错误
|
|
||||||
* -1004: 解析远程manifest文件失败
|
|
||||||
*/
|
*/
|
||||||
public checkUpdate(res: { succeed: (need: boolean, size: number) => void, fail: (code: number, message: string) => void }): void {
|
public checkUpdate(): Promise<ICheckUpdatePromiseResult> {
|
||||||
this._checkSucceed = res.succeed;
|
return new Promise((resolve, reject) => {
|
||||||
this._checkFail = res.fail;
|
|
||||||
if (this._updating) {
|
|
||||||
res.fail(-1001, "正在更新或者正在检查更新");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Platform.isNativeMobile) {
|
if (!Platform.isNativeMobile) {
|
||||||
res.succeed(false, 0);
|
resolve({ code: HotUpdateCode.PlatformNotSupported, message: "当前平台不需要热更新" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this._am) {
|
if (!this._isInitialized) {
|
||||||
res.fail(-1000, "未初始化, 需要先调用init方法");
|
resolve({ code: HotUpdateCode.NotInitialized, message: "未初始化, 需要先调用init方法" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._updating) {
|
||||||
|
resolve({ code: HotUpdateCode.Updating, message: "正在更新或者正在检查更新中" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._updating = true;
|
this._updating = true;
|
||||||
// 设置回调
|
new HotUpdate().checkUpdate().then((res) => {
|
||||||
this._am.setEventCallback(this._checkCb.bind(this));
|
this._updating = false;
|
||||||
// 检查更新
|
resolve(res);
|
||||||
this._am.checkUpdate();
|
}).catch((err) => {
|
||||||
|
this._updating = false;
|
||||||
|
resolve({ code: HotUpdateCode.Error, message: JSON.stringify(err) });
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3. 开始热更新
|
* 开始热更新
|
||||||
|
* @param res.skipCheck 是否跳过检查更新
|
||||||
* @param res.progress 更新进度回调 kb: 已下载的资源大小, total: 总资源大小 (kb)
|
* @param res.progress 更新进度回调 kb: 已下载的资源大小, total: 总资源大小 (kb)
|
||||||
* @param res.fail 更新失败 可以重试
|
* @param res.complete 更新结束回调 根据错误码判断 跳过还是重试失败资源
|
||||||
* @param res.fail.code 更新失败错误码
|
|
||||||
* -10001: 更新失败 需要重试
|
|
||||||
* @param res.error 更新错误 无法重试
|
|
||||||
* @param res.error.code 更新错误错误码
|
|
||||||
* -1000: 未初始化
|
|
||||||
* -1001: 正在更新或者正在检查更新
|
|
||||||
* -10002: 资源更新错误
|
|
||||||
* -10003: 解压错误
|
|
||||||
*/
|
*/
|
||||||
public startUpdate(res: {
|
public startUpdate(res: { skipCheck: boolean, progress: (kb: number, total: number) => void, complete: (code: HotUpdateCode, message: string) => void }): void {
|
||||||
progress: (kb: number, total: number) => void,
|
if (!Platform.isNativeMobile) {
|
||||||
fail: (code: number, message: string) => void,
|
res.complete(HotUpdateCode.PlatformNotSupported, "当前平台不需要热更新");
|
||||||
error: (code: number, message: string) => void
|
|
||||||
}): void {
|
|
||||||
this._updateProgress = res.progress;
|
|
||||||
this._updateFail = res.fail;
|
|
||||||
this._updateError = res.error;
|
|
||||||
|
|
||||||
log(`${TAG} 开始热更新`);
|
|
||||||
if (this._updating) {
|
|
||||||
res.error(-1001, "正在更新或者正在检查更新");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this._am) {
|
if (!this._isInitialized) {
|
||||||
res.error(-1000, "未初始化, 需要先调用init方法");
|
res.complete(HotUpdateCode.NotInitialized, "未初始化, 需要先调用init方法");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._updating) {
|
||||||
|
res.complete(HotUpdateCode.Updating, "正在更新或者正在检查更新");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._updating = true;
|
this._updating = true;
|
||||||
this._am.setEventCallback(this._updateCb.bind(this));
|
this._hotUpdate = new HotUpdate();
|
||||||
this._am.update();
|
this._hotUpdate.startUpdate({
|
||||||
|
skipCheck: res.skipCheck,
|
||||||
|
progress: res.progress,
|
||||||
|
complete: (code: HotUpdateCode, message: string) => {
|
||||||
|
this._updating = false;
|
||||||
|
res.complete(code, message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重试失败的资源 */
|
/** 重试失败的资源 */
|
||||||
public retryUpdate(): void {
|
public retryUpdate(): void {
|
||||||
this._am.downloadFailedAssets();
|
if (!this._hotUpdate) {
|
||||||
|
throw new Error(`${TAG} 使用前 必须使用过startUpdate方法`);
|
||||||
}
|
}
|
||||||
|
this._hotUpdate.retryUpdate();
|
||||||
/** 检查更新的回调 */
|
|
||||||
private _checkCb(event: native.EventAssetsManager) {
|
|
||||||
let eventCode = event.getEventCode();
|
|
||||||
log(`${TAG} 检查更新回调code:${eventCode}`);
|
|
||||||
this._updating = false;
|
|
||||||
switch (eventCode) {
|
|
||||||
case native.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
|
|
||||||
this._checkFail(-1002, "本地没有manifest文件");
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
|
|
||||||
// this._checkFail(-1003, "下载manifest文件失败");
|
|
||||||
this._checkSucceed(false, 0);
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.ERROR_PARSE_MANIFEST:
|
|
||||||
this._checkFail(-1004, "解析远程manifest文件失败");
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.ALREADY_UP_TO_DATE:
|
|
||||||
this._checkSucceed(false, 0);
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.NEW_VERSION_FOUND:
|
|
||||||
// 发现新版本
|
|
||||||
this._checkSucceed(true, this._am.getTotalBytes() / 1024);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._am.setEventCallback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 更新的回调 */
|
|
||||||
private _updateCb(event: native.EventAssetsManager) {
|
|
||||||
let eventCode = event.getEventCode();
|
|
||||||
log(`${TAG} 更新回调code:${eventCode}`);
|
|
||||||
let needRestart = false;
|
|
||||||
switch (eventCode) {
|
|
||||||
case native.EventAssetsManager.UPDATE_PROGRESSION:
|
|
||||||
let bytes = event.getDownloadedBytes() / 1024;
|
|
||||||
let total = event.getTotalBytes() / 1024;
|
|
||||||
this._updateProgress(bytes, total);
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.UPDATE_FINISHED:
|
|
||||||
// 更新完成 自动重启
|
|
||||||
needRestart = true;
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.UPDATE_FAILED:
|
|
||||||
this._updating = false;
|
|
||||||
this._updateFail(-10001, event.getMessage());
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.ERROR_UPDATING:
|
|
||||||
this._updating = false;
|
|
||||||
this._updateError(-10002, event.getMessage());
|
|
||||||
break;
|
|
||||||
case native.EventAssetsManager.ERROR_DECOMPRESS:
|
|
||||||
this._updating = false;
|
|
||||||
this._updateError(-10003, event.getMessage());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (needRestart) {
|
|
||||||
this._am.setEventCallback(null);
|
|
||||||
|
|
||||||
// Prepend the manifest's search path
|
|
||||||
let searchPaths = native.fileUtils.getSearchPaths();
|
|
||||||
log(`${TAG} 当前搜索路径:${JSON.stringify(searchPaths)}`);
|
|
||||||
|
|
||||||
let newPaths = this._am.getLocalManifest().getSearchPaths();
|
|
||||||
log(`${TAG} 新搜索路径:${JSON.stringify(newPaths)}`);
|
|
||||||
|
|
||||||
Array.prototype.unshift.apply(searchPaths, newPaths);
|
|
||||||
sys.localStorage.setItem('hotupdate::version', this._version);
|
|
||||||
sys.localStorage.setItem('hotupdate::searchpaths', JSON.stringify(searchPaths));
|
|
||||||
native.fileUtils.setSearchPaths(searchPaths);
|
|
||||||
|
|
||||||
// 重启游戏
|
|
||||||
setTimeout(() => {
|
|
||||||
game.restart()
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _verifyCallback(path: string, asset: native.ManifestAsset): boolean {
|
|
||||||
// 资源是否被压缩, 如果压缩我们不需要检查它的md5值
|
|
||||||
let compressed = asset.compressed;
|
|
||||||
if (compressed) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// 预期的md5
|
|
||||||
let expectedMD5 = asset.md5;
|
|
||||||
// 资源大小
|
|
||||||
let size = asset.size;
|
|
||||||
// 验证资源md5
|
|
||||||
log(`${TAG} 记录的md5:${expectedMD5} 文件大小:${size} 文件相对路径:${asset.path} 绝对路径:${path}`);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
src/interface/PromiseResult.ts
Normal file
19
src/interface/PromiseResult.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @Author: Gongxh
|
||||||
|
* @Date: 2025-04-18
|
||||||
|
* @Description: 通用的 Promise 结果
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface IPromiseResult {
|
||||||
|
/** 0:成功 其他:失败 */
|
||||||
|
code: number;
|
||||||
|
/** 失败信息 */
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICheckUpdatePromiseResult extends IPromiseResult {
|
||||||
|
/** 是否需要更新 */
|
||||||
|
needUpdate?: boolean;
|
||||||
|
/** 需要更新的资源大小 (KB) */
|
||||||
|
size?: number;
|
||||||
|
}
|
@ -4,6 +4,7 @@ export { GlobalTimer } from "./global/GlobalTimer";
|
|||||||
export { enableDebugMode, FrameConfig, KUNPO_DEBUG } from "./global/header";
|
export { enableDebugMode, FrameConfig, KUNPO_DEBUG } from "./global/header";
|
||||||
export { Platform, PlatformType } from "./global/Platform";
|
export { Platform, PlatformType } from "./global/Platform";
|
||||||
export { Screen } from "./global/Screen";
|
export { Screen } from "./global/Screen";
|
||||||
|
export * from "./interface/PromiseResult";
|
||||||
|
|
||||||
/** tool */
|
/** tool */
|
||||||
export { Binary } from "./tool/Binary";
|
export { Binary } from "./tool/Binary";
|
||||||
@ -24,6 +25,9 @@ export { IHttpResponse } from "./net/http/IHttpResponse";
|
|||||||
/** Socket */
|
/** Socket */
|
||||||
export { Socket } from "./net/socket/Socket";
|
export { Socket } from "./net/socket/Socket";
|
||||||
|
|
||||||
|
/** 读取网络文件 */
|
||||||
|
export { ReadNetFile } from "./net/nettools/ReadNetFile";
|
||||||
|
|
||||||
/** 四叉树 */
|
/** 四叉树 */
|
||||||
export { Box } from "./quadtree/Box";
|
export { Box } from "./quadtree/Box";
|
||||||
export { Circle } from "./quadtree/Circle";
|
export { Circle } from "./quadtree/Circle";
|
||||||
@ -78,6 +82,7 @@ export { ConditionAnyNode } from "./condition/node/ConditionAnyNode";
|
|||||||
export { ConditionBase } from "./condition/node/ConditionBase";
|
export { ConditionBase } from "./condition/node/ConditionBase";
|
||||||
|
|
||||||
/** 热更新 */
|
/** 热更新 */
|
||||||
|
export { HotUpdateCode } from "./hotupdate/HotUpdate";
|
||||||
export { HotUpdateManager } from "./hotupdate/HotUpdateManager";
|
export { HotUpdateManager } from "./hotupdate/HotUpdateManager";
|
||||||
|
|
||||||
/** 小游戏 */
|
/** 小游戏 */
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
* @Description: 字节跳动小游戏工具类
|
* @Description: 字节跳动小游戏工具类
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { LaunchParams } from "@douyin-microapp/typings/types/app";
|
|
||||||
import { warn } from "../../tool/log";
|
import { warn } from "../../tool/log";
|
||||||
import { IMiniCommon } from "../interface/IMiniCommon";
|
import { IMiniCommon } from "../interface/IMiniCommon";
|
||||||
|
|
||||||
@ -24,14 +23,14 @@ export class BytedanceCommon implements IMiniCommon {
|
|||||||
/**
|
/**
|
||||||
* 获取冷启动参数
|
* 获取冷启动参数
|
||||||
*/
|
*/
|
||||||
public getLaunchOptions(): LaunchParams {
|
public getLaunchOptions(): BytedanceMiniprogram.LaunchParams {
|
||||||
return this._launchOptions;
|
return this._launchOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取热启动参数
|
* 获取热启动参数
|
||||||
*/
|
*/
|
||||||
public getHotLaunchOptions(): LaunchParams {
|
public getHotLaunchOptions(): BytedanceMiniprogram.LaunchParams {
|
||||||
warn("字节跳动小游戏未提供热启动参数获取方式,请在 onShow 中获取");
|
warn("字节跳动小游戏未提供热启动参数获取方式,请在 onShow 中获取");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { IHttpResponse } from "./IHttpResponse";
|
|||||||
|
|
||||||
export interface IHttpEvent {
|
export interface IHttpEvent {
|
||||||
/** 名称 */
|
/** 名称 */
|
||||||
name: string;
|
name?: string;
|
||||||
/** 自定义参数 */
|
/** 自定义参数 */
|
||||||
data?: any;
|
data?: any;
|
||||||
/** 网络请求成功 */
|
/** 网络请求成功 */
|
||||||
|
29
src/net/nettools/ReadNetFile.ts
Normal file
29
src/net/nettools/ReadNetFile.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* @Author: Gongxh
|
||||||
|
* @Date: 2025-04-18
|
||||||
|
* @Description: 读取网络文件内容
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Time } from "../../tool/Time";
|
||||||
|
import { HttpManager } from "../http/HttpManager";
|
||||||
|
import { IHttpResponse } from "../http/IHttpResponse";
|
||||||
|
|
||||||
|
export class ReadNetFile {
|
||||||
|
constructor(res: { url: string, timeout: number, responseType: "text" | "json" | "arraybuffer", onComplete: (data: any) => void, onError: (code: number, message: string) => void }) {
|
||||||
|
// 地址上带时间戳参数 确保每次请求都到服务器上请求最新配置,而不是拿到上次请求的缓存数据
|
||||||
|
let url = res.url;
|
||||||
|
if (url.indexOf("?") > -1) {
|
||||||
|
url += `&timeStamp=${Time.now()}`;
|
||||||
|
} else {
|
||||||
|
url += `?timeStamp=${Time.now()}`;
|
||||||
|
}
|
||||||
|
HttpManager.get(url, null, res.responseType, {
|
||||||
|
onComplete: (response: IHttpResponse) => {
|
||||||
|
res.onComplete(response.data);
|
||||||
|
},
|
||||||
|
onError: (response: IHttpResponse) => {
|
||||||
|
res.onError(response.statusCode, response.message);
|
||||||
|
}
|
||||||
|
}, null, res.timeout || 6);
|
||||||
|
}
|
||||||
|
}
|
@ -36,4 +36,15 @@ export class Utils {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断传入的字符串是否是json格式的字符串
|
||||||
|
*/
|
||||||
|
public static isJsonString(str: string): boolean {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user