Files
youle_app_zhuoyitong/TSGame_应用启动流程详解.md
2026-02-16 18:24:19 +08:00

2536 lines
81 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<void> {
// 初始化文件管理器
await FileManager.initialize();
// 初始化网络管理器
await NetworkManager.initialize();
// 初始化配置管理器
await ConfigManager.initialize();
// 初始化资源管理器
await ResourceManager.initialize();
}
}
```
#### 阶段2配置文件检查
```typescript
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结构
```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<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}`);
}
}
```
#### 配置获取的优先级规则
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<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 下载地址获取详解
#### 应用下载地址的具体获取
```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<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解压路径详解
```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<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 大厅跳转子游戏的具体逻辑详解
#### 跳转触发和参数传递
```typescript
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包解压路径规范
#### 路径管理类
```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<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 大厅资源初始化
#### 大厅管理器
```typescript
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管理器
```typescript
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接口调用
```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<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 子游戏启动流程的配置属性读取详解
#### 启动过程中读取的具体配置属性
```typescript
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 子游戏启动路径的完整解析
#### 游戏启动路径的构建和验证
```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<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` | 应用启动配置的本地缓存 |
### 大厅跳转子游戏的完整流程
#### 第一阶段:接收跳转请求
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版本完全一致的功能特性。