81 KiB
81 KiB
TSGame 应用启动流程详解
目录
概述
TSGame HarmonyOS应用采用分阶段启动流程,通过远程配置文件驱动,支持大厅和子游戏的动态资源管理。应用启动过程包括配置文件获取、资源下载解压、WebView初始化和页面加载等关键步骤。
核心特性
- 远程配置驱动:通过七牛云CDN获取分层配置
- 双WebView架构:大厅和子游戏独立管理
- 动态资源更新:支持ZIP包热更新
- 版本检查机制:自动检测和更新资源版本
- 降级兼容:配置获取失败时使用本地缓存
启动流程时序
完整启动时序图
用户启动App
↓
EntryAbility.onCreate()
↓
初始化基础管理器
↓
ConfigManager.loadStartupConfig() ←→ 加载本地启动配置
↓
检测远程配置文件 ←→ 七牛云CDN
↓ ↓
配置文件存在? 下载配置文件
↓ (是) ↓
解析远程配置 校验文件格式
↓ ↓
获取大厅ZIP包URL 保存到本地缓存
↓
下载大厅ZIP包 ←→ CDN服务器
↓ ↓
解压大厅资源 验证ZIP包完整性
↓
初始化大厅WebView
↓
加载大厅HTML页面
↓
大厅显示完成
↓
用户点击子游戏
↓
获取子游戏ZIP包URL
↓
验证子游戏ZIP包有效性
↓
下载子游戏ZIP包 ←→ CDN服务器
↓ ↓
解压子游戏资源 验证ZIP包完整性
↓
初始化子游戏WebView
↓
加载子游戏HTML页面
↓
子游戏启动完成
启动阶段详解
阶段1:应用初始化
class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
console.log('TSGame应用启动');
// 1. 初始化基础服务
this.initBaseServices();
// 2. 加载启动配置
this.loadStartupConfig();
// 3. 启动配置检查流程
this.startConfigCheckFlow();
}
private async initBaseServices(): Promise<void> {
// 初始化文件管理器
await FileManager.initialize();
// 初始化网络管理器
await NetworkManager.initialize();
// 初始化配置管理器
await ConfigManager.initialize();
// 初始化资源管理器
await ResourceManager.initialize();
}
}
阶段2:配置文件检查
class ConfigManager {
/**
* 启动配置检查流程
*/
public async startConfigCheckFlow(): Promise<void> {
try {
// 1. 加载本地启动配置
const localConfig = await this.loadLocalStartupConfig();
// 2. 获取远程配置文件
const remoteConfig = await this.fetchRemoteConfig();
// 3. 比较并更新配置
const needUpdate = this.compareConfigs(localConfig, remoteConfig);
if (needUpdate) {
// 4. 更新本地配置缓存
await this.updateLocalConfig(remoteConfig);
}
// 5. 启动大厅初始化流程
await this.startHallInitialization(remoteConfig);
} catch (error) {
console.error('配置检查失败:', error);
// 降级到本地配置
await this.fallbackToLocalConfig();
}
}
/**
* 获取远程配置文件
*/
private async fetchRemoteConfig(): Promise<RemoteConfig> {
const gameData = await this.getCurrentGameData();
const configUrl = this.buildConfigUrl(gameData);
console.log('请求配置文件URL:', configUrl);
const response = await fetch(configUrl + '?t=' + Date.now(), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
},
timeout: 10000
});
if (!response.ok) {
throw new Error(`配置文件获取失败: ${response.status}`);
}
const configData = await response.json();
console.log('远程配置获取成功:', configData);
return configData;
}
}
远程配置文件管理
3.1 配置文件结构
配置文件完整JSON结构
{
"configVersion": "1.0.0",
"timestamp": 1688544000000,
"data": {
"app_download": "https://cdn.jxjygame.com/app/tsgame_v3.6.3.apk",
"app_version": "3.6.3",
"app_size": "25.6MB",
"game_download": "https://cdn.jxjygame.com/games/default_game.zip",
"game_version": "1.0.0",
"game_size": "12.3MB",
"url": "https://api.jxjygame.com",
"showmessage": "欢迎使用进贤聚友棋牌",
"agentlist": [
{
"agentid": "agent001",
"agentname": "代理商A",
"url": "https://api-agent001.jxjygame.com",
"app_download": "https://cdn.jxjygame.com/app/agent001_v3.6.3.apk",
"app_version": "3.6.3",
"app_size": "25.8MB",
"game_download": "https://cdn.jxjygame.com/games/agent001_games.zip",
"game_version": "2.1.0",
"game_size": "15.2MB",
"showmessage": "代理商A定制版本",
"channellist": [
{
"channelid": "channel001",
"channelname": "华为渠道",
"app_download": "https://cdn.jxjygame.com/app/huawei_v3.6.3.apk",
"app_version": "3.6.3",
"app_size": "25.9MB",
"game_download": "https://cdn.jxjygame.com/games/huawei_games.zip",
"game_version": "2.1.1",
"game_size": "15.5MB",
"showmessage": "华为渠道专版",
"marketlist": [
{
"marketid": "huawei_appgallery",
"marketname": "华为应用市场",
"app_download": "https://cdn.jxjygame.com/app/huawei_market_v3.6.4.apk",
"app_version": "3.6.4",
"app_size": "26.1MB",
"game_download": "https://cdn.jxjygame.com/games/huawei_market_games.zip",
"game_version": "2.1.2",
"game_size": "15.8MB",
"showmessage": "华为应用市场版本"
}
]
}
]
}
]
}
}
配置层级说明
配置文件采用四级层次结构,支持参数覆盖:
- 全局配置 (data根级别) - 最低优先级
- 代理商配置 (agentlist) - 覆盖全局配置
- 渠道配置 (channellist) - 覆盖代理商配置
- 市场配置 (marketlist) - 最高优先级,覆盖所有上级配置
3.2 配置参数获取逻辑
核心参数获取类
/**
* 配置参数获取工具类
* 严格按照Android版本的逻辑实现,支持分层参数覆盖
*/
export class ConfigParameterHelper {
/**
* 根据层级结构获取配置参数值
* @param config 配置对象
* @param paramName 参数名称
* @param gameData 游戏数据上下文
* @returns 参数值
*/
public static getParameterValue(config: any, paramName: string, gameData: GameData): any {
const agentId = gameData.agentId;
const channelId = gameData.channelId;
const marketId = gameData.marketId;
let paramValue = null;
// 内部函数:遍历数组匹配key获取参数值
const getParameterFromList = (arrayList: any[], keyName: string, keyValue: string): any => {
if (arrayList && keyName && keyValue) {
for (let i = 0; i < arrayList.length; i++) {
if (arrayList[i][keyName] === keyValue) {
// 如果找到匹配项且包含目标参数,更新参数值
if (arrayList[i][paramName] !== undefined) {
paramValue = arrayList[i][paramName];
}
return arrayList[i];
}
}
}
return null;
};
// 检查配置数据是否存在
if (!config || !config.data) {
return paramValue;
}
// 1. 首先检查全局配置(最低优先级)
if (config.data[paramName] !== undefined) {
paramValue = config.data[paramName];
}
// 2. 按层级查找,后面的层级覆盖前面的:代理商 -> 渠道 -> 市场
// 代理商级别配置
const agentConfig = getParameterFromList(config.data.agentlist, "agentid", agentId);
if (!agentConfig) {
return paramValue; // 如果找不到代理商配置,返回全局配置值
}
// 渠道级别配置
const channelConfig = getParameterFromList(agentConfig.channellist, "channelid", channelId);
if (!channelConfig) {
return paramValue; // 如果找不到渠道配置,返回当前参数值
}
// 市场级别配置(最高优先级)
const marketConfig = getParameterFromList(channelConfig.marketlist, "marketid", marketId);
// 不管是否找到市场配置,都返回当前参数值(可能已被前面层级更新)
return paramValue;
}
/**
* 获取应用下载地址
* 对应配置文件中的 app_download 属性
*/
public static getAppDownloadUrl(config: any, gameData: GameData): string {
return this.getParameterValue(config, "app_download", gameData) || "";
}
/**
* 获取应用版本号
* 对应配置文件中的 app_version 属性
*/
public static getAppVersion(config: any, gameData: GameData): string {
return this.getParameterValue(config, "app_version", gameData) || "";
}
/**
* 获取游戏ZIP包下载地址
* 对应配置文件中的 game_download 属性
*/
public static getGameDownloadUrl(config: any, gameData: GameData): string {
return this.getParameterValue(config, "game_download", gameData) || "";
}
/**
* 获取游戏版本号
* 对应配置文件中的 game_version 属性
*/
public static getGameVersion(config: any, gameData: GameData): string {
return this.getParameterValue(config, "game_version", gameData) || "";
}
/**
* 获取服务器地址
* 对应配置文件中的 url 属性
*/
public static getServerUrl(config: any, gameData: GameData): string {
return this.getParameterValue(config, "url", gameData) || "";
}
/**
* 获取显示消息
* 对应配置文件中的 showmessage 属性
*/
public static getShowMessage(config: any, gameData: GameData): string {
return this.getParameterValue(config, "showmessage", gameData) || "";
}
}
/**
* 游戏数据上下文
*/
export interface GameData {
agentId: string; // 代理商ID,从本地配置或启动参数获取
channelId: string; // 渠道ID,从本地配置或启动参数获取
marketId: string; // 市场ID,从本地配置或启动参数获取
}
配置参数获取示例
// 配置参数获取的实际使用示例
export class ConfigUsageExample {
public static async demonstrateConfigUsage(): Promise<void> {
// 1. 获取当前游戏数据上下文
const gameData: GameData = {
agentId: "agent001", // 从本地存储或启动参数获取
channelId: "channel001", // 从本地存储或启动参数获取
marketId: "huawei_appgallery" // 从本地存储或启动参数获取
};
// 2. 加载远程配置
const config = await ConfigManager.fetchRemoteConfig();
// 3. 获取各种配置参数
const appDownloadUrl = ConfigParameterHelper.getAppDownloadUrl(config, gameData);
// 实际获取到: "https://cdn.jxjygame.com/app/huawei_market_v3.6.4.apk"
const appVersion = ConfigParameterHelper.getAppVersion(config, gameData);
// 实际获取到: "3.6.4" (市场级配置覆盖)
const gameDownloadUrl = ConfigParameterHelper.getGameDownloadUrl(config, gameData);
// 实际获取到: "https://cdn.jxjygame.com/games/huawei_market_games.zip"
const gameVersion = ConfigParameterHelper.getGameVersion(config, gameData);
// 实际获取到: "2.1.2" (市场级配置覆盖)
const serverUrl = ConfigParameterHelper.getServerUrl(config, gameData);
// 实际获取到: "https://api-agent001.jxjygame.com" (代理商级配置)
const showMessage = ConfigParameterHelper.getShowMessage(config, gameData);
// 实际获取到: "华为应用市场版本" (市场级配置覆盖)
console.log('配置参数获取结果:');
console.log(`App Download URL: ${appDownloadUrl}`);
console.log(`App Version: ${appVersion}`);
console.log(`Game Download URL: ${gameDownloadUrl}`);
console.log(`Game Version: ${gameVersion}`);
console.log(`Server URL: ${serverUrl}`);
console.log(`Show Message: ${showMessage}`);
}
}
配置获取的优先级规则
- 全局配置 (
data根级别):作为默认值,优先级最低 - 代理商配置 (
agentlist[].):覆盖全局配置中的同名属性 - 渠道配置 (
channellist[].):覆盖代理商和全局配置中的同名属性 - 市场配置 (
marketlist[].):最高优先级,覆盖所有上级配置中的同名属性
3.4 应用升级验证详解
升级验证的具体配置属性
TSGame应用的升级验证基于以下配置属性的组合判断:
interface AppUpgradeValidation {
// 主要验证属性
app_version: string; // 远程应用版本号 - 核心验证属性
app_download: string; // 应用下载URL - 升级时获取新版本
app_size: string; // 应用包大小 - 用于下载进度显示
showmessage: string; // 版本更新说明 - 用户升级提示
// 辅助验证属性
force_update?: boolean; // 强制更新标志 - 是否必须升级
min_version?: string; // 最低支持版本 - 低于此版本强制升级
update_priority?: number; // 更新优先级 - 控制更新推送频率
}
具体的升级验证逻辑
export class AppUpgradeValidator {
/**
* 验证应用是否需要升级
* 主要读取配置属性:app_version、force_update、min_version
*/
public static async validateAppUpgrade(config: any, gameData: any): Promise<UpgradeInfo> {
// 1. 获取远程版本号 - 读取 app_version 属性
const remoteVersion = ConfigParameterHelper.getAppVersion(config, gameData);
console.log(`远程应用版本: ${remoteVersion}`);
// 2. 获取本地应用版本
const localVersion = await DeviceInfo.getAppVersion();
console.log(`本地应用版本: ${localVersion}`);
// 3. 版本比较 - 核心验证逻辑
const needUpdate = VersionComparator.isVersionNewer(remoteVersion, localVersion);
// 4. 检查是否强制更新 - 读取 force_update 属性
const forceUpdate = ConfigParameterHelper.getBooleanValue(config, gameData, 'force_update', false);
// 5. 检查最低版本要求 - 读取 min_version 属性
const minVersion = ConfigParameterHelper.getString(config, gameData, 'min_version', '1.0.0');
const belowMinVersion = VersionComparator.isVersionOlder(localVersion, minVersion);
return {
needUpdate: needUpdate || belowMinVersion,
forceUpdate: forceUpdate || belowMinVersion,
remoteVersion,
localVersion,
upgradeType: belowMinVersion ? 'critical' : (forceUpdate ? 'force' : 'optional')
};
}
/**
* 获取应用下载信息
* 主要读取配置属性:app_download、app_size、showmessage
*/
public static getAppDownloadInfo(config: any, gameData: any): AppDownloadInfo {
return {
// 读取 app_download 属性 - 应用下载地址
downloadUrl: ConfigParameterHelper.getAppDownloadUrl(config, gameData),
// 读取 app_size 属性 - 应用包大小
packageSize: ConfigParameterHelper.getAppSize(config, gameData),
// 读取 showmessage 属性 - 更新说明
upgradeMessage: ConfigParameterHelper.getShowMessage(config, gameData),
// 其他下载相关属性
md5Hash: ConfigParameterHelper.getString(config, gameData, 'app_md5', ''),
timeout: ConfigParameterHelper.getNumber(config, gameData, 'download_timeout', 300000)
};
}
}
3.5 下载地址获取详解
应用下载地址的具体获取
export class DownloadUrlResolver {
/**
* 获取应用下载地址
* 配置属性:app_download
*/
public static getAppDownloadUrl(config: any, gameData: any): string {
// 按优先级读取 app_download 属性
const downloadUrl = ConfigParameterHelper.getAppDownloadUrl(config, gameData);
console.log(`应用下载地址配置路径解析:`);
console.log(`1. 尝试读取市场级别: marketlist[${gameData.marketid}].app_download`);
console.log(`2. 尝试读取渠道级别: channellist[${gameData.channelid}].app_download`);
console.log(`3. 尝试读取代理商级别: agentlist[${gameData.agentid}].app_download`);
console.log(`4. 尝试读取全局级别: data.app_download`);
console.log(`最终获取到的下载地址: ${downloadUrl}`);
return downloadUrl;
}
/**
* 获取大厅ZIP包下载地址
* 配置属性:game_download(大厅使用相同的配置)
*/
public static getHallZipDownloadUrl(config: any, gameData: any): string {
// 大厅ZIP包使用 game_download 属性
const hallZipUrl = ConfigParameterHelper.getGameDownloadUrl(config, gameData);
console.log(`大厅ZIP包下载地址: ${hallZipUrl}`);
return hallZipUrl;
}
/**
* 获取子游戏ZIP包下载地址
* 配置属性:game_download + 游戏ID拼接
*/
public static getGameZipDownloadUrl(config: any, gameData: any, gameId: string): string {
// 基础下载地址
const baseDownloadUrl = ConfigParameterHelper.getGameDownloadUrl(config, gameData);
// 拼接游戏ID构成完整下载地址
const gameZipUrl = `${baseDownloadUrl}/${gameId}.zip`;
console.log(`子游戏${gameId}的ZIP包下载地址: ${gameZipUrl}`);
return gameZipUrl;
}
}
3.6 解压路径和启动路径详解
大厅ZIP解压路径详解
export class HallPathResolver {
/**
* 大厅ZIP包解压的完整路径规范
*/
public static getHallPaths(): HallPathInfo {
const appRootPath = getContext().filesDir;
return {
// 1. 大厅ZIP包下载临时路径
downloadTempPath: `${appRootPath}/downloads/hall_temp.zip`,
// 2. 大厅资源解压目标路径
extractTargetPath: `${appRootPath}/game_resources/hall`,
// 3. 大厅启动入口文件路径
entryFilePath: `${appRootPath}/game_resources/hall/index.html`,
// 4. 大厅资源验证路径
versionFilePath: `${appRootPath}/game_resources/hall/version.txt`,
configFilePath: `${appRootPath}/game_resources/hall/config.json`,
// 5. 大厅WebView加载URL
webViewLoadUrl: `file://${appRootPath}/game_resources/hall/index.html`
};
}
/**
* 大厅解压流程详解
*/
public static async extractHallZip(zipFilePath: string): Promise<void> {
const paths = this.getHallPaths();
console.log(`=== 大厅ZIP包解压流程 ===`);
console.log(`源文件: ${zipFilePath}`);
console.log(`目标路径: ${paths.extractTargetPath}`);
// 1. 清理旧的大厅资源
if (await fileIo.access(paths.extractTargetPath)) {
await fileIo.rmdir(paths.extractTargetPath, true);
console.log(`已清理旧大厅资源: ${paths.extractTargetPath}`);
}
// 2. 创建解压目标目录
await fileIo.mkdir(paths.extractTargetPath, true);
// 3. 执行ZIP解压
await ZipExtractor.extractToDirectory(zipFilePath, paths.extractTargetPath);
// 4. 验证关键文件
await this.validateHallFiles(paths);
console.log(`大厅ZIP包解压完成,启动路径: ${paths.webViewLoadUrl}`);
}
/**
* 验证大厅文件完整性
*/
private static async validateHallFiles(paths: HallPathInfo): Promise<void> {
const requiredFiles = [
paths.entryFilePath, // index.html - 必须存在
`${paths.extractTargetPath}/js/main.js`, // 主逻辑文件
`${paths.extractTargetPath}/css/main.css` // 主样式文件
];
for (const filePath of requiredFiles) {
if (!await fileIo.access(filePath)) {
throw new Error(`大厅关键文件缺失: ${filePath}`);
}
}
console.log(`大厅文件验证通过`);
}
}
子游戏ZIP解压路径详解
export class GamePathResolver {
/**
* 子游戏ZIP包解压的完整路径规范
*/
public static getGamePaths(gameId: string): GamePathInfo {
const appRootPath = getContext().filesDir;
return {
// 1. 子游戏ZIP包下载临时路径
downloadTempPath: `${appRootPath}/downloads/${gameId}_temp.zip`,
// 2. 子游戏资源解压目标路径
extractTargetPath: `${appRootPath}/game_resources/games/${gameId}`,
// 3. 子游戏启动入口文件路径
entryFilePath: `${appRootPath}/game_resources/games/${gameId}/index.html`,
// 4. 子游戏资源验证路径
versionFilePath: `${appRootPath}/game_resources/games/${gameId}/version.txt`,
configFilePath: `${appRootPath}/game_resources/games/${gameId}/game.json`,
// 5. 子游戏WebView加载URL
webViewLoadUrl: `file://${appRootPath}/game_resources/games/${gameId}/index.html`,
// 6. 子游戏资源子目录
jsDir: `${appRootPath}/game_resources/games/${gameId}/js`,
cssDir: `${appRootPath}/game_resources/games/${gameId}/css`,
imagesDir: `${appRootPath}/game_resources/games/${gameId}/images`,
audioDir: `${appRootPath}/game_resources/games/${gameId}/audio`
};
}
/**
* 子游戏解压流程详解
*/
public static async extractGameZip(zipFilePath: string, gameId: string): Promise<void> {
const paths = this.getGamePaths(gameId);
console.log(`=== 子游戏${gameId}的ZIP包解压流程 ===`);
console.log(`源文件: ${zipFilePath}`);
console.log(`目标路径: ${paths.extractTargetPath}`);
// 1. 清理旧的游戏资源
if (await fileIo.access(paths.extractTargetPath)) {
await fileIo.rmdir(paths.extractTargetPath, true);
console.log(`已清理旧游戏${gameId}资源: ${paths.extractTargetPath}`);
}
// 2. 创建游戏目录结构
await this.createGameDirectories(paths);
// 3. 执行ZIP解压
await ZipExtractor.extractToDirectory(zipFilePath, paths.extractTargetPath);
// 4. 验证游戏文件
await this.validateGameFiles(gameId, paths);
console.log(`子游戏${gameId}解压完成,启动路径: ${paths.webViewLoadUrl}`);
}
/**
* 创建游戏目录结构
*/
private static async createGameDirectories(paths: GamePathInfo): Promise<void> {
const directories = [
paths.extractTargetPath,
paths.jsDir,
paths.cssDir,
paths.imagesDir,
paths.audioDir
];
for (const dir of directories) {
await fileIo.mkdir(dir, true);
}
console.log(`游戏目录结构创建完成`);
}
/**
* 验证游戏文件完整性
*/
private static async validateGameFiles(gameId: string, paths: GamePathInfo): Promise<void> {
const requiredFiles = [
paths.entryFilePath, // index.html - 游戏入口
`${paths.jsDir}/game.js`, // 游戏主逻辑
`${paths.cssDir}/game.css` // 游戏样式
];
for (const filePath of requiredFiles) {
if (!await fileIo.access(filePath)) {
throw new Error(`游戏${gameId}关键文件缺失: ${filePath}`);
}
}
console.log(`游戏${gameId}文件验证通过`);
}
}
3.7 大厅跳转子游戏的具体逻辑详解
跳转触发和参数传递
export class HallGameSwitchController {
/**
* 大厅跳转子游戏的完整流程
* 这是从大厅点击游戏到子游戏启动的详细逻辑
*/
public static async switchFromHallToGame(gameId: string, gameData: any): Promise<void> {
try {
console.log(`=== 大厅跳转子游戏流程开始 ===`);
console.log(`目标游戏ID: ${gameId}`);
console.log(`游戏数据:`, gameData);
// 第一步:在大厅WebView中接收跳转请求
await this.handleHallGameClick(gameId, gameData);
// 第二步:检查子游戏资源状态
const resourceStatus = await this.checkGameResourceStatus(gameId);
// 第三步:根据资源状态决定后续流程
if (resourceStatus.needDownload) {
await this.downloadAndPrepareGame(gameId, resourceStatus);
}
// 第四步:切换WebView并启动子游戏
await this.switchWebViewToGame(gameId, gameData);
console.log(`=== 大厅跳转子游戏流程完成 ===`);
} catch (error) {
console.error(`大厅跳转子游戏失败:`, error);
await this.handleSwitchError(gameId, error);
}
}
/**
* 第一步:处理大厅中的游戏点击事件
*/
private static async handleHallGameClick(gameId: string, gameData: any): Promise<void> {
console.log(`=== 第一步:处理大厅游戏点击 ===`);
// 1. 在大厅WebView中触发JSBridge调用
// 大厅JS代码会调用: window.WebViewJavascriptBridge.callHandler('SwitchOverGameData', {...})
// 2. 验证游戏参数
this.validateGameSwitchParams(gameId, gameData);
// 3. 记录跳转日志用于统计
await Logger.logGameSwitch(gameId, 'hall_click', gameData);
// 4. 显示切换加载界面
await UIController.showGameSwitchLoading(gameId);
console.log(`大厅游戏点击处理完成`);
}
/**
* 第二步:检查子游戏资源状态
*/
private static async checkGameResourceStatus(gameId: string): Promise<GameResourceStatus> {
console.log(`=== 第二步:检查游戏${gameId}资源状态 ===`);
const paths = GamePathResolver.getGamePaths(gameId);
const config = await ConfigManager.getRemoteConfig();
const gameData = await ConfigManager.getCurrentGameData();
// 1. 检查游戏目录是否存在
const gameExists = await fileIo.access(paths.extractTargetPath);
console.log(`游戏目录存在: ${gameExists}`);
if (!gameExists) {
return {
needDownload: true,
reason: 'game_not_exists',
downloadUrl: DownloadUrlResolver.getGameZipDownloadUrl(config, gameData, gameId)
};
}
// 2. 检查游戏版本是否需要更新
const remoteVersion = ConfigParameterHelper.getGameVersion(config, gameData);
const localVersion = await this.getLocalGameVersion(gameId, paths);
console.log(`远程游戏版本: ${remoteVersion}`);
console.log(`本地游戏版本: ${localVersion}`);
const needUpdate = VersionComparator.isVersionNewer(remoteVersion, localVersion);
if (needUpdate) {
return {
needDownload: true,
reason: 'version_update',
downloadUrl: DownloadUrlResolver.getGameZipDownloadUrl(config, gameData, gameId),
oldVersion: localVersion,
newVersion: remoteVersion
};
}
// 3. 验证游戏文件完整性
const isValid = await this.validateGameIntegrity(gameId, paths);
if (!isValid) {
return {
needDownload: true,
reason: 'file_corruption',
downloadUrl: DownloadUrlResolver.getGameZipDownloadUrl(config, gameData, gameId)
};
}
console.log(`游戏${gameId}资源检查通过,无需下载`);
return {
needDownload: false,
reason: 'ready',
launchUrl: paths.webViewLoadUrl
};
}
/**
* 第三步:下载并准备游戏资源(如果需要)
*/
private static async downloadAndPrepareGame(gameId: string, resourceStatus: GameResourceStatus): Promise<void> {
console.log(`=== 第三步:下载并准备游戏${gameId}资源 ===`);
console.log(`下载原因: ${resourceStatus.reason}`);
console.log(`下载地址: ${resourceStatus.downloadUrl}`);
const paths = GamePathResolver.getGamePaths(gameId);
// 1. 显示下载进度界面
await UIController.showGameDownloadProgress(gameId, resourceStatus);
// 2. 下载游戏ZIP包
await FileDownloader.downloadWithProgress(
resourceStatus.downloadUrl,
paths.downloadTempPath,
{
onProgress: (progress: number, total: number) => {
const percent = Math.floor((progress / total) * 100);
UIController.updateGameDownloadProgress(gameId, percent);
},
timeout: 300000 // 5分钟超时
}
);
console.log(`游戏${gameId}ZIP包下载完成: ${paths.downloadTempPath}`);
// 3. 解压游戏资源
await GamePathResolver.extractGameZip(paths.downloadTempPath, gameId);
// 4. 清理临时文件
await fileIo.unlink(paths.downloadTempPath);
// 5. 隐藏下载进度界面
await UIController.hideGameDownloadProgress(gameId);
console.log(`游戏${gameId}资源准备完成`);
}
/**
* 第四步:切换WebView并启动子游戏
*/
private static async switchWebViewToGame(gameId: string, gameData: any): Promise<void> {
console.log(`=== 第四步:切换WebView启动游戏${gameId} ===`);
const paths = GamePathResolver.getGamePaths(gameId);
// 1. 隐藏大厅WebView
await WebViewManager.hideHallWebView();
console.log(`大厅WebView已隐藏`);
// 2. 初始化子游戏WebView
await WebViewManager.initGameWebView(gameId);
console.log(`子游戏WebView初始化完成`);
// 3. 配置子游戏WebView参数
await this.configureGameWebView(gameId, gameData);
// 4. 加载子游戏页面
console.log(`开始加载游戏页面: ${paths.webViewLoadUrl}`);
await WebViewManager.loadGamePage(gameId, paths.webViewLoadUrl);
// 5. 显示子游戏WebView
await WebViewManager.showGameWebView(gameId);
console.log(`子游戏WebView已显示`);
// 6. 向子游戏传递启动参数
await this.passGameStartupParams(gameId, gameData);
// 7. 隐藏加载界面
await UIController.hideGameSwitchLoading();
console.log(`游戏${gameId}启动完成`);
}
/**
* 配置子游戏WebView的特定参数
*/
private static async configureGameWebView(gameId: string, gameData: any): Promise<void> {
const webView = WebViewManager.getGameWebView(gameId);
// 1. 设置用户代理
webView.setUserAgent(`TSGame-HarmonyOS/${await DeviceInfo.getAppVersion()} Game/${gameId}`);
// 2. 设置JavaScript Bridge
await JSBridgeManager.setupGameBridge(gameId, webView);
// 3. 注册游戏专用接口
await this.registerGameSpecificHandlers(gameId, webView);
console.log(`游戏${gameId}的WebView配置完成`);
}
/**
* 向子游戏传递启动参数
*/
private static async passGameStartupParams(gameId: string, gameData: any): Promise<void> {
const webView = WebViewManager.getGameWebView(gameId);
// 构造游戏启动参数
const startupParams = {
gameId: gameId,
userId: gameData.userId,
userToken: gameData.userToken,
serverUrl: ConfigParameterHelper.getServerUrl(await ConfigManager.getRemoteConfig(), gameData),
fromPage: 'hall',
timestamp: Date.now(),
deviceInfo: await DeviceInfo.getBasicInfo()
};
// 通过JSBridge传递参数给游戏
webView.callHandler('onGameInit', startupParams, (response) => {
console.log(`游戏${gameId}初始化回调:`, response);
});
console.log(`游戏${gameId}启动参数传递完成:`, startupParams);
}
/**
* 获取本地游戏版本
*/
private static async getLocalGameVersion(gameId: string, paths: GamePathInfo): Promise<string> {
try {
if (await fileIo.access(paths.versionFilePath)) {
const versionContent = await fileIo.readText(paths.versionFilePath);
return versionContent.trim();
}
} catch (error) {
console.warn(`读取游戏${gameId}版本失败:`, error);
}
return '';
}
/**
* 验证游戏文件完整性
*/
private static async validateGameIntegrity(gameId: string, paths: GamePathInfo): Promise<boolean> {
try {
// 检查关键文件是否存在
const requiredFiles = [
paths.entryFilePath,
`${paths.jsDir}/game.js`,
`${paths.cssDir}/game.css`
];
for (const filePath of requiredFiles) {
if (!await fileIo.access(filePath)) {
console.warn(`游戏${gameId}关键文件缺失: ${filePath}`);
return false;
}
}
return true;
} catch (error) {
console.error(`验证游戏${gameId}完整性失败:`, error);
return false;
}
}
/**
* 处理切换错误
*/
private static async handleSwitchError(gameId: string, error: Error): Promise<void> {
console.error(`游戏${gameId}切换失败:`, error);
// 隐藏加载界面
await UIController.hideGameSwitchLoading();
// 显示错误提示
await UIController.showErrorDialog(`游戏启动失败`, error.message);
// 记录错误日志
await Logger.logError('GAME_SWITCH_ERROR', {
gameId,
error: error.message,
stack: error.stack
});
}
}
// 相关数据结构定义
interface GameResourceStatus {
needDownload: boolean;
reason: 'game_not_exists' | 'version_update' | 'file_corruption' | 'ready';
downloadUrl?: string;
oldVersion?: string;
newVersion?: string;
launchUrl?: string;
}
interface HallPathInfo {
downloadTempPath: string;
extractTargetPath: string;
entryFilePath: string;
versionFilePath: string;
configFilePath: string;
webViewLoadUrl: string;
}
interface GamePathInfo {
downloadTempPath: string;
extractTargetPath: string;
entryFilePath: string;
versionFilePath: string;
configFilePath: string;
webViewLoadUrl: string;
jsDir: string;
cssDir: string;
imagesDir: string;
audioDir: string;
}
资源目录和解压路径
4.1 文件系统结构
HarmonyOS应用文件系统布局
/data/app/el2/100/base/com.jx.jyhd.harmonyos/haps/entry/files/
├── game_resources/ # 游戏资源根目录
│ ├── hall/ # 大厅资源目录
│ │ ├── index.html # 大厅入口文件
│ │ ├── js/ # 大厅JS文件
│ │ │ ├── main.js # 大厅主要逻辑
│ │ │ ├── bridge.js # JSBridge桥接文件
│ │ │ └── utils.js # 工具函数
│ │ ├── css/ # 大厅样式文件
│ │ │ ├── main.css # 主样式
│ │ │ └── responsive.css # 响应式样式
│ │ ├── images/ # 大厅图片资源
│ │ │ ├── logo.png # 应用Logo
│ │ │ ├── bg.jpg # 背景图片
│ │ │ └── icons/ # 图标文件夹
│ │ └── version.js # 大厅版本信息
│ ├── games/ # 子游戏资源目录
│ │ ├── game001/ # 斗地主游戏
│ │ │ ├── index.html # 游戏入口文件
│ │ │ ├── js/ # 游戏JS文件
│ │ │ ├── css/ # 游戏样式文件
│ │ │ ├── images/ # 游戏图片资源
│ │ │ ├── audio/ # 游戏音频资源
│ │ │ └── version.js # 游戏版本信息
│ │ ├── game002/ # 麻将游戏
│ │ │ ├── index.html
│ │ │ ├── js/
│ │ │ ├── css/
│ │ │ ├── images/
│ │ │ ├── audio/
│ │ │ └── version.js
│ │ └── ... # 其他子游戏
│ ├── downloads/ # 临时下载目录
│ │ ├── hall_temp.zip # 临时大厅ZIP包
│ │ ├── game001_temp.zip # 临时游戏ZIP包
│ │ └── ...
│ └── cache/ # 缓存目录
│ ├── images/ # 图片缓存
│ ├── config/ # 配置缓存
│ └── temp/ # 临时文件
├── config_cache/ # 配置缓存目录
│ ├── remote_config.json # 远程配置缓存
│ └── startup_config.json # 启动配置缓存
└── logs/ # 日志目录
├── app.log # 应用日志
├── download.log # 下载日志
└── error.log # 错误日志
4.2 ZIP包解压路径规范
路径管理类
export class PathManager {
/**
* 获取应用根目录路径
*/
public static getAppRootPath(): string {
return getContext().filesDir;
}
/**
* 获取游戏资源根目录路径
*/
public static getGameResourcesPath(): string {
return `${this.getAppRootPath()}/game_resources`;
}
/**
* 获取大厅资源目录路径
*/
public static getHallResourcePath(): string {
return `${this.getGameResourcesPath()}/hall`;
}
/**
* 获取子游戏资源目录路径
* @param gameId 游戏ID
*/
public static getGameResourcePath(gameId: string): string {
return `${this.getGameResourcesPath()}/games/${gameId}`;
}
/**
* 获取下载临时目录路径
*/
public static getDownloadsPath(): string {
return `${this.getGameResourcesPath()}/downloads`;
}
/**
* 获取大厅ZIP包临时下载路径
*/
public static getHallZipTempPath(): string {
return `${this.getDownloadsPath()}/hall_temp.zip`;
}
/**
* 获取子游戏ZIP包临时下载路径
* @param gameId 游戏ID
*/
public static getGameZipTempPath(gameId: string): string {
return `${this.getDownloadsPath()}/${gameId}_temp.zip`;
}
/**
* 获取配置缓存目录路径
*/
public static getConfigCachePath(): string {
return `${this.getAppRootPath()}/config_cache`;
}
/**
* 获取远程配置缓存文件路径
*/
public static getRemoteConfigCachePath(): string {
return `${this.getConfigCachePath()}/remote_config.json`;
}
}
ZIP包解压流程
export class ZipExtractor {
/**
* 解压大厅ZIP包到指定目录
* @param zipFilePath ZIP包文件路径
*/
public static async extractHallZip(zipFilePath: string): Promise<void> {
const targetPath = PathManager.getHallResourcePath();
console.log(`开始解压大厅ZIP包:`);
console.log(`源文件: ${zipFilePath}`);
console.log(`目标路径: ${targetPath}`);
// 1. 清理目标目录
await this.cleanDirectory(targetPath);
// 2. 创建目标目录
await fileIo.mkdir(targetPath, true);
// 3. 解压ZIP包
await this.extractZipToDirectory(zipFilePath, targetPath);
// 4. 验证解压结果
await this.verifyExtractedFiles(targetPath);
console.log('大厅ZIP包解压完成');
}
/**
* 解压子游戏ZIP包到指定目录
* @param zipFilePath ZIP包文件路径
* @param gameId 游戏ID
*/
public static async extractGameZip(zipFilePath: string, gameId: string): Promise<void> {
const targetPath = PathManager.getGameResourcePath(gameId);
console.log(`开始解压游戏ZIP包:`);
console.log(`游戏ID: ${gameId}`);
console.log(`源文件: ${zipFilePath}`);
console.log(`目标路径: ${targetPath}`);
// 1. 清理目标目录
await this.cleanDirectory(targetPath);
// 2. 创建目标目录
await fileIo.mkdir(targetPath, true);
// 3. 解压ZIP包
await this.extractZipToDirectory(zipFilePath, targetPath);
// 4. 验证解压结果
await this.verifyExtractedFiles(targetPath);
console.log(`游戏${gameId}的ZIP包解压完成`);
}
/**
* 清理目录
*/
private static async cleanDirectory(dirPath: string): Promise<void> {
if (await fileIo.access(dirPath)) {
await fileIo.rmdir(dirPath, true);
console.log(`已清理目录: ${dirPath}`);
}
}
/**
* 执行ZIP包解压
*/
private static async extractZipToDirectory(zipPath: string, targetPath: string): Promise<void> {
// 使用HarmonyOS的解压API
const zipFile = new zlib.ZipFile(zipPath);
await zipFile.extractAll(targetPath);
console.log(`ZIP包解压完成: ${zipPath} -> ${targetPath}`);
}
/**
* 验证解压后的文件
*/
private static async verifyExtractedFiles(dirPath: string): Promise<void> {
// 检查必要的文件是否存在
const indexHtmlPath = `${dirPath}/index.html`;
if (!await fileIo.access(indexHtmlPath)) {
throw new Error(`解压验证失败: 缺少index.html文件`);
}
console.log(`解压文件验证通过: ${dirPath}`);
}
}
大厅启动逻辑
5.1 大厅资源初始化
大厅管理器
export class HallManager {
/**
* 初始化大厅资源
*/
public static async initializeHall(): Promise<void> {
try {
console.log('开始初始化大厅资源');
// 1. 检查大厅资源版本
const needUpdate = await this.checkHallVersion();
if (needUpdate) {
// 2. 下载大厅资源
await this.downloadHallResources();
// 3. 解压大厅资源
await this.extractHallResources();
}
// 4. 验证大厅资源完整性
await this.verifyHallResources();
// 5. 初始化大厅WebView
await this.initializeHallWebView();
console.log('大厅资源初始化完成');
} catch (error) {
console.error('大厅资源初始化失败:', error);
throw error;
}
}
/**
* 检查大厅版本
*/
private static async checkHallVersion(): Promise<boolean> {
const config = await ConfigManager.getRemoteConfig();
const gameData = await ConfigManager.getCurrentGameData();
// 获取远程大厅版本
const remoteVersion = ConfigParameterHelper.getGameVersion(config, gameData);
console.log('远程大厅版本:', remoteVersion);
// 获取本地大厅版本
const localVersion = await this.getLocalHallVersion();
console.log('本地大厅版本:', localVersion);
// 比较版本
const needUpdate = VersionComparator.isVersionNewer(remoteVersion, localVersion);
console.log('大厅需要更新:', needUpdate);
return needUpdate;
}
/**
* 下载大厅资源
*/
private static async downloadHallResources(): Promise<void> {
const config = await ConfigManager.getRemoteConfig();
const gameData = await ConfigManager.getCurrentGameData();
// 获取大厅下载URL
const downloadUrl = ConfigParameterHelper.getGameDownloadUrl(config, gameData);
console.log('大厅下载URL:', downloadUrl);
if (!downloadUrl) {
throw new Error('未找到大厅下载URL');
}
// 下载大厅ZIP包
const zipPath = PathManager.getHallZipTempPath();
await FileDownloader.download(downloadUrl, zipPath, {
onProgress: (progress: number, total: number) => {
const percent = Math.floor((progress / total) * 100);
console.log(`大厅下载进度: ${percent}%`);
}
});
console.log('大厅ZIP包下载完成:', zipPath);
}
/**
* 解压大厅资源
*/
private static async extractHallResources(): Promise<void> {
const zipPath = PathManager.getHallZipTempPath();
// 解压到大厅目录
await ZipExtractor.extractHallZip(zipPath);
// 清理临时ZIP文件
await fileIo.unlink(zipPath);
console.log('大厅资源解压完成');
}
/**
* 获取本地大厅版本
*/
private static async getLocalHallVersion(): Promise<string> {
const versionPath = `${PathManager.getHallResourcePath()}/version.txt`;
try {
if (await fileIo.access(versionPath)) {
const versionContent = await fileIo.readText(versionPath);
return versionContent.trim();
}
} catch (error) {
console.warn('读取本地大厅版本失败:', error);
}
return '';
}
}
5.2 大厅WebView启动
WebView管理器
export class HallWebViewController {
private webView: WebviewController | null = null;
/**
* 初始化大厅WebView
*/
public async initializeHallWebView(): Promise<void> {
try {
console.log('开始初始化大厅WebView');
// 1. 创建WebView控制器
this.webView = new webview.WebviewController();
// 2. 配置WebView设置
await this.configureWebViewSettings();
// 3. 注册JS Bridge接口
await this.registerJSBridgeHandlers();
// 4. 加载大厅HTML页面
await this.loadHallPage();
console.log('大厅WebView初始化完成');
} catch (error) {
console.error('大厅WebView初始化失败:', error);
throw error;
}
}
/**
* 配置WebView设置
*/
private async configureWebViewSettings(): Promise<void> {
if (!this.webView) return;
// 启用JavaScript
this.webView.enableJavaScript = true;
// 启用DOM存储
this.webView.domStorageAccess = true;
// 启用文件访问
this.webView.fileAccess = true;
// 设置用户代理
this.webView.userAgent = 'TSGame_HarmonyOS/3.6.0 (HarmonyOS)';
console.log('WebView设置配置完成');
}
/**
* 注册JS Bridge接口
*/
private async registerJSBridgeHandlers(): Promise<void> {
if (!this.webView) return;
// 注册所有JS Bridge接口
await JSBridgeManager.registerAllHandlers(this.webView);
console.log('JS Bridge接口注册完成');
}
/**
* 加载大厅HTML页面
*/
private async loadHallPage(): Promise<void> {
if (!this.webView) return;
const hallIndexPath = `${PathManager.getHallResourcePath()}/index.html`;
// 检查大厅HTML文件是否存在
if (!await fileIo.access(hallIndexPath)) {
throw new Error('大厅HTML文件不存在');
}
// 构建文件URL
const fileUrl = `file://${hallIndexPath}`;
console.log('加载大厅页面:', fileUrl);
// 加载页面
await this.webView.loadUrl(fileUrl);
// 设置页面加载监听
this.webView.onPageBegin = (event) => {
console.log('大厅页面开始加载:', event.url);
};
this.webView.onPageEnd = (event) => {
console.log('大厅页面加载完成:', event.url);
this.onHallPageLoadCompleted();
};
this.webView.onErrorReceive = (event) => {
console.error('大厅页面加载错误:', event.error);
};
}
/**
* 大厅页面加载完成回调
*/
private onHallPageLoadCompleted(): void {
console.log('大厅启动完成,可以接受用户操作');
// 通知应用大厅已就绪
EventManager.emit('hall_ready');
}
}
子游戏跳转启动逻辑
6.1 子游戏资源管理
子游戏启动的完整时序
大厅点击游戏
↓
HallWebView.callHandler('SwitchOverGameData')
↓
GameSwitchController.handleGameSwitch()
↓
检查子游戏资源状态 ← 读取 game_version 配置属性
↓
[如果需要] 下载子游戏ZIP ← 使用 game_download 配置属性
↓
[如果需要] 解压到游戏目录 ← 解压到 /game_resources/games/{gameId}/
↓
隐藏大厅WebView,初始化游戏WebView
↓
加载游戏页面 ← 加载 file:///game_resources/games/{gameId}/index.html
↓
显示游戏WebView,传递启动参数
↓
子游戏启动完成
子游戏跳转的JSBridge接口调用
// 在大厅WebView中的JavaScript代码
function switchToGame(gameInfo) {
// 调用SwitchOverGameData接口跳转到子游戏
window.WebViewJavascriptBridge.callHandler('SwitchOverGameData', {
gameId: gameInfo.gameId, // 子游戏唯一标识
gameType: gameInfo.gameType, // 游戏类型
gameUrl: gameInfo.gameUrl, // 游戏访问地址(用于fallback)
gameName: gameInfo.gameName, // 游戏显示名称
gameIcon: gameInfo.gameIcon, // 游戏图标URL
gameDesc: gameInfo.gameDesc, // 游戏描述
extraData: { // 传递给子游戏的额外数据
userId: currentUser.userId,
userToken: currentUser.token,
fromHall: true,
timestamp: Date.now()
}
});
}
6.2 子游戏WebView切换的详细实现
WebView切换管理器的完整实现
export class GameWebViewController {
private static hallWebView: WebviewController | null = null;
private static gameWebViews: Map<string, WebviewController> = new Map();
private static currentGameId: string = '';
/**
* 处理大厅到子游戏的切换
* 这是SwitchOverGameData接口的具体实现
*/
public static async handleSwitchOverGameData(params: SwitchGameParams): Promise<void> {
try {
console.log(`=== 开始处理游戏切换请求 ===`);
console.log(`游戏ID: ${params.gameId}`);
console.log(`游戏参数:`, params);
// 第一阶段:资源检查和准备
await this.prepareGameResources(params.gameId, params);
// 第二阶段:WebView切换
await this.performWebViewSwitch(params.gameId, params);
// 第三阶段:游戏启动
await this.launchGameInWebView(params.gameId, params);
console.log(`=== 游戏切换完成 ===`);
} catch (error) {
console.error(`游戏切换失败:`, error);
await this.handleSwitchError(params.gameId, error);
}
}
/**
* 第一阶段:准备游戏资源
*/
private static async prepareGameResources(gameId: string, params: SwitchGameParams): Promise<void> {
console.log(`=== 第一阶段:准备游戏${gameId}资源 ===`);
// 1. 获取远程配置,检查版本信息
const config = await ConfigManager.getRemoteConfig();
const gameData = await ConfigManager.getCurrentGameData();
// 2. 读取游戏版本配置属性进行版本检查
const remoteGameVersion = ConfigParameterHelper.getGameVersion(config, gameData);
console.log(`远程游戏版本: ${remoteGameVersion}`);
// 3. 获取游戏路径信息
const gamePaths = GamePathResolver.getGamePaths(gameId);
console.log(`游戏资源路径: ${gamePaths.extractTargetPath}`);
console.log(`游戏启动路径: ${gamePaths.webViewLoadUrl}`);
// 4. 检查本地游戏资源状态
const resourceStatus = await this.checkGameResourceStatus(gameId, remoteGameVersion, gamePaths);
// 5. 如果需要下载,执行下载和解压流程
if (resourceStatus.needDownload) {
await this.downloadAndExtractGame(gameId, config, gameData, gamePaths);
}
console.log(`游戏${gameId}资源准备完成`);
}
/**
* 检查游戏资源状态的详细逻辑
*/
private static async checkGameResourceStatus(
gameId: string,
remoteVersion: string,
gamePaths: GamePathInfo
): Promise<GameResourceStatus> {
// 1. 检查游戏目录是否存在
const gameExists = await fileIo.access(gamePaths.extractTargetPath);
if (!gameExists) {
console.log(`游戏${gameId}目录不存在: ${gamePaths.extractTargetPath}`);
return { needDownload: true, reason: 'not_exists' };
}
// 2. 检查游戏入口文件
const entryExists = await fileIo.access(gamePaths.entryFilePath);
if (!entryExists) {
console.log(`游戏${gameId}入口文件不存在: ${gamePaths.entryFilePath}`);
return { needDownload: true, reason: 'entry_missing' };
}
// 3. 读取本地版本信息
const localVersion = await this.getLocalGameVersion(gamePaths.versionFilePath);
console.log(`本地游戏版本: ${localVersion}`);
// 4. 版本比较
if (VersionComparator.isVersionNewer(remoteVersion, localVersion)) {
console.log(`游戏${gameId}需要版本更新: ${localVersion} -> ${remoteVersion}`);
return {
needDownload: true,
reason: 'version_update',
oldVersion: localVersion,
newVersion: remoteVersion
};
}
// 5. 验证关键文件完整性
const isValid = await this.validateGameFiles(gameId, gamePaths);
if (!isValid) {
console.log(`游戏${gameId}文件验证失败`);
return { needDownload: true, reason: 'file_corruption' };
}
console.log(`游戏${gameId}资源检查通过`);
return { needDownload: false, reason: 'ready' };
}
/**
* 下载和解压游戏的详细流程
*/
private static async downloadAndExtractGame(
gameId: string,
config: any,
gameData: any,
gamePaths: GamePathInfo
): Promise<void> {
console.log(`=== 开始下载和解压游戏${gameId} ===`);
// 1. 读取游戏下载地址配置属性
const gameDownloadUrl = DownloadUrlResolver.getGameZipDownloadUrl(config, gameData, gameId);
console.log(`游戏下载地址: ${gameDownloadUrl}`);
if (!gameDownloadUrl) {
throw new Error(`未找到游戏${gameId}的下载地址配置`);
}
// 2. 显示下载进度界面
await UIController.showGameDownloadDialog(gameId);
try {
// 3. 下载游戏ZIP包到临时路径
console.log(`开始下载到: ${gamePaths.downloadTempPath}`);
await FileDownloader.downloadWithProgress(
gameDownloadUrl,
gamePaths.downloadTempPath,
{
onProgress: (loaded: number, total: number) => {
const percent = Math.floor((loaded / total) * 100);
UIController.updateGameDownloadProgress(gameId, percent);
console.log(`游戏${gameId}下载进度: ${percent}%`);
},
timeout: 300000 // 5分钟超时
}
);
console.log(`游戏${gameId}下载完成`);
// 4. 解压游戏ZIP包到目标路径
console.log(`开始解压到: ${gamePaths.extractTargetPath}`);
await GamePathResolver.extractGameZip(gamePaths.downloadTempPath, gameId);
console.log(`游戏${gameId}解压完成`);
// 5. 清理临时下载文件
await fileIo.unlink(gamePaths.downloadTempPath);
console.log(`临时文件已清理: ${gamePaths.downloadTempPath}`);
} finally {
// 6. 隐藏下载进度界面
await UIController.hideGameDownloadDialog(gameId);
}
}
/**
* 第二阶段:执行WebView切换
*/
private static async performWebViewSwitch(gameId: string, params: SwitchGameParams): Promise<void> {
console.log(`=== 第二阶段:执行WebView切换到游戏${gameId} ===`);
// 1. 保存当前大厅状态
await this.saveHallState();
// 2. 隐藏大厅WebView
await this.hideHallWebView();
console.log(`大厅WebView已隐藏`);
// 3. 初始化或获取游戏WebView
const gameWebView = await this.getOrCreateGameWebView(gameId);
console.log(`游戏${gameId}的WebView已准备`);
// 4. 配置游戏WebView
await this.configureGameWebView(gameId, gameWebView, params);
console.log(`游戏${gameId}的WebView配置完成`);
// 5. 显示游戏WebView
await this.showGameWebView(gameId);
console.log(`游戏${gameId}的WebView已显示`);
// 6. 更新当前游戏状态
this.currentGameId = gameId;
}
/**
* 第三阶段:在WebView中启动游戏
*/
private static async launchGameInWebView(gameId: string, params: SwitchGameParams): Promise<void> {
console.log(`=== 第三阶段:在WebView中启动游戏${gameId} ===`);
const gamePaths = GamePathResolver.getGamePaths(gameId);
const gameWebView = this.gameWebViews.get(gameId);
if (!gameWebView) {
throw new Error(`游戏${gameId}的WebView未找到`);
}
// 1. 加载游戏页面
console.log(`加载游戏页面: ${gamePaths.webViewLoadUrl}`);
await gameWebView.loadUrl(gamePaths.webViewLoadUrl);
// 2. 等待页面加载完成
await this.waitForGamePageReady(gameId, gameWebView);
// 3. 设置游戏启动参数
const startupParams = await this.buildGameStartupParams(gameId, params);
console.log(`游戏启动参数:`, startupParams);
// 4. 通过JSBridge传递启动参数
await this.passStartupParamsToGame(gameId, gameWebView, startupParams);
// 5. 通知游戏初始化完成
await this.notifyGameInitComplete(gameId, gameWebView);
console.log(`游戏${gameId}在WebView中启动完成`);
}
/**
* 获取或创建游戏WebView
*/
private static async getOrCreateGameWebView(gameId: string): Promise<WebviewController> {
let gameWebView = this.gameWebViews.get(gameId);
if (!gameWebView) {
console.log(`创建新的游戏${gameId}WebView`);
// 创建新的WebView实例
gameWebView = new webview.WebviewController();
// 基础配置
gameWebView.setJavaScriptEnabled(true);
gameWebView.setDomStorageEnabled(true);
gameWebView.setFileAccessEnabled(true);
gameWebView.setAllowFileAccessFromFileURLs(true);
// 设置用户代理
const userAgent = `TSGame-HarmonyOS/${await DeviceInfo.getAppVersion()} Game/${gameId}`;
gameWebView.setUserAgent(userAgent);
// 缓存WebView实例
this.gameWebViews.set(gameId, gameWebView);
console.log(`游戏${gameId}WebView创建完成`);
}
return gameWebView;
}
/**
* 配置游戏WebView的专用设置
*/
private static async configureGameWebView(
gameId: string,
gameWebView: WebviewController,
params: SwitchGameParams
): Promise<void> {
// 1. 设置JSBridge
await JSBridgeManager.setupGameJSBridge(gameId, gameWebView);
console.log(`游戏${gameId}的JSBridge设置完成`);
// 2. 注册游戏专用的Handler
await this.registerGameSpecificHandlers(gameId, gameWebView);
// 3. 设置WebView事件监听
this.setupGameWebViewListeners(gameId, gameWebView);
// 4. 配置游戏特定的WebView选项
await this.applyGameSpecificWebViewConfig(gameId, gameWebView, params);
}
/**
* 构建游戏启动参数
*/
private static async buildGameStartupParams(gameId: string, params: SwitchGameParams): Promise<any> {
const config = await ConfigManager.getRemoteConfig();
const gameData = await ConfigManager.getCurrentGameData();
const userInfo = await UserManager.getCurrentUserInfo();
return {
// 游戏基础信息
gameId: gameId,
gameName: params.gameName,
gameType: params.gameType,
// 用户信息
userId: userInfo.userId,
userToken: userInfo.token,
nickname: userInfo.nickname,
avatar: userInfo.avatar,
coins: userInfo.coins,
level: userInfo.level,
// 服务器配置
serverUrl: ConfigParameterHelper.getServerUrl(config, gameData),
apiVersion: 'v1.0',
// 启动上下文
fromPage: 'hall',
timestamp: Date.now(),
sessionId: generateSessionId(),
// 设备信息
deviceInfo: await DeviceInfo.getBasicInfo(),
// 传递的额外数据
extraData: params.extraData || {}
};
}
/**
* 通过JSBridge传递启动参数给游戏
*/
private static async passStartupParamsToGame(
gameId: string,
gameWebView: WebviewController,
startupParams: any
): Promise<void> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`游戏${gameId}启动参数传递超时`));
}, 10000);
// 调用游戏的初始化Handler
gameWebView.callHandler('onGameInit', startupParams, (response) => {
clearTimeout(timeout);
console.log(`游戏${gameId}初始化响应:`, response);
if (response && response.success) {
resolve();
} else {
reject(new Error(`游戏${gameId}初始化失败: ${response?.message || '未知错误'}`));
}
});
});
}
/**
* 等待游戏页面加载完成
*/
private static async waitForGamePageReady(gameId: string, gameWebView: WebviewController): Promise<void> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`游戏${gameId}页面加载超时`));
}, 30000);
// 监听页面加载完成事件
gameWebView.setOnPageFinished((url) => {
clearTimeout(timeout);
console.log(`游戏${gameId}页面加载完成: ${url}`);
resolve();
});
// 监听页面加载错误
gameWebView.setOnPageError((error) => {
clearTimeout(timeout);
reject(new Error(`游戏${gameId}页面加载失败: ${error.description}`));
});
});
}
/**
* 隐藏大厅WebView
*/
private static async hideHallWebView(): Promise<void> {
if (this.hallWebView) {
// 通知大厅页面即将隐藏
this.hallWebView.callHandler('onPagePause', { reason: 'game_switch' });
// 隐藏大厅WebView UI
await UIController.hideHallWebView();
}
}
/**
* 显示游戏WebView
*/
private static async showGameWebView(gameId: string): Promise<void> {
// 显示游戏WebView UI
await UIController.showGameWebView(gameId);
// 通知游戏页面已显示
const gameWebView = this.gameWebViews.get(gameId);
if (gameWebView) {
gameWebView.callHandler('onPageResume', {
fromBackground: false,
reason: 'game_start'
});
}
}
/**
* 游戏返回大厅的逻辑
*/
public static async switchBackToHall(gameId: string): Promise<void> {
console.log(`=== 游戏${gameId}返回大厅 ===`);
// 1. 隐藏游戏WebView
await UIController.hideGameWebView(gameId);
// 2. 通知游戏页面暂停
const gameWebView = this.gameWebViews.get(gameId);
if (gameWebView) {
gameWebView.callHandler('onPagePause', { reason: 'back_to_hall' });
}
// 3. 显示大厅WebView
await UIController.showHallWebView();
// 4. 通知大厅页面恢复
if (this.hallWebView) {
this.hallWebView.callHandler('onPageResume', {
fromBackground: false,
reason: 'back_from_game',
gameId: gameId
});
}
// 5. 更新当前状态
this.currentGameId = '';
console.log(`已返回大厅`);
}
/**
* 处理切换错误
*/
private static async handleSwitchError(gameId: string, error: Error): Promise<void> {
console.error(`游戏${gameId}切换失败:`, error);
// 记录错误日志
await Logger.logError('GAME_SWITCH_ERROR', {
gameId,
error: error.message,
stack: error.stack,
timestamp: Date.now()
});
// 隐藏可能的加载界面
await UIController.hideGameDownloadDialog(gameId);
// 显示错误提示
await UIController.showErrorDialog(`游戏启动失败`, `${error.message}\n\n请稍后重试或联系客服。`);
// 确保回到大厅状态
await this.switchBackToHall(gameId);
}
}
// 辅助数据结构
interface SwitchGameParams {
gameId: string;
gameType: string;
gameUrl: string;
gameName: string;
gameIcon: string;
gameDesc?: string;
extraData?: any;
}
interface GameResourceStatus {
needDownload: boolean;
reason: 'not_exists' | 'entry_missing' | 'version_update' | 'file_corruption' | 'ready';
oldVersion?: string;
newVersion?: string;
}
6.3 子游戏启动流程的配置属性读取详解
启动过程中读取的具体配置属性
export class GameStartupConfigReader {
/**
* 子游戏启动过程中需要读取的所有配置属性
*/
public static async readGameStartupConfig(gameId: string): Promise<GameStartupConfig> {
const config = await ConfigManager.getRemoteConfig();
const gameData = await ConfigManager.getCurrentGameData();
console.log(`=== 读取游戏${gameId}启动配置 ===`);
return {
// 版本相关配置(用于资源检查)
gameVersion: ConfigParameterHelper.getGameVersion(config, gameData),
// 下载相关配置(用于资源下载)
gameDownloadUrl: ConfigParameterHelper.getGameDownloadUrl(config, gameData),
gameSize: ConfigParameterHelper.getGameSize(config, gameData),
// 服务器相关配置(传递给游戏用于网络请求)
serverUrl: ConfigParameterHelper.getServerUrl(config, gameData),
apiEndpoint: ConfigParameterHelper.getString(config, gameData, 'api_endpoint', '/api'),
// 功能开关配置
enableAudio: ConfigParameterHelper.getBoolean(config, gameData, 'enable_audio', true),
enableVibration: ConfigParameterHelper.getBoolean(config, gameData, 'enable_vibration', true),
// 游戏专用配置
maxPlayers: ConfigParameterHelper.getNumber(config, gameData, 'max_players', 4),
gameTimeout: ConfigParameterHelper.getNumber(config, gameData, 'game_timeout', 300),
// 调试配置
debugMode: ConfigParameterHelper.getBoolean(config, gameData, 'debug_mode', false),
logLevel: ConfigParameterHelper.getString(config, gameData, 'log_level', 'info')
};
}
/**
* 打印配置读取详细信息
*/
public static logConfigDetails(gameId: string, config: any, gameData: any): void {
console.log(`=== 游戏${gameId}配置读取详情 ===`);
// 显示配置文件层级结构
console.log(`配置文件层级信息:`);
console.log(`- 代理商ID: ${gameData.agentid}`);
console.log(`- 渠道ID: ${gameData.channelid}`);
console.log(`- 市场ID: ${gameData.marketid}`);
// 显示版本配置读取路径
console.log(`版本配置读取路径:`);
this.logConfigPath('game_version', config, gameData);
// 显示下载配置读取路径
console.log(`下载配置读取路径:`);
this.logConfigPath('game_download', config, gameData);
// 显示服务器配置读取路径
console.log(`服务器配置读取路径:`);
this.logConfigPath('url', config, gameData);
}
/**
* 记录具体配置属性的读取路径
*/
private static logConfigPath(propertyName: string, config: any, gameData: any): void {
const paths = [
`marketlist[${gameData.marketid}].${propertyName}`,
`channellist[${gameData.channelid}].${propertyName}`,
`agentlist[${gameData.agentid}].${propertyName}`,
`data.${propertyName}`
];
for (const path of paths) {
const value = this.getValueByPath(config, path);
if (value !== undefined) {
console.log(` ✓ ${path} = ${value}`);
break;
} else {
console.log(` ✗ ${path} = undefined`);
}
}
}
/**
* 根据路径获取配置值
*/
private static getValueByPath(config: any, path: string): any {
const parts = path.split('.');
let current = config;
for (const part of parts) {
if (part.includes('[') && part.includes(']')) {
// 处理数组索引,如 marketlist[market001]
const arrayName = part.substring(0, part.indexOf('['));
const index = part.substring(part.indexOf('[') + 1, part.indexOf(']'));
if (current[arrayName] && Array.isArray(current[arrayName])) {
current = current[arrayName].find((item: any) =>
item.marketid === index || item.channelid === index || item.agentid === index
);
} else {
return undefined;
}
} else {
current = current[part];
}
if (current === undefined) {
return undefined;
}
}
return current;
}
}
interface GameStartupConfig {
gameVersion: string;
gameDownloadUrl: string;
gameSize: string;
serverUrl: string;
apiEndpoint: string;
enableAudio: boolean;
enableVibration: boolean;
maxPlayers: number;
gameTimeout: number;
debugMode: boolean;
logLevel: string;
}
6.4 子游戏启动路径的完整解析
游戏启动路径的构建和验证
export class GameLaunchPathResolver {
/**
* 解析游戏的完整启动路径
*/
public static resolveGameLaunchPaths(gameId: string): GameLaunchPaths {
const appRoot = getContext().filesDir;
const paths = {
// 基础路径
appRootPath: appRoot,
gameRootPath: `${appRoot}/game_resources/games/${gameId}`,
// 启动相关路径
entryHtmlPath: `${appRoot}/game_resources/games/${gameId}/index.html`,
webViewLoadUrl: `file://${appRoot}/game_resources/games/${gameId}/index.html`,
// 资源路径
jsPath: `${appRoot}/game_resources/games/${gameId}/js`,
cssPath: `${appRoot}/game_resources/games/${gameId}/css`,
imagesPath: `${appRoot}/game_resources/games/${gameId}/images`,
audioPath: `${appRoot}/game_resources/games/${gameId}/audio`,
// 配置和数据路径
gameConfigPath: `${appRoot}/game_resources/games/${gameId}/game.json`,
versionFilePath: `${appRoot}/game_resources/games/${gameId}/version.txt`,
saveDataPath: `${appRoot}/game_resources/games/${gameId}/savedata`,
// 临时和缓存路径
tempPath: `${appRoot}/game_resources/games/${gameId}/temp`,
cachePath: `${appRoot}/game_resources/games/${gameId}/cache`
};
console.log(`=== 游戏${gameId}启动路径解析 ===`);
console.log(`游戏根目录: ${paths.gameRootPath}`);
console.log(`启动入口: ${paths.entryHtmlPath}`);
console.log(`WebView加载URL: ${paths.webViewLoadUrl}`);
return paths;
}
/**
* 验证游戏启动路径的有效性
*/
public static async validateGameLaunchPaths(gameId: string, paths: GameLaunchPaths): Promise<ValidationResult> {
console.log(`=== 验证游戏${gameId}启动路径 ===`);
const validationResult: ValidationResult = {
isValid: true,
errors: [],
warnings: []
};
// 1. 验证游戏根目录
if (!await fileIo.access(paths.gameRootPath)) {
validationResult.isValid = false;
validationResult.errors.push(`游戏根目录不存在: ${paths.gameRootPath}`);
}
// 2. 验证启动入口文件
if (!await fileIo.access(paths.entryHtmlPath)) {
validationResult.isValid = false;
validationResult.errors.push(`游戏入口文件不存在: ${paths.entryHtmlPath}`);
}
// 3. 验证关键资源目录
const requiredDirs = [paths.jsPath, paths.cssPath];
for (const dir of requiredDirs) {
if (!await fileIo.access(dir)) {
validationResult.warnings.push(`资源目录不存在: ${dir}`);
}
}
// 4. 验证版本文件
if (!await fileIo.access(paths.versionFilePath)) {
validationResult.warnings.push(`版本文件不存在: ${paths.versionFilePath}`);
}
console.log(`游戏${gameId}路径验证结果:`, validationResult);
return validationResult;
}
/**
* 创建游戏启动所需的目录结构
*/
public static async createGameDirectoryStructure(gameId: string, paths: GameLaunchPaths): Promise<void> {
console.log(`=== 创建游戏${gameId}目录结构 ===`);
const directories = [
paths.gameRootPath,
paths.jsPath,
paths.cssPath,
paths.imagesPath,
paths.audioPath,
paths.saveDataPath,
paths.tempPath,
paths.cachePath
];
for (const dir of directories) {
try {
await fileIo.mkdir(dir, true);
console.log(`目录创建成功: ${dir}`);
} catch (error) {
console.warn(`目录创建失败: ${dir}`, error);
}
}
}
}
interface GameLaunchPaths {
appRootPath: string;
gameRootPath: string;
entryHtmlPath: string;
webViewLoadUrl: string;
jsPath: string;
cssPath: string;
imagesPath: string;
audioPath: string;
gameConfigPath: string;
versionFilePath: string;
saveDataPath: string;
tempPath: string;
cachePath: string;
}
interface ValidationResult {
isValid: boolean;
errors: string[];
warnings: string[];
}
总结
TSGame HarmonyOS应用的启动流程采用分阶段、可恢复的设计理念,通过精确的配置属性读取和路径管理实现应用和游戏的动态更新。
关键配置属性汇总
应用升级验证的配置属性
| 配置属性 | 读取方法 | 作用说明 | 使用时机 |
|---|---|---|---|
app_version |
ConfigParameterHelper.getAppVersion() |
主要验证属性 - 远程应用版本号,与本地版本比较判断是否需要升级 | 应用启动时版本检查 |
app_download |
ConfigParameterHelper.getAppDownloadUrl() |
下载地址属性 - 应用APK/APP安装包下载URL | 确认需要升级时获取下载地址 |
app_size |
ConfigParameterHelper.getAppSize() |
应用安装包大小,用于显示下载进度和预估时间 | 下载过程中显示进度 |
force_update |
ConfigParameterHelper.getBooleanValue() |
强制更新标志,控制是否必须升级 | 版本检查时确定升级策略 |
min_version |
ConfigParameterHelper.getString() |
最低支持版本,低于此版本强制升级 | 版本检查时确定升级策略 |
showmessage |
ConfigParameterHelper.getShowMessage() |
版本更新说明,向用户展示升级内容 | 升级提示对话框显示 |
游戏资源管理的配置属性
| 配置属性 | 读取方法 | 作用说明 | 使用时机 |
|---|---|---|---|
game_version |
ConfigParameterHelper.getGameVersion() |
主要验证属性 - 远程游戏资源版本号,与本地比较判断是否需要更新 | 游戏启动前资源检查 |
game_download |
ConfigParameterHelper.getGameDownloadUrl() |
下载地址属性 - 游戏ZIP包基础下载URL,拼接gameId构成完整下载地址 | 需要下载游戏资源时 |
game_size |
ConfigParameterHelper.getGameSize() |
游戏ZIP包大小,用于下载进度显示 | 游戏资源下载过程中 |
服务器和功能配置属性
| 配置属性 | 读取方法 | 作用说明 | 使用时机 |
|---|---|---|---|
url |
ConfigParameterHelper.getServerUrl() |
服务器地址属性 - API服务器基础URL,传递给游戏用于网络请求 | 游戏启动时传递给WebView |
api_endpoint |
ConfigParameterHelper.getString() |
API端点路径,拼接到服务器地址后 | 构建完整API地址 |
enable_audio |
ConfigParameterHelper.getBoolean() |
音频功能开关,控制游戏是否启用音频 | 游戏初始化时配置 |
enable_vibration |
ConfigParameterHelper.getBoolean() |
震动功能开关,控制游戏是否启用震动反馈 | 游戏初始化时配置 |
关键路径汇总
大厅相关路径
| 路径类型 | 具体路径 | 用途说明 |
|---|---|---|
| 大厅ZIP下载临时路径 | {filesDir}/downloads/hall_temp.zip |
大厅ZIP包下载的临时存储位置 |
| 大厅资源解压目标路径 | {filesDir}/game_resources/hall/ |
大厅ZIP包解压后的资源存放目录 |
| 大厅启动入口路径 | {filesDir}/game_resources/hall/index.html |
大厅WebView加载的HTML入口文件 |
| 大厅WebView加载URL | file://{filesDir}/game_resources/hall/index.html |
大厅WebView实际加载的file://协议URL |
子游戏相关路径
| 路径类型 | 具体路径模板 | 用途说明 |
|---|---|---|
| 子游戏ZIP下载临时路径 | {filesDir}/downloads/{gameId}_temp.zip |
子游戏ZIP包下载的临时存储位置 |
| 子游戏资源解压目标路径 | {filesDir}/game_resources/games/{gameId}/ |
子游戏ZIP包解压后的资源存放目录 |
| 子游戏启动入口路径 | {filesDir}/game_resources/games/{gameId}/index.html |
子游戏WebView加载的HTML入口文件 |
| 子游戏WebView加载URL | file://{filesDir}/game_resources/games/{gameId}/index.html |
子游戏WebView实际加载的file://协议URL |
| 子游戏JS资源路径 | {filesDir}/game_resources/games/{gameId}/js/ |
子游戏JavaScript文件存放目录 |
| 子游戏CSS资源路径 | {filesDir}/game_resources/games/{gameId}/css/ |
子游戏CSS样式文件存放目录 |
| 子游戏图片资源路径 | {filesDir}/game_resources/games/{gameId}/images/ |
子游戏图片资源存放目录 |
| 子游戏音频资源路径 | {filesDir}/game_resources/games/{gameId}/audio/ |
子游戏音频文件存放目录 |
配置和缓存路径
| 路径类型 | 具体路径 | 用途说明 |
|---|---|---|
| 远程配置缓存路径 | {filesDir}/config_cache/remote_config.json |
远程配置文件的本地缓存 |
| 启动配置缓存路径 | {filesDir}/config_cache/startup_config.json |
应用启动配置的本地缓存 |
大厅跳转子游戏的完整流程
第一阶段:接收跳转请求
- 触发方式:大厅WebView中JavaScript调用
window.WebViewJavascriptBridge.callHandler('SwitchOverGameData', {...}) - 参数验证:验证gameId、gameName等必要参数
- 显示加载:显示游戏切换加载界面
第二阶段:资源检查和准备
-
读取配置:
- 读取
game_version属性进行版本比较 - 读取
game_download属性获取下载地址 - 读取
game_size属性用于进度显示
- 读取
-
资源检查:
- 检查游戏目录:
{filesDir}/game_resources/games/{gameId}/ - 检查入口文件:
{filesDir}/game_resources/games/{gameId}/index.html - 读取本地版本:
{filesDir}/game_resources/games/{gameId}/version.txt
- 检查游戏目录:
-
下载解压(如需要):
- 下载到临时路径:
{filesDir}/downloads/{gameId}_temp.zip - 解压到目标路径:
{filesDir}/game_resources/games/{gameId}/ - 清理临时文件
- 下载到临时路径:
第三阶段:WebView切换
- 隐藏大厅:调用大厅WebView的
onPagePause事件 - 初始化游戏WebView:创建或获取gameId对应的WebView实例
- 配置JSBridge:设置游戏专用的JavaScript Bridge接口
- 显示游戏WebView:将游戏WebView设置为可见状态
第四阶段:游戏启动
- 加载游戏页面:WebView加载
file://{filesDir}/game_resources/games/{gameId}/index.html - 传递启动参数:
- 用户信息(userId、token、coins等)
- 服务器配置(从
url属性读取) - 游戏配置(从其他配置属性读取)
- 调用游戏初始化:通过JSBridge调用游戏的
onGameInit接口 - 隐藏加载界面:游戏初始化完成后隐藏加载界面
配置属性读取的层级优先级
TSGame应用的配置读取遵循严格的层级优先级规则:
市场级别配置 (最高优先级)
↓ 覆盖
渠道级别配置
↓ 覆盖
代理商级别配置
↓ 覆盖
全局级别配置 (最低优先级,默认值)
具体的配置路径读取顺序
以 app_version 配置属性为例:
- 第一优先级:
marketlist[{marketid}].app_version - 第二优先级:
channellist[{channelid}].app_version - 第三优先级:
agentlist[{agentid}].app_version - 第四优先级:
data.app_version(全局默认值)
核心特性总结
- 配置驱动启动:所有启动决策基于远程配置文件的具体属性值
- 分层配置管理:支持全局、代理商、渠道、市场四级配置覆盖机制
- 精确路径管理:每个资源都有明确的下载路径、解压路径和启动路径
- 双WebView架构:大厅和子游戏完全独立的WebView实例和资源管理
- 版本检查机制:基于
app_version和game_version的自动版本检测 - 异常降级处理:配置获取失败、资源下载失败等多重降级策略
开发者要点
- 配置属性命名:所有配置属性名称必须与Android版本保持一致
- 路径兼容性:文件路径需要适配HarmonyOS的沙盒文件系统
- 权限申请:文件读写、网络访问等权限需要动态申请
- 性能优化:WebView初始化、资源解压等耗时操作需要异步处理
- 错误处理:每个环节都需要完善的错误处理和用户提示机制
这个启动流程设计确保了TSGame应用在HarmonyOS平台上的高可用性、可维护性和用户体验的一致性。通过精确的配置管理和路径规范,实现了与Android版本完全一致的功能特性。