# TSGame 应用启动流程详解 ## 目录 1. [概述](#概述) 2. [启动流程时序](#启动流程时序) 3. [远程配置文件管理](#远程配置文件管理) - [3.1 配置文件结构](#31-配置文件结构) - [3.2 配置参数获取逻辑](#32-配置参数获取逻辑) - [3.3 配置属性详解](#33-配置属性详解) - [3.4 应用升级验证详解](#34-应用升级验证详解) - [3.5 下载地址获取详解](#35-下载地址获取详解) - [3.6 解压路径和启动路径详解](#36-解压路径和启动路径详解) - [3.7 大厅跳转子游戏的具体逻辑详解](#37-大厅跳转子游戏的具体逻辑详解) 4. [资源目录和解压路径](#资源目录和解压路径) - [4.1 文件系统结构](#41-文件系统结构) - [4.2 ZIP包解压路径规范](#42-zip包解压路径规范) 5. [大厅启动逻辑](#大厅启动逻辑) - [5.1 大厅资源初始化](#51-大厅资源初始化) - [5.2 大厅WebView启动](#52-大厅webview启动) 6. [子游戏跳转启动逻辑](#子游戏跳转启动逻辑) - [6.1 子游戏资源管理](#61-子游戏资源管理) - [6.2 子游戏WebView切换](#62-子游戏webview切换) - [6.3 子游戏启动流程的配置属性读取详解](#63-子游戏启动流程的配置属性读取详解) - [6.4 子游戏启动路径的完整解析](#64-子游戏启动路径的完整解析) 7. [版本检查和更新流程](#版本检查和更新流程) 8. [启动异常处理](#启动异常处理) --- ## 概述 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:应用初始化 ```typescript 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 { // 初始化文件管理器 await FileManager.initialize(); // 初始化网络管理器 await NetworkManager.initialize(); // 初始化配置管理器 await ConfigManager.initialize(); // 初始化资源管理器 await ResourceManager.initialize(); } } ``` #### 阶段2:配置文件检查 ```typescript class ConfigManager { /** * 启动配置检查流程 */ public async startConfigCheckFlow(): Promise { 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 { 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结构 ```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": "华为应用市场版本" } ] } ] } ] } } ``` #### 配置层级说明 配置文件采用四级层次结构,支持参数覆盖: 1. **全局配置** (data根级别) - 最低优先级 2. **代理商配置** (agentlist) - 覆盖全局配置 3. **渠道配置** (channellist) - 覆盖代理商配置 4. **市场配置** (marketlist) - 最高优先级,覆盖所有上级配置 ### 3.2 配置参数获取逻辑 #### 核心参数获取类 ```typescript /** * 配置参数获取工具类 * 严格按照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,从本地配置或启动参数获取 } ``` #### 配置参数获取示例 ```typescript // 配置参数获取的实际使用示例 export class ConfigUsageExample { public static async demonstrateConfigUsage(): Promise { // 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}`); } } ``` #### 配置获取的优先级规则 1. **全局配置** (`data`根级别):作为默认值,优先级最低 2. **代理商配置** (`agentlist[].`):覆盖全局配置中的同名属性 3. **渠道配置** (`channellist[].`):覆盖代理商和全局配置中的同名属性 4. **市场配置** (`marketlist[].`):最高优先级,覆盖所有上级配置中的同名属性 ### 3.4 应用升级验证详解 #### 升级验证的具体配置属性 TSGame应用的升级验证基于以下配置属性的组合判断: ```typescript interface AppUpgradeValidation { // 主要验证属性 app_version: string; // 远程应用版本号 - 核心验证属性 app_download: string; // 应用下载URL - 升级时获取新版本 app_size: string; // 应用包大小 - 用于下载进度显示 showmessage: string; // 版本更新说明 - 用户升级提示 // 辅助验证属性 force_update?: boolean; // 强制更新标志 - 是否必须升级 min_version?: string; // 最低支持版本 - 低于此版本强制升级 update_priority?: number; // 更新优先级 - 控制更新推送频率 } ``` #### 具体的升级验证逻辑 ```typescript export class AppUpgradeValidator { /** * 验证应用是否需要升级 * 主要读取配置属性:app_version、force_update、min_version */ public static async validateAppUpgrade(config: any, gameData: any): Promise { // 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 下载地址获取详解 #### 应用下载地址的具体获取 ```typescript 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解压路径详解 ```typescript 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 { 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 { 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解压路径详解 ```typescript 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 { 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 { 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 { 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 大厅跳转子游戏的具体逻辑详解 #### 跳转触发和参数传递 ```typescript export class HallGameSwitchController { /** * 大厅跳转子游戏的完整流程 * 这是从大厅点击游戏到子游戏启动的详细逻辑 */ public static async switchFromHallToGame(gameId: string, gameData: any): Promise { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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包解压路径规范 #### 路径管理类 ```typescript 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包解压流程 ```typescript export class ZipExtractor { /** * 解压大厅ZIP包到指定目录 * @param zipFilePath ZIP包文件路径 */ public static async extractHallZip(zipFilePath: string): Promise { 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 { 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 { if (await fileIo.access(dirPath)) { await fileIo.rmdir(dirPath, true); console.log(`已清理目录: ${dirPath}`); } } /** * 执行ZIP包解压 */ private static async extractZipToDirectory(zipPath: string, targetPath: string): Promise { // 使用HarmonyOS的解压API const zipFile = new zlib.ZipFile(zipPath); await zipFile.extractAll(targetPath); console.log(`ZIP包解压完成: ${zipPath} -> ${targetPath}`); } /** * 验证解压后的文件 */ private static async verifyExtractedFiles(dirPath: string): Promise { // 检查必要的文件是否存在 const indexHtmlPath = `${dirPath}/index.html`; if (!await fileIo.access(indexHtmlPath)) { throw new Error(`解压验证失败: 缺少index.html文件`); } console.log(`解压文件验证通过: ${dirPath}`); } } ``` --- ## 大厅启动逻辑 ### 5.1 大厅资源初始化 #### 大厅管理器 ```typescript export class HallManager { /** * 初始化大厅资源 */ public static async initializeHall(): Promise { 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 { 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 { 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 { const zipPath = PathManager.getHallZipTempPath(); // 解压到大厅目录 await ZipExtractor.extractHallZip(zipPath); // 清理临时ZIP文件 await fileIo.unlink(zipPath); console.log('大厅资源解压完成'); } /** * 获取本地大厅版本 */ private static async getLocalHallVersion(): Promise { 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管理器 ```typescript export class HallWebViewController { private webView: WebviewController | null = null; /** * 初始化大厅WebView */ public async initializeHallWebView(): Promise { 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 { 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 { if (!this.webView) return; // 注册所有JS Bridge接口 await JSBridgeManager.registerAllHandlers(this.webView); console.log('JS Bridge接口注册完成'); } /** * 加载大厅HTML页面 */ private async loadHallPage(): Promise { 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接口调用 ```typescript // 在大厅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切换管理器的完整实现 ```typescript export class GameWebViewController { private static hallWebView: WebviewController | null = null; private static gameWebViews: Map = new Map(); private static currentGameId: string = ''; /** * 处理大厅到子游戏的切换 * 这是SwitchOverGameData接口的具体实现 */ public static async handleSwitchOverGameData(params: SwitchGameParams): Promise { 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 { 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 { // 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 { 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 { 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 { 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 { 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 { // 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 { 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 { 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 { 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 { if (this.hallWebView) { // 通知大厅页面即将隐藏 this.hallWebView.callHandler('onPagePause', { reason: 'game_switch' }); // 隐藏大厅WebView UI await UIController.hideHallWebView(); } } /** * 显示游戏WebView */ private static async showGameWebView(gameId: string): Promise { // 显示游戏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 { 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 { 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 子游戏启动流程的配置属性读取详解 #### 启动过程中读取的具体配置属性 ```typescript export class GameStartupConfigReader { /** * 子游戏启动过程中需要读取的所有配置属性 */ public static async readGameStartupConfig(gameId: string): Promise { 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 子游戏启动路径的完整解析 #### 游戏启动路径的构建和验证 ```typescript 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 { 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 { 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` | 应用启动配置的本地缓存 | ### 大厅跳转子游戏的完整流程 #### 第一阶段:接收跳转请求 1. **触发方式**:大厅WebView中JavaScript调用 `window.WebViewJavascriptBridge.callHandler('SwitchOverGameData', {...})` 2. **参数验证**:验证gameId、gameName等必要参数 3. **显示加载**:显示游戏切换加载界面 #### 第二阶段:资源检查和准备 1. **读取配置**: - 读取 `game_version` 属性进行版本比较 - 读取 `game_download` 属性获取下载地址 - 读取 `game_size` 属性用于进度显示 2. **资源检查**: - 检查游戏目录:`{filesDir}/game_resources/games/{gameId}/` - 检查入口文件:`{filesDir}/game_resources/games/{gameId}/index.html` - 读取本地版本:`{filesDir}/game_resources/games/{gameId}/version.txt` 3. **下载解压**(如需要): - 下载到临时路径:`{filesDir}/downloads/{gameId}_temp.zip` - 解压到目标路径:`{filesDir}/game_resources/games/{gameId}/` - 清理临时文件 #### 第三阶段:WebView切换 1. **隐藏大厅**:调用大厅WebView的 `onPagePause` 事件 2. **初始化游戏WebView**:创建或获取gameId对应的WebView实例 3. **配置JSBridge**:设置游戏专用的JavaScript Bridge接口 4. **显示游戏WebView**:将游戏WebView设置为可见状态 #### 第四阶段:游戏启动 1. **加载游戏页面**:WebView加载 `file://{filesDir}/game_resources/games/{gameId}/index.html` 2. **传递启动参数**: - 用户信息(userId、token、coins等) - 服务器配置(从 `url` 属性读取) - 游戏配置(从其他配置属性读取) 3. **调用游戏初始化**:通过JSBridge调用游戏的 `onGameInit` 接口 4. **隐藏加载界面**:游戏初始化完成后隐藏加载界面 ### 配置属性读取的层级优先级 TSGame应用的配置读取遵循严格的层级优先级规则: ``` 市场级别配置 (最高优先级) ↓ 覆盖 渠道级别配置 ↓ 覆盖 代理商级别配置 ↓ 覆盖 全局级别配置 (最低优先级,默认值) ``` #### 具体的配置路径读取顺序 以 `app_version` 配置属性为例: 1. **第一优先级**:`marketlist[{marketid}].app_version` 2. **第二优先级**:`channellist[{channelid}].app_version` 3. **第三优先级**:`agentlist[{agentid}].app_version` 4. **第四优先级**:`data.app_version`(全局默认值) ### 核心特性总结 1. **配置驱动启动**:所有启动决策基于远程配置文件的具体属性值 2. **分层配置管理**:支持全局、代理商、渠道、市场四级配置覆盖机制 3. **精确路径管理**:每个资源都有明确的下载路径、解压路径和启动路径 4. **双WebView架构**:大厅和子游戏完全独立的WebView实例和资源管理 5. **版本检查机制**:基于 `app_version` 和 `game_version` 的自动版本检测 6. **异常降级处理**:配置获取失败、资源下载失败等多重降级策略 ### 开发者要点 1. **配置属性命名**:所有配置属性名称必须与Android版本保持一致 2. **路径兼容性**:文件路径需要适配HarmonyOS的沙盒文件系统 3. **权限申请**:文件读写、网络访问等权限需要动态申请 4. **性能优化**:WebView初始化、资源解压等耗时操作需要异步处理 5. **错误处理**:每个环节都需要完善的错误处理和用户提示机制 这个启动流程设计确保了TSGame应用在HarmonyOS平台上的高可用性、可维护性和用户体验的一致性。通过精确的配置管理和路径规范,实现了与Android版本完全一致的功能特性。