commit c458f3c70710b6a4d4fae033a499592b74996a3a
Author: Joywayer
Date: Mon Feb 16 18:24:19 2026 +0800
first commit
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0691144
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,87 @@
+# Mac 忽略文件
+.DS_Store
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..ae78c11
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..61a9130
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..0c0c338
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..5959e0b
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..c2bae49
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..e34606c
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..be6fe31
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/HarmonyOS_官方资源快速参考.md b/HarmonyOS_官方资源快速参考.md
new file mode 100644
index 0000000..671a031
--- /dev/null
+++ b/HarmonyOS_官方资源快速参考.md
@@ -0,0 +1,236 @@
+# HarmonyOS 5.0 官方开发者资源快速参考手册
+
+## 🎯 使用说明
+
+本文档为"进贤聚友棋牌 (HarmonyOS版)"项目提供官方开发者资源的快速索引和使用指导。**开发过程中必须优先参考官方文档**,本文档仅作为导航和补充。
+
+---
+
+## 📚 一、核心官方文档(优先级排序)
+
+### ⭐⭐⭐⭐⭐ 最高优先级 - 日常开发必备
+
+#### 1. **HarmonyOS API参考文档**
+- **官方链接**: https://developer.huawei.com/consumer/cn/doc/harmonyos-references/development-intro-api
+- **使用场景**: 所有API调用前必须查阅
+- **重点关注**:
+ - Web组件API: `ts-basic-components-web`
+ - WebviewController: `js-apis-webview`
+ - 文件管理: `js-apis-file-fs`
+ - 网络请求: `js-apis-http`
+- **开发建议**: 收藏并设为浏览器首页,开发过程中随时查阅
+
+#### 2. **HarmonyOS应用开发指南**
+- **官方链接**: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-dev-guide
+- **使用场景**: 项目架构设计、功能实现方案设计
+- **重点关注**:
+ - 应用工程结构: `application-project-structure`
+ - WebView开发: `webview-js-interaction`
+ - 数据管理: `data-mgmt-overview`
+ - 网络管理: `network-connection-overview`
+- **开发建议**: 项目启动前通读相关章节,设计阶段重点参考
+
+### ⭐⭐⭐⭐ 高优先级 - 技术决策参考
+
+#### 3. **HarmonyOS最佳实践**
+- **官方链接**: https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-best-practices-overview
+- **使用场景**: 架构设计、性能优化、代码质量提升
+- **重点关注**:
+ - WebView性能优化: `bpta-webview-performance`
+ - 网络请求优化: `bpta-network-request`
+ - 内存管理: `bpta-memory-management`
+ - ArkTS开发规范: `bpta-arkts-guidelines`
+- **开发建议**: 每个功能模块设计前查阅相关最佳实践
+
+#### 4. **HarmonyOS版本发布说明**
+- **官方链接**: https://developer.huawei.com/consumer/cn/doc/harmonyos-releases/overview-allversion
+- **使用场景**: 版本兼容性确认、API变更跟踪
+- **重点关注**:
+ - API 12新特性和变更
+ - 兼容性要求
+ - 废弃API列表
+- **开发建议**: 项目开始前确认目标版本,定期检查更新
+
+### ⭐⭐⭐ 中等优先级 - 问题解决工具
+
+#### 5. **HarmonyOS开发FAQ**
+- **官方链接**: https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ux-design
+- **使用场景**: 遇到问题时的第一参考资料
+- **重点关注**:
+ - WebView相关FAQ
+ - ArkTS编译错误FAQ
+ - 性能问题FAQ
+- **开发建议**: 遇到问题先查FAQ,再搜索或提问
+
+---
+
+## 🛠️ 二、开发工具官方资源
+
+### DevEco Studio
+- **官方下载**: https://developer.huawei.com/consumer/cn/deveco-studio/
+- **文档**: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-guide
+- **版本要求**: 支持HarmonyOS 5.0的最新版本
+- **关键功能**: 代码编辑、调试、模拟器、性能分析
+
+### SDK管理
+- **官方指南**: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-sdk-manager
+- **版本选择**: API 12+ for HarmonyOS 5.0
+- **更新策略**: 定期检查更新,关注兼容性变更
+
+---
+
+## 🎯 三、项目关键功能的官方资源映射
+
+### WebView与JSBridge开发
+```
+功能需求 → 官方资源
+├── WebView基础配置 → Web组件API参考
+├── JSBridge通信 → JavaScript与应用交互指南
+├── WebView控制器 → WebviewController API参考
+├── 性能优化 → WebView性能优化最佳实践
+└── 问题排查 → WebView FAQ专区
+```
+
+### 资源管理与文件操作
+```
+功能需求 → 官方资源
+├── 文件读写 → 文件管理API参考
+├── 网络下载 → 网络请求API + 最佳实践
+├── 资源解压 → 文件系统API + ZIP处理示例
+├── 缓存管理 → 数据管理指南
+└── 权限申请 → 权限管理指南
+```
+
+### 应用架构与状态管理
+```
+功能需求 → 官方资源
+├── 页面导航 → 页面路由开发指南
+├── 状态管理 → ArkUI状态管理
+├── 组件通信 → 组件化开发指南
+├── 生命周期 → 应用/组件生命周期
+└── 数据持久化 → 数据管理overview
+```
+
+---
+
+## 📋 四、开发阶段的官方资源使用计划
+
+### 第一阶段:环境搭建
+**主要参考**:
+1. DevEco Studio安装指南
+2. SDK管理文档
+3. 项目创建向导
+4. 开发环境配置FAQ
+
+**使用策略**: 严格按照官方指南操作,遇到问题优先查阅FAQ
+
+### 第二阶段:核心功能开发
+**主要参考**:
+1. API参考文档(日常查阅)
+2. 开发指南(设计参考)
+3. 最佳实践(质量保证)
+4. 示例代码(快速上手)
+
+**使用策略**: API文档作为编码标准,最佳实践指导架构设计
+
+### 第三阶段:功能完善
+**主要参考**:
+1. 性能优化最佳实践
+2. 用户体验设计指南
+3. 兼容性测试指南
+4. 错误处理最佳实践
+
+**使用策略**: 对照最佳实践优化代码质量和用户体验
+
+### 第四阶段:测试与优化
+**主要参考**:
+1. 测试框架文档
+2. 性能分析工具指南
+3. 调试技巧文档
+4. 发布前检查清单
+
+**使用策略**: 使用官方工具进行全面测试和性能优化
+
+---
+
+## 🔍 五、快速查找指南
+
+### 常见问题快速定位
+
+#### WebView相关问题
+```
+问题类型 → 查找路径
+├── 类型错误 → API参考文档 → WebviewController
+├── 通信失败 → 开发指南 → JavaScript交互
+├── 性能问题 → 最佳实践 → WebView性能优化
+├── 兼容性 → 版本说明 → API变更列表
+└── 其他 → FAQ → WebView专区
+```
+
+#### ArkTS编译错误
+```
+问题类型 → 查找路径
+├── 语法错误 → 开发指南 → ArkTS语法
+├── 类型错误 → API参考 → 具体API类型定义
+├── 装饰器错误 → 开发指南 → 状态管理
+├── 导入错误 → 开发指南 → 模块化开发
+└── 其他 → FAQ → 编译错误专区
+```
+
+### 搜索关键词建议
+
+#### 中文搜索
+- "HarmonyOS WebView"
+- "ArkTS WebviewController"
+- "HarmonyOS JSBridge"
+- "HarmonyOS 文件管理"
+- "ArkTS 类型检查"
+
+#### 英文搜索
+- "HarmonyOS Web Component"
+- "ArkTS WebviewController API"
+- "HarmonyOS JavaScript Proxy"
+- "HarmonyOS File System"
+- "ArkTS Type System"
+
+---
+
+## ⚠️ 重要提醒
+
+### 优先级原则
+1. **官方文档 > 第三方资料**:当存在冲突时,以官方文档为准
+2. **API参考 > 示例代码**:编码时优先查阅API文档确保参数正确
+3. **最新版本 > 历史版本**:确保使用与目标版本一致的文档
+
+### 版本一致性
+- 确保查阅的文档版本与项目目标版本(HarmonyOS 5.0 API 12+)一致
+- 关注API变更和废弃通知
+- 定期检查文档更新
+
+### 问题反馈
+- 通过官方开发者社区反馈技术问题
+- 利用DevEco Studio内置的问题报告功能
+- 参与官方技术交流群获得及时支持
+
+---
+
+## 📞 官方支持渠道
+
+### 开发者社区
+- **HarmonyOS开发者论坛**: https://developer.huawei.com/consumer/cn/forum/block/harmonyos
+- **用途**: 技术讨论、问题求助、经验分享
+
+### 官方技术支持
+- **在线客服**: DevEco Studio → Help → Contact Support
+- **用途**: 紧急技术问题、工具bug反馈
+
+### 官方培训资源
+- **在线课程**: https://developer.huawei.com/consumer/cn/training/
+- **用途**: 系统学习HarmonyOS开发技能
+
+---
+
+**最后更新**: 2024年12月19日
+**文档版本**: v1.0
+**适用项目**: 进贤聚友棋牌 (HarmonyOS版)
+**目标平台**: HarmonyOS 5.0 (API 12+)
diff --git a/TSGame_HarmonyOS开发指南.md b/TSGame_HarmonyOS开发指南.md
new file mode 100644
index 0000000..58d26e3
--- /dev/null
+++ b/TSGame_HarmonyOS开发指南.md
@@ -0,0 +1,1529 @@
+# TSGame HarmonyOS应用开发指南
+
+## 项目概述
+
+本文档提供了基于HarmonyOS平台开发TSGame棋牌游戏应用的完整技术指南,包括架构设计、开发环境搭建、核心功能实现、JS Bridge接口开发等详细内容。
+
+### HarmonyOS 5.0新特性适配
+
+本项目充分利用HarmonyOS 5.0的新特性和性能优化:
+
+#### 1. 增强的WebView引擎
+- **性能提升**: 利用5.0优化的WebView渲染引擎,提高H5游戏运行性能
+- **内存优化**: 改进的内存管理机制,减少WebView内存占用
+- **启动加速**: WebView初始化和页面加载速度显著提升
+
+#### 2. 新的权限管理机制
+- **精细化权限**: 支持5.0的精细化权限控制
+- **动态权限**: 改进的运行时权限申请机制
+- **隐私保护**: 增强的用户隐私保护特性
+
+#### 3. 文件系统API改进
+- **沙盒机制**: 适配5.0的新沙盒文件访问机制
+- **存储优化**: 利用改进的文件存储API提高I/O性能
+- **安全增强**: 文件访问安全性提升
+
+#### 4. 应用启动优化
+- **冷启动**: 利用5.0的应用预加载机制
+- **热启动**: 优化的应用恢复机制
+- **资源预加载**: 智能资源预加载策略
+
+#### 5. 向下兼容性
+- **API适配层**: 建立兼容性适配层支持HarmonyOS 4.0
+- **特性检测**: 运行时检测系统版本并启用对应特性
+- **降级策略**: 为不支持的特性提供降级方案
+
+### 应用基本信息
+- **应用名称**: 进贤聚友棋牌 (HarmonyOS版)
+- **Bundle名称**: `com.jx.jyhd.harmonyos`
+- **目标API**: HarmonyOS API 12+ (支持HarmonyOS 5.0)
+- **最低支持**: HarmonyOS API 11 (HarmonyOS 4.0)
+- **开发语言**: ArkTS/TypeScript
+- **应用模型**: Stage模型
+- **项目周期**: 8-10周
+- **团队规模**: 3-5人
+
+## 1. 程序设计框架
+
+### 1.1 整体架构设计
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ H5游戏前端层 │
+│ (HTML5 + CSS3 + JavaScript) │
+│ - 游戏逻辑实现 │
+│ - UI界面展示 │
+│ - 用户交互处理 │
+└─────────────────────────────────────────────────────────┘
+ │
+ JavaScript Bridge API
+ │
+┌─────────────────────────────────────────────────────────┐
+│ HarmonyOS Native层 │
+│ │
+│ ┌─────────────┬─────────────┬─────────────────────┐ │
+│ │ AbilityKit │ WebView组件 │ 系统服务集成 │ │
+│ │ 生命周期 │ JS Bridge │ - 位置服务 │ │
+│ │ 页面管理 │ 通信管理 │ - 多媒体服务 │ │
+│ │ 权限管理 │ 事件处理 │ - 网络服务 │ │
+│ └─────────────┴─────────────┴─────────────────────┘ │
+│ │
+│ ┌─────────────┬─────────────┬─────────────────────┐ │
+│ │ 第三方SDK集成│ 数据存储 │ 平台特性适配 │ │
+│ │ - 华为地图 │ - 本地存储 │ - 华为账号 │ │
+│ │ - 原生音频 │ - 文件管理 │ - 华为分享 │ │
+│ │ - 华为分析 │ - 配置管理 │ - HarmonyOS规范 │ │
+│ └─────────────┴─────────────┴─────────────────────┘ │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 1.2 双WebView架构设计
+
+TSGame应用采用双WebView架构,实现大厅和子游戏的分离管理:
+
+```
+应用启动流程:
+启动App → 加载远程配置 → 下载大厅ZIP包 → 解压大厅资源 → 显示大厅WebView
+ ↓
+用户点击子游戏 → 验证子游戏ZIP包 → 下载子游戏ZIP → 解压子游戏资源 → 显示子游戏WebView
+```
+
+#### 1.2.1 表现层 (UI Layer)
+```typescript
+// 双WebView UI组件层次结构
+EntryAbility
+├── MainPage (应用主页面)
+│ ├── HallWebView (大厅WebView)
+│ │ ├── HallWebComponent (大厅Web组件)
+│ │ ├── HallLoadingComponent (大厅加载组件)
+│ │ └── HallErrorComponent (大厅错误处理)
+│ ├── GameWebView (子游戏WebView)
+│ │ ├── GameWebComponent (子游戏Web组件)
+│ │ ├── GameLoadingComponent (子游戏加载组件)
+│ │ └── GameErrorComponent (子游戏错误处理)
+│ ├── NavigationController (WebView切换控制器)
+│ ├── DownloadProgressView (下载进度显示)
+│ └── GlobalLoadingView (全局加载组件)
+└── SettingsPage (设置页面)
+ ├── PermissionSettings (权限设置)
+ ├── AboutPage (关于页面)
+ └── VersionCheck (版本检查)
+```
+
+#### 1.2.2 业务逻辑层 (Business Layer)
+```typescript
+// 核心业务模块 - 双WebView架构
+├── HallManager (大厅管理器)
+│ ├── HallLauncher (大厅启动器)
+│ ├── HallResourceManager (大厅资源管理器)
+│ └── HallConfigManager (大厅配置管理器)
+├── GameManager (子游戏管理器)
+│ ├── GameLauncher (子游戏启动器)
+│ ├── GameResourceManager (子游戏资源管理器)
+│ ├── GameValidation (子游戏验证器)
+│ └── GamePackageManager (子游戏包管理器)
+├── WebViewManager (WebView管理器)
+│ ├── HallWebViewController (大厅WebView控制器)
+│ ├── GameWebViewController (子游戏WebView控制器)
+│ └── WebViewSwitcher (WebView切换器)
+├── BridgeManager (桥接管理器)
+│ ├── JSBridgeService (JS桥接服务)
+│ ├── APIDispatcher (API分发器)
+│ └── MessageHandler (消息处理器)
+├── ResourceManager (资源管理器)
+│ ├── ZipDownloader (ZIP下载器)
+│ ├── ZipExtractor (ZIP解压器)
+│ ├── ResourceValidator (资源验证器)
+│ └── CacheManager (缓存管理器)
+├── UserManager (用户管理器)
+│ ├── AuthService (认证服务)
+│ ├── ProfileManager (用户档案管理)
+│ └── SessionManager (会话管理)
+└── MediaManager (多媒体管理器)
+ ├── AudioService (音频服务)
+ ├── VideoService (视频服务)
+ └── CameraService (相机服务)
+```
+
+#### 1.2.3 数据访问层 (Data Layer)
+```typescript
+// 数据管理模块
+├── LocalStorage (本地存储)
+│ ├── PreferenceUtil (首选项工具)
+│ ├── FileManager (文件管理)
+│ └── DatabaseManager (数据库管理)
+├── NetworkService (网络服务)
+│ ├── HttpClient (HTTP客户端)
+│ ├── DownloadManager (下载管理器)
+│ └── UploadManager (上传管理器)
+└── CacheManager (缓存管理)
+ ├── ImageCache (图片缓存)
+ ├── DataCache (数据缓存)
+ └── WebCache (Web缓存)
+```
+
+### 1.3 模块依赖关系
+
+```typescript
+// 双WebView架构模块依赖图
+HallManager
+├── 依赖 → ResourceManager (ZIP下载解压)
+├── 依赖 → ConfigManager (配置管理)
+├── 依赖 → WebViewManager (大厅WebView控制)
+└── 依赖 → NetworkService (网络服务)
+
+GameManager
+├── 依赖 → ResourceManager (子游戏ZIP管理)
+├── 依赖 → WebViewManager (子游戏WebView控制)
+├── 依赖 → BridgeManager (JS桥接)
+└── 依赖 → ValidationService (ZIP包验证)
+
+WebViewManager
+├── 依赖 → BridgeManager (JS Bridge注册)
+├── 依赖 → MediaManager (多媒体功能)
+├── 依赖 → LocationService (定位服务)
+└── 依赖 → UserManager (用户管理)
+
+ResourceManager
+├── 依赖 → NetworkService (ZIP下载)
+├── 依赖 → FileManager (文件解压)
+├── 依赖 → CacheManager (资源缓存)
+└── 依赖 → ValidationService (完整性验证)
+
+BridgeManager
+├── 依赖 → MediaManager (音视频通话)
+├── 依赖 → LocationService (定位功能)
+├── 依赖 → UserManager (认证登录)
+└── 依赖 → SystemService (系统功能)
+```
+
+## 2. 项目目录结构设计
+
+### 2.1 标准HarmonyOS项目结构 (双WebView架构)
+```
+tsgame_harmonyos/
+├── AppScope/ # 应用级配置
+│ └── app.json5 # 应用配置文件
+├── entry/ # 主模块
+│ ├── src/main/
+│ │ ├── ets/ # ArkTS源码
+│ │ │ ├── entryability/ # Ability入口
+│ │ │ │ └── EntryAbility.ts
+│ │ │ ├── pages/ # 页面文件
+│ │ │ │ ├── MainPage.ets # 主页面(双WebView容器)
+│ │ │ │ └── SettingsPage.ets # 设置页面
+│ │ │ ├── common/ # 通用工具类
+│ │ │ │ ├── constants/ # 常量定义
+│ │ │ │ │ ├── AppConstants.ts # 应用常量
+│ │ │ │ │ └── WebViewConstants.ts # WebView常量
+│ │ │ │ ├── utils/ # 工具类
+│ │ │ │ │ ├── ConfigParameterHelper.ts # 配置参数工具
+│ │ │ │ │ ├── ZipExtractor.ts # ZIP解压工具
+│ │ │ │ │ └── FileUtils.ts # 文件工具
+│ │ │ │ └── types/ # 类型定义
+│ │ │ │ ├── GameTypes.ts # 游戏类型定义
+│ │ │ │ ├── ConfigTypes.ts # 配置类型定义
+│ │ │ │ └── WebViewTypes.ts # WebView类型定义
+│ │ │ ├── manager/ # 管理器类
+│ │ │ │ ├── HallManager.ts # 大厅管理器
+│ │ │ │ ├── GameManager.ts # 子游戏管理器
+│ │ │ │ ├── WebViewManager.ts # WebView管理器
+│ │ │ │ ├── BridgeManager.ts # 桥接管理器
+│ │ │ │ ├── ResourceManager.ts # 资源管理器
+│ │ │ │ ├── MediaManager.ts # 多媒体管理器
+│ │ │ │ ├── UserManager.ts # 用户管理器
+│ │ │ │ └── StartupManager.ts # 启动管理器
+│ │ │ ├── service/ # 服务类
+│ │ │ │ ├── AuthService.ts # 认证服务
+│ │ │ │ ├── LocationService.ts # 定位服务
+│ │ │ │ ├── NetworkService.ts # 网络服务
+│ │ │ │ ├── MediaService.ts # 媒体服务
+│ │ │ │ ├── ConfigManager.ts # 配置管理服务
+│ │ │ │ └── DownloadService.ts # 下载服务
+│ │ │ ├── bridge/ # JS桥接实现
+│ │ │ │ ├── JSBridgeService.ts # 桥接核心服务
+│ │ │ │ ├── handlers/ # API处理器
+│ │ │ │ │ ├── AuthHandler.ts # 认证处理器
+│ │ │ │ │ ├── MediaHandler.ts# 媒体处理器
+│ │ │ │ │ ├── SystemHandler.ts# 系统处理器
+│ │ │ │ │ ├── LocationHandler.ts# 定位处理器
+│ │ │ │ │ ├── HallHandler.ts # 大厅处理器
+│ │ │ │ │ └── GameHandler.ts # 游戏处理器
+│ │ │ │ └── types/ # 桥接类型定义
+│ │ │ │ └── BridgeTypes.ts
+│ │ │ └── components/ # 自定义组件
+│ │ │ ├── HallWebComponent.ets # 大厅Web组件
+│ │ │ ├── GameWebComponent.ets # 子游戏Web组件
+│ │ │ ├── LoadingComponent.ets # 加载组件
+│ │ │ ├── ProgressComponent.ets # 进度组件
+│ │ │ └── ErrorComponent.ets # 错误组件
+│ │ ├── resources/ # 资源文件
+│ │ │ ├── base/ # 基础资源
+│ │ │ │ ├── element/ # 元素资源
+│ │ │ │ ├── media/ # 媒体资源
+│ │ │ │ └── profile/ # 配置文件
+│ │ │ │ ├── main_pages.json # 页面路由配置
+│ │ │ │ └── startup_config.json # 启动配置
+│ │ │ ├── rawfile/ # 原始文件
+│ │ │ │ ├── default_hall/ # 默认大厅资源
+│ │ │ │ │ ├── index.html # 大厅入口页面
+│ │ │ │ │ ├── js/ # JavaScript文件
+│ │ │ │ │ ├── css/ # 样式文件
+│ │ │ │ │ └── images/ # 图片资源
+│ │ │ │ ├── default_games/ # 默认游戏资源
+│ │ │ │ │ └── placeholder/ # 占位游戏
+│ │ │ │ └── bridge/ # 桥接相关
+│ │ │ │ ├── hall_bridge.js # 大厅桥接JS
+│ │ │ │ └── game_bridge.js # 游戏桥接JS
+│ │ │ └── en_US/ # 英文资源
+│ │ └── module.json5 # 模块配置文件
+│ ├── build-profile.json5 # 构建配置
+│ ├── hvigorfile.ts # 构建脚本
+│ └── oh-package.json5 # 依赖配置
+├── har/ # HAR模块(可选)
+│ └── common_lib/ # 通用库模块
+├── libs/ # 第三方库
+│ ├── huawei-map-sdk/ # 华为地图SDK
+│ └── wechat-harmonyos-sdk/ # 微信SDK(如有)
+├── docs/ # 文档目录
+│ ├── API_REFERENCE.md # API参考文档
+│ ├── DEPLOYMENT_GUIDE.md # 部署指南
+│ └── TROUBLESHOOTING.md # 故障排除
+├── build-profile.json5 # 项目构建配置
+├── hvigorfile.ts # 项目构建脚本
+└── oh-package.json5 # 项目依赖配置
+```
+
+### 2.2 运行时资源目录结构
+
+应用运行时会在设备上创建以下目录结构来管理动态下载的游戏资源:
+
+```
+/data/app/el2/100/base/com.jx.jyhd.harmonyos/haps/entry/files/
+├── game_resources/ # 游戏资源根目录
+│ ├── hall/ # 大厅资源目录
+│ │ ├── index.html # 大厅入口文件
+│ │ ├── js/ # 大厅JS文件
+│ │ ├── css/ # 大厅样式文件
+│ │ ├── images/ # 大厅图片资源
+│ │ └── version.txt # 大厅版本信息
+│ ├── games/ # 子游戏资源目录
+│ │ ├── game001/ # 斗地主游戏
+│ │ │ ├── index.html # 游戏入口文件
+│ │ │ ├── js/ # 游戏JS文件
+│ │ │ ├── css/ # 游戏样式文件
+│ │ │ ├── images/ # 游戏图片资源
+│ │ │ ├── audio/ # 游戏音频资源
+│ │ │ └── version.txt # 游戏版本信息
+│ │ ├── game002/ # 麻将游戏
+│ │ │ ├── index.html
+│ │ │ ├── js/
+│ │ │ ├── css/
+│ │ │ ├── images/
+│ │ │ ├── audio/
+│ │ │ └── version.txt
+│ │ └── ... # 其他子游戏
+│ ├── 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 # 错误日志
+```
+
+### 2.2 关键配置文件说明
+
+#### 2.2.1 应用配置 (app.json5)
+```json5
+{
+ "app": {
+ "bundleName": "com.jx.jyhd.harmonyos",
+ "vendor": "YouleGames",
+ "versionCode": 1000360,
+ "versionName": "3.6.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name",
+ "description": "$string:app_description",
+ "distributedNotificationEnabled": true,
+ "targetAPIVersion": 9,
+ "compatibleAPIVersion": 8
+ }
+}
+```
+
+#### 2.2.2 模块配置 (module.json5)
+```json5
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "phone",
+ "tablet",
+ "2in1"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ts",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {
+ "name": "ohos.permission.INTERNET",
+ "reason": "$string:permission_internet_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "always"
+ }
+ },
+ {
+ "name": "ohos.permission.LOCATION",
+ "reason": "$string:permission_location_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.APPROXIMATELY_LOCATION",
+ "reason": "$string:permission_location_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.CAMERA",
+ "reason": "$string:permission_camera_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.MICROPHONE",
+ "reason": "$string:permission_microphone_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.WRITE_MEDIA",
+ "reason": "$string:permission_storage_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.READ_MEDIA",
+ "reason": "$string:permission_storage_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.VIBRATE",
+ "reason": "$string:permission_vibrate_reason",
+ "usedScene": {
+ "abilities": ["EntryAbility"],
+ "when": "inuse"
+ }
+ }
+ ]
+ }
+}
+```
+
+## 2. 应用启动流程设计
+
+TSGame HarmonyOS应用采用分阶段启动流程,通过远程配置文件驱动,支持大厅和子游戏的动态资源管理。由于启动流程涉及多个复杂的组件和详细的配置参数,已将完整的启动逻辑分离到独立文档中。
+
+### 2.1 启动流程文档
+
+**📖 详细启动流程说明请参考:[TSGame_应用启动流程详解.md](./TSGame_应用启动流程详解.md)**
+
+该文档包含以下完整内容:
+
+#### 2.1.1 核心启动流程
+- **启动时序图**:从应用启动到大厅显示的完整流程
+- **阶段化启动**:EntryAbility.onCreate() → 配置检查 → 资源下载 → WebView初始化
+- **大厅启动逻辑**:大厅资源管理、WebView配置、页面加载
+- **子游戏跳转**:游戏资源检查、下载解压、WebView切换
+
+#### 2.1.2 远程配置文件管理
+- **配置文件结构**:JSON格式的分层配置,存储在七牛云CDN
+- **配置参数获取**:ConfigParameterHelper类的详细实现
+- **属性读取逻辑**:具体到每个配置属性的获取方法
+- **分层覆盖机制**:全局 → 代理商 → 渠道 → 市场的参数覆盖
+
+#### 2.1.3 关键配置属性说明
+- `app_download`:应用下载地址,用于应用更新
+- `app_version`:应用版本号,用于版本比较
+- `game_download`:游戏ZIP包下载地址,用于游戏资源更新
+- `game_version`:游戏版本号,用于资源版本比较
+- `url`:API服务器地址,用于网络请求
+- `showmessage`:版本更新说明,用于用户提示
+
+#### 2.1.4 ZIP包解压路径规范
+- **文件系统结构**:HarmonyOS应用的完整目录布局
+- **解压路径管理**:PathManager类的路径规范
+- **资源目录说明**:大厅、子游戏、缓存、配置等目录结构
+
+#### 2.1.5 版本检查和更新
+- **版本检测流程**:启动时的版本比较逻辑
+- **更新策略**:应用更新vs资源更新的区别
+- **异常处理**:配置获取失败、下载失败等异常的降级处理
+
+### 2.2 HarmonyOS适配要点
+
+#### 2.2.1 启动性能优化
+- 利用HarmonyOS 5.0的应用预加载机制
+- 异步加载非关键资源
+- WebView初始化优化
+
+#### 2.2.2 资源管理适配
+- 适配HarmonyOS的沙盒文件系统
+- 文件权限管理
+- 存储空间优化
+
+#### 2.2.3 网络请求优化
+- 网络状态检测和适配
+- CDN加速配置
+- 下载断点续传支持
+
+## 3. JavaScript与App双向调用接口
+
+TSGame应用通过JS Bridge实现WebView(H5游戏)与原生Android/HarmonyOS应用之间的双向通信。由于接口数量众多且参数复杂,已将所有JavaScript与App双向调用的接口详细说明分离到独立文档中。
+
+### 3.1 接口详解文档
+
+**📖 详细接口说明请参考:[TSGame_JSBridge_接口详解.md](./TSGame_JSBridge_接口详解.md)**
+
+该文档包含以下完整内容:
+
+#### 3.1.1 WebView调用App接口
+- **游戏相关接口**:SwitchOverGameData、getGameinstall、downloadGame等
+- **用户信息接口**:getUserInfo、updateUserCoins等
+- **分享接口**:friendsSharetypeUrlToptitleDescript、getSharePlatforms等
+- **媒体播放接口**:mediaTypeAudio、mediaTypeVideo等
+- **设备信息接口**:getDeviceInfo、getPhoneInfo等
+- **网络请求接口**:httpRequest等
+- **系统功能接口**:showToast、showDialog、openUrl等
+
+#### 3.1.2 App调用WebView接口
+- **页面生命周期**:onPageResume、onPagePause、onPageDestroy
+- **网络状态通知**:onNetworkChanged
+- **数据更新通知**:onUserDataChanged
+
+#### 3.1.3 数据结构定义
+- **基础数据结构**:BaseResponse、Position、Size等
+- **游戏数据结构**:GameInfo、GameProgress等
+- **分享数据结构**:ShareConfig、ShareResult等
+- **媒体数据结构**:AudioInfo、VideoInfo等
+
+#### 3.1.4 完整的接口参考
+- 每个接口的作用、调用时机、参数结构、字段说明
+- TypeScript类型定义和JSON数据格式
+- 实际使用示例和错误处理
+- 错误码定义和注意事项
+
+### 3.2 HarmonyOS适配要点
+
+基于Android工程webviewActivity.java和NewwebviewActivity.java代码完整分析,HarmonyOS版本的JS Bridge实现需要注意以下几点:
+
+#### 3.2.1 接口兼容性
+- 所有接口方法名、参数格式与Android工程代码保持完全一致
+- 保持与现有H5游戏的无缝兼容
+- 接口调用方式和数据格式保持不变
+
+#### 3.2.2 权限适配
+- 相机、定位、通讯录等接口需要动态申请权限
+- 适配HarmonyOS 5.0的精细化权限控制
+- 实现权限申请失败的降级处理
+
+#### 3.2.3 性能优化
+- 避免频繁调用Bridge接口
+- 大量数据传输考虑分批处理
+- 适当使用本地缓存减少接口调用
+
+#### 3.2.4 异常处理
+- 所有Bridge调用都是异步的,需要适当的超时处理
+- 网络中断时部分接口可能失效
+- 提供友好的错误提示和重试机制
+
+## 4. 总结
+
+TSGame HarmonyOS版本通过完整的JavaScript Bridge接口体系,实现了WebView与原生应用的深度集成。本文档详细说明了所有40多个双向接口的用法、参数格式和调用时机,为HarmonyOS版本的开发提供了完整的技术参考。
+
+所有接口的方法名、参数格式均与Android工程保持完全一致,确保了跨平台的兼容性和一致性。开发者可以基于这些接口实现丰富的原生功能集成。
+
+## 8. 应用和游戏更新机制
+
+TSGame支持应用APK/APP和游戏ZIP资源的自动更新机制,确保用户始终使用最新版本的应用和游戏内容。HarmonyOS版本需要完全实现与Android一致的更新逻辑。
+
+### 8.1 更新机制概述
+
+#### 8.1.1 更新类型
+
+**应用更新(APP级别):**
+- 更新应用本身的代码和核心功能
+- 涉及应用包(.hap文件)的下载和安装
+- 需要用户确认和重启应用
+- 对应Android的APK更新
+
+**游戏更新(资源级别):**
+- 更新游戏资源文件(HTML、CSS、JS、图片等)
+- 涉及ZIP包的下载、解压和替换
+- 支持静默更新,无需用户干预
+- 不需要重启应用,热更新生效
+
+#### 8.1.2 版本控制策略
+
+更新机制采用分层配置的版本控制策略:
+
+```typescript
+interface VersionConfig {
+ // 应用版本信息
+ app_version: string; // 应用版本号,如"3.6.3"
+ app_download: string; // 应用下载URL
+ app_size: string; // 应用包大小
+
+ // 游戏版本信息
+ game_version: string; // 游戏版本号,如"2.1.0"
+ game_download: string; // 游戏ZIP包下载URL
+ game_size: string; // 游戏包大小
+
+ // 显示信息
+ showmessage: string; // 版本更新说明文本
+}
+```
+
+### 8.2 版本检测流程
+
+#### 8.2.1 启动时版本检测
+
+```typescript
+class VersionChecker {
+
+ /**
+ * 启动时执行版本检测流程
+ */
+ public async checkVersionOnLaunch(): Promise {
+ try {
+ // 1. 获取本地版本信息
+ const localVersions = await this.getLocalVersionInfo();
+
+ // 2. 获取服务器版本配置
+ const serverConfig = await this.fetchServerVersionConfig();
+
+ // 3. 比较版本并决定更新策略
+ const updatePlan = this.compareVersions(localVersions, serverConfig);
+
+ // 4. 执行相应的更新流程
+ await this.executeUpdatePlan(updatePlan);
+
+ } catch (error) {
+ console.error('版本检测失败:', error);
+ // 失败时继续启动,使用本地版本
+ this.startGameWithLocalVersion();
+ }
+ }
+
+ /**
+ * 获取本地版本信息
+ */
+ private async getLocalVersionInfo(): Promise {
+ const preferences = dataPreferences.getPreferences(getContext(), 'version_prefs');
+
+ return {
+ appVersion: await preferences.get('app_version', ''),
+ gameVersion: await preferences.get('game_version', ''),
+ gameId: await preferences.get('game_id', ''),
+ lastUpdateTime: await preferences.get('last_update_time', 0)
+ };
+ }
+
+ /**
+ * 获取服务器版本配置
+ */
+ private async fetchServerVersionConfig(): Promise {
+ const gameData = this.getCurrentGameData();
+ const configUrl = this.buildConfigUrl(gameData);
+
+ const response = await fetch(configUrl + '?t=' + Date.now());
+ if (!response.ok) {
+ throw new Error(`配置获取失败: ${response.status}`);
+ }
+
+ return response.json();
+ }
+
+ /**
+ * 比较版本并生成更新计划
+ */
+ private compareVersions(local: LocalVersionInfo, server: VersionConfig): UpdatePlan {
+ const plan: UpdatePlan = {
+ needAppUpdate: false,
+ needGameUpdate: false,
+ appUpdateInfo: null,
+ gameUpdateInfo: null
+ };
+
+ // 比较应用版本
+ if (this.isVersionNewer(server.app_version, local.appVersion)) {
+ plan.needAppUpdate = true;
+ plan.appUpdateInfo = {
+ version: server.app_version,
+ downloadUrl: server.app_download,
+ size: server.app_size,
+ message: server.showmessage
+ };
+ }
+
+ // 比较游戏版本
+ if (this.isVersionNewer(server.game_version, local.gameVersion)) {
+ plan.needGameUpdate = true;
+ plan.gameUpdateInfo = {
+ version: server.game_version,
+ downloadUrl: server.game_download,
+ size: server.game_size,
+ message: server.showmessage
+ };
+ }
+
+ return plan;
+ }
+}
+```
+
+#### 8.2.2 版本比较逻辑
+
+```typescript
+class VersionComparator {
+
+ /**
+ * 比较两个版本号,判断newVersion是否比oldVersion新
+ */
+ public static isVersionNewer(newVersion: string, oldVersion: string): boolean {
+ if (!newVersion || !oldVersion) {
+ return !!newVersion && !oldVersion;
+ }
+
+ const newParts = newVersion.split('.').map(Number);
+ const oldParts = oldVersion.split('.').map(Number);
+ const maxLength = Math.max(newParts.length, oldParts.length);
+
+ for (let i = 0; i < maxLength; i++) {
+ const newPart = newParts[i] || 0;
+ const oldPart = oldParts[i] || 0;
+
+ if (newPart > oldPart) return true;
+ if (newPart < oldPart) return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取版本比较结果码(用于JS Bridge接口)
+ * @returns "1" - 本地版本高于服务器版本,"0" - 本地版本低于或等于服务器版本
+ */
+ public static getCompareCode(localVersion: string, serverVersion: string): string {
+ return this.isVersionNewer(localVersion, serverVersion) ? "1" : "0";
+ }
+}
+```
+
+### 8.3 应用更新实现
+
+#### 8.3.1 应用更新流程
+
+```typescript
+class AppUpdater {
+
+ /**
+ * 执行应用更新
+ */
+ public async updateApp(updateInfo: AppUpdateInfo): Promise {
+ try {
+ // 1. 显示更新确认对话框
+ const userConfirmed = await this.showUpdateConfirmDialog(updateInfo);
+ if (!userConfirmed) {
+ return;
+ }
+
+ // 2. 显示下载进度界面
+ this.showDownloadProgress();
+
+ // 3. 下载应用包
+ const appPackagePath = await this.downloadAppPackage(updateInfo.downloadUrl);
+
+ // 4. 验证包完整性
+ await this.verifyPackageIntegrity(appPackagePath);
+
+ // 5. 启动安装流程
+ await this.installAppPackage(appPackagePath);
+
+ } catch (error) {
+ console.error('应用更新失败:', error);
+ this.showUpdateErrorDialog(error.message);
+ }
+ }
+
+ /**
+ * 显示更新确认对话框
+ */
+ private async showUpdateConfirmDialog(updateInfo: AppUpdateInfo): Promise {
+ return new Promise((resolve) => {
+ AlertDialog.show({
+ title: '发现新版本',
+ message: `版本 ${updateInfo.version}\n大小: ${updateInfo.size}\n\n${updateInfo.message}`,
+ primaryButton: {
+ value: '立即更新',
+ action: () => resolve(true)
+ },
+ secondaryButton: {
+ value: '稍后更新',
+ action: () => resolve(false)
+ }
+ });
+ });
+ }
+
+ /**
+ * 下载应用包
+ */
+ private async downloadAppPackage(downloadUrl: string): Promise {
+ const downloader = new FileDownloader();
+ const fileName = `tsgame_update_${Date.now()}.hap`;
+ const downloadPath = getContext().filesDir + '/downloads/' + fileName;
+
+ // 创建下载目录
+ await fileIo.mkdir(downloadPath.substring(0, downloadPath.lastIndexOf('/')));
+
+ return new Promise((resolve, reject) => {
+ downloader.download(downloadUrl, downloadPath, {
+ onProgress: (progress: number, total: number) => {
+ this.updateDownloadProgress(progress, total);
+ },
+ onSuccess: () => {
+ console.log('应用包下载完成:', downloadPath);
+ resolve(downloadPath);
+ },
+ onError: (error: Error) => {
+ console.error('应用包下载失败:', error);
+ reject(error);
+ }
+ });
+ });
+ }
+
+ /**
+ * 安装应用包
+ */
+ private async installAppPackage(packagePath: string): Promise {
+ try {
+ // HarmonyOS应用安装API调用
+ const bundleInstaller = installer.getBundleInstaller();
+
+ await bundleInstaller.install([packagePath], {
+ userId: 100,
+ installFlag: installer.InstallFlag.REPLACE_EXISTING
+ });
+
+ console.log('应用安装成功');
+
+ // 安装成功后重启应用
+ this.restartApp();
+
+ } catch (error) {
+ console.error('应用安装失败:', error);
+ throw new Error('安装失败,请手动安装');
+ }
+ }
+}
+```
+
+#### 8.3.2 下载进度管理
+
+```typescript
+class DownloadProgressManager {
+ private progressDialog: CustomDialogController | null = null;
+
+ /**
+ * 显示下载进度界面
+ */
+ public showDownloadProgress(): void {
+ this.progressDialog = new CustomDialogController({
+ builder: DownloadProgressDialog({
+ progress: 0,
+ downloadedSize: '0 MB',
+ totalSize: '0 MB',
+ onCancel: () => this.cancelDownload()
+ }),
+ autoCancel: false,
+ customStyle: true
+ });
+
+ this.progressDialog.open();
+ }
+
+ /**
+ * 更新下载进度
+ */
+ public updateProgress(downloaded: number, total: number): void {
+ if (!this.progressDialog) return;
+
+ const progress = Math.floor((downloaded / total) * 100);
+ const downloadedMB = (downloaded / 1024 / 1024).toFixed(2);
+ const totalMB = (total / 1024 / 1024).toFixed(2);
+
+ // 更新进度界面显示
+ this.progressDialog.update({
+ progress: progress,
+ downloadedSize: `${downloadedMB} MB`,
+ totalSize: `${totalMB} MB`
+ });
+ }
+
+ /**
+ * 隐藏下载进度界面
+ */
+ public hideDownloadProgress(): void {
+ if (this.progressDialog) {
+ this.progressDialog.close();
+ this.progressDialog = null;
+ }
+ }
+}
+```
+
+### 8.4 游戏资源更新实现
+
+#### 8.4.1 游戏资源更新流程
+
+```typescript
+class GameResourceUpdater {
+
+ /**
+ * 执行游戏资源更新
+ */
+ public async updateGameResources(updateInfo: GameUpdateInfo): Promise {
+ try {
+ // 1. 静默下载游戏ZIP包
+ this.showUpdateProgress('正在下载游戏资源...');
+ const zipFilePath = await this.downloadGameZip(updateInfo.downloadUrl);
+
+ // 2. 验证ZIP包完整性
+ this.showUpdateProgress('正在验证资源包...');
+ await this.verifyZipIntegrity(zipFilePath);
+
+ // 3. 备份当前游戏资源
+ this.showUpdateProgress('正在备份当前资源...');
+ await this.backupCurrentGameResources();
+
+ // 4. 解压新的游戏资源
+ this.showUpdateProgress('正在解压游戏资源...');
+ await this.extractGameZip(zipFilePath);
+
+ // 5. 更新本地版本信息
+ await this.updateLocalVersionInfo(updateInfo.version);
+
+ // 6. 清理临时文件
+ await this.cleanupTempFiles(zipFilePath);
+
+ console.log('游戏资源更新完成');
+ this.hideUpdateProgress();
+
+ } catch (error) {
+ console.error('游戏资源更新失败:', error);
+
+ // 失败时恢复备份
+ await this.restoreBackupResources();
+ this.showUpdateErrorDialog('游戏资源更新失败,已恢复原始资源');
+ }
+ }
+
+ /**
+ * 下载游戏ZIP包
+ */
+ private async downloadGameZip(downloadUrl: string): Promise {
+ const downloader = new FileDownloader();
+ const fileName = `game_${Date.now()}.zip`;
+ const downloadPath = getContext().filesDir + '/temp/' + fileName;
+
+ // 创建临时目录
+ await fileIo.mkdir(downloadPath.substring(0, downloadPath.lastIndexOf('/')));
+
+ return new Promise((resolve, reject) => {
+ downloader.download(downloadUrl, downloadPath, {
+ onProgress: (progress: number, total: number) => {
+ const percent = Math.floor((progress / total) * 100);
+ this.updateProgressText(`下载进度: ${percent}%`);
+ },
+ onSuccess: () => {
+ console.log('游戏ZIP包下载完成:', downloadPath);
+ resolve(downloadPath);
+ },
+ onError: (error: Error) => {
+ console.error('游戏ZIP包下载失败:', error);
+ reject(error);
+ }
+ });
+ });
+ }
+
+ /**
+ * 解压游戏ZIP包
+ */
+ private async extractGameZip(zipFilePath: string): Promise {
+ const gameResourceDir = this.getGameResourceDirectory();
+
+ // 删除旧的游戏资源
+ if (await fileIo.access(gameResourceDir)) {
+ await fileIo.rmdir(gameResourceDir, true);
+ }
+
+ // 创建游戏资源目录
+ await fileIo.mkdir(gameResourceDir, true);
+
+ // 解压ZIP包到游戏资源目录
+ const zipExtractor = new ZipExtractor();
+ await zipExtractor.extract(zipFilePath, gameResourceDir, {
+ onProgress: (extractedFiles: number, totalFiles: number) => {
+ const percent = Math.floor((extractedFiles / totalFiles) * 100);
+ this.updateProgressText(`解压进度: ${percent}%`);
+ }
+ });
+
+ console.log('游戏资源解压完成');
+ }
+
+ /**
+ * 备份当前游戏资源
+ */
+ private async backupCurrentGameResources(): Promise {
+ const gameResourceDir = this.getGameResourceDirectory();
+ const backupDir = this.getGameBackupDirectory();
+
+ if (await fileIo.access(gameResourceDir)) {
+ // 删除旧备份
+ if (await fileIo.access(backupDir)) {
+ await fileIo.rmdir(backupDir, true);
+ }
+
+ // 创建新备份
+ await this.copyDirectory(gameResourceDir, backupDir);
+ console.log('游戏资源备份完成');
+ }
+ }
+
+ /**
+ * 恢复备份资源
+ */
+ private async restoreBackupResources(): Promise {
+ const gameResourceDir = this.getGameResourceDirectory();
+ const backupDir = this.getGameBackupDirectory();
+
+ if (await fileIo.access(backupDir)) {
+ // 删除损坏的资源
+ if (await fileIo.access(gameResourceDir)) {
+ await fileIo.rmdir(gameResourceDir, true);
+ }
+
+ // 恢复备份资源
+ await this.copyDirectory(backupDir, gameResourceDir);
+ console.log('游戏资源恢复完成');
+ }
+ }
+}
+```
+
+#### 8.4.2 ZIP包处理工具
+
+```typescript
+class ZipExtractor {
+
+ /**
+ * 解压ZIP文件
+ */
+ public async extract(zipPath: string, targetDir: string, options?: ExtractOptions): Promise {
+ return new Promise((resolve, reject) => {
+ // 使用HarmonyOS的压缩解压API
+ const decompressor = zlib.createDecompressor();
+
+ decompressor.decompress(zipPath, targetDir, (error, result) => {
+ if (error) {
+ reject(new Error(`解压失败: ${error.message}`));
+ return;
+ }
+
+ if (options?.onProgress) {
+ options.onProgress(result.extractedFiles, result.totalFiles);
+ }
+
+ resolve();
+ });
+ });
+ }
+
+ /**
+ * 验证ZIP包完整性
+ */
+ public async verifyIntegrity(zipPath: string): Promise {
+ try {
+ const validator = zlib.createValidator();
+ const result = await validator.validate(zipPath);
+ return result.isValid;
+ } catch (error) {
+ console.error('ZIP包验证失败:', error);
+ return false;
+ }
+ }
+}
+```
+
+### 8.5 文件下载器实现
+
+#### 8.5.1 网络下载管理
+
+```typescript
+class FileDownloader {
+ private downloadTask: request.DownloadTask | null = null;
+
+ /**
+ * 下载文件
+ */
+ public download(url: string, filePath: string, callbacks: DownloadCallbacks): void {
+ const downloadConfig: request.DownloadConfig = {
+ url: url,
+ filePath: filePath,
+ enableMetered: true,
+ enableRoaming: true,
+ description: '正在下载更新包...',
+ networkType: request.NETWORK_WIFI | request.NETWORK_MOBILE
+ };
+
+ request.downloadFile(getContext(), downloadConfig).then((task) => {
+ this.downloadTask = task;
+
+ // 监听下载进度
+ task.on('progress', (receivedSize: number, totalSize: number) => {
+ callbacks.onProgress?.(receivedSize, totalSize);
+ });
+
+ // 监听下载完成
+ task.on('complete', () => {
+ callbacks.onSuccess?.();
+ this.downloadTask = null;
+ });
+
+ // 监听下载失败
+ task.on('fail', (error: number) => {
+ const errorMsg = this.getDownloadErrorMessage(error);
+ callbacks.onError?.(new Error(errorMsg));
+ this.downloadTask = null;
+ });
+
+ }).catch((error) => {
+ callbacks.onError?.(error);
+ });
+ }
+
+ /**
+ * 取消下载
+ */
+ public cancel(): void {
+ if (this.downloadTask) {
+ this.downloadTask.delete((error) => {
+ if (error) {
+ console.error('取消下载失败:', error);
+ } else {
+ console.log('下载已取消');
+ }
+ });
+ this.downloadTask = null;
+ }
+ }
+
+ /**
+ * 获取下载错误信息
+ */
+ private getDownloadErrorMessage(errorCode: number): string {
+ const errorMessages = {
+ [request.ERROR_CANNOT_RESUME]: '无法恢复下载',
+ [request.ERROR_DEVICE_NOT_FOUND]: '设备未找到',
+ [request.ERROR_FILE_ALREADY_EXISTS]: '文件已存在',
+ [request.ERROR_FILE_ERROR]: '文件错误',
+ [request.ERROR_HTTP_DATA_ERROR]: 'HTTP数据错误',
+ [request.ERROR_INSUFFICIENT_SPACE]: '存储空间不足',
+ [request.ERROR_TOO_MANY_REDIRECTS]: '重定向次数过多',
+ [request.ERROR_UNHANDLED_HTTP_CODE]: '未处理的HTTP状态码',
+ [request.ERROR_UNKNOWN]: '未知错误'
+ };
+
+ return errorMessages[errorCode] || `下载失败,错误代码: ${errorCode}`;
+ }
+}
+```
+
+### 8.6 更新状态管理
+
+#### 8.6.1 更新进度界面
+
+```typescript
+@CustomDialog
+struct DownloadProgressDialog {
+ @State progress: number = 0;
+ @State downloadedSize: string = '0 MB';
+ @State totalSize: string = '0 MB';
+ @State statusText: string = '正在下载...';
+ @State errorMessage: string = '';
+ onCancel?: () => void;
+
+ build() {
+ Column({ space: 20 }) {
+ // 标题
+ Text('更新下载')
+ .fontSize(18)
+ .fontWeight(FontWeight.Medium)
+ .fontColor(Color.Black)
+
+ // 进度条
+ Progress({
+ value: this.progress,
+ total: 100,
+ type: ProgressType.Linear
+ })
+ .width('100%')
+ .height(10)
+ .color(Color.Blue)
+ .backgroundColor(Color.Gray)
+
+ // 进度文本
+ Row() {
+ Text(`${this.progress}%`)
+ .fontSize(14)
+ .fontColor(Color.Gray)
+
+ Spacer()
+
+ Text(`${this.downloadedSize} / ${this.totalSize}`)
+ .fontSize(14)
+ .fontColor(Color.Gray)
+ }
+ .width('100%')
+
+ // 状态文本
+ Text(this.statusText)
+ .fontSize(14)
+ .fontColor(Color.Gray)
+ .textAlign(TextAlign.Center)
+
+ // 错误信息
+ if (this.errorMessage.trim() !== '') {
+ Text(this.errorMessage)
+ .fontSize(14)
+ .fontColor(Color.Red)
+ .textAlign(TextAlign.Center)
+ }
+
+ // 取消按钮
+ Button('取消下载')
+ .width('100%')
+ .height(40)
+ .backgroundColor(Color.Gray)
+ .onClick(() => {
+ this.onCancel?.();
+ })
+ }
+ .width('80%')
+ .padding(20)
+ .backgroundColor(Color.White)
+ .borderRadius(10)
+ }
+}
+```
+
+## 9. 项目风险评估与解决方案
+
+### 9.1 高风险项
+
+#### 9.1.1 微信SDK集成 (风险等级: 高)
+**问题描述**: 微信SDK可能不支持HarmonyOS平台
+**影响评估**:
+- 微信登录功能无法实现
+- 微信分享功能受限
+- 用户体验下降
+
+**解决方案**:
+1. **WebView内嵌登录**: 使用WebView内嵌微信网页版登录
+2. **华为账号优先**: 将华为账号作为主要登录方式
+3. **预留接口**: 保留微信SDK接口,待官方支持后再集成
+4. **系统分享**: 使用HarmonyOS系统级分享功能替代
+
+#### 9.1.2 双WebView架构性能 (风险等级: 中)
+**问题描述**: 多个WebView可能导致内存占用过高
+**影响评估**:
+- 应用内存占用增加
+- 系统性能下降
+- 可能出现OOM崩溃
+
+**解决方案**:
+1. **WebView复用机制**: 实现WebView池化管理
+2. **及时释放**: 及时释放不活跃的WebView资源
+3. **资源优化**: 优化H5资源加载策略
+4. **内存监控**: 建立内存使用监控机制
+
+#### 9.1.3 HarmonyOS 5.0新API适配 (风险等级: 中)
+**问题描述**: 新版本API可能存在兼容性问题或文档不完善
+**影响评估**:
+- 新特性无法正常使用
+- 向下兼容性问题
+- 开发周期延长
+
+**解决方案**:
+1. **官方文档跟踪**: 及时关注华为官方文档更新
+2. **兼容性测试**: 建立API兼容性测试用例
+3. **技术支持**: 保持与华为技术支持的沟通
+4. **降级方案**: 准备API降级方案确保向下兼容
+
+### 9.2 中风险项
+
+#### 9.2.1 华为地图SDK集成
+**解决方案**: 参考华为官方文档,建立标准集成流程
+
+#### 9.2.2 版本更新机制
+**解决方案**: 参考HarmonyOS应用更新最佳实践
+
+#### 9.2.3 性能优化
+**解决方案**: 建立性能监控体系,持续优化
+
+## 10. 团队分工与技能要求
+
+### 10.1 核心开发团队 (3-5人)
+
+#### 10.1.1 架构师/技术负责人 (1人)
+**职责**:
+- 技术架构设计和决策
+- 核心模块开发
+- 代码审查和质量把控
+- 技术难点攻关
+
+**技能要求**:
+- 精通HarmonyOS开发 (熟悉5.0新特性)
+- 丰富的移动应用架构经验
+- 熟悉WebView和JS Bridge技术
+- 了解HarmonyOS 5.0性能优化技术
+
+#### 10.1.2 前端开发/UI开发 (1人)
+**职责**:
+- WebView组件开发
+- 用户界面实现
+- 用户体验优化
+- 前端性能优化
+
+**技能要求**:
+- 熟练掌握ArkTS/TypeScript
+- 熟悉HarmonyOS UI框架 (包括5.0新特性)
+- 有WebView开发经验
+- 了解HarmonyOS 5.0 UI渲染优化
+
+#### 10.1.3 后端集成/API开发 (1-2人)
+**职责**:
+- JS Bridge API实现
+- 第三方SDK集成
+- 系统服务集成
+- 网络和存储功能
+
+**技能要求**:
+- 熟悉HarmonyOS系统API (特别是5.0新增API)
+- 有移动应用API开发经验
+- 了解第三方SDK集成
+- 掌握HarmonyOS 5.0安全特性
+
+#### 10.1.4 测试工程师 (1人)
+**职责**:
+- 功能测试和验证
+- 性能测试
+- 兼容性测试
+- Bug跟踪和质量保证
+
+**技能要求**:
+- 熟悉移动应用测试
+- 了解HarmonyOS测试工具 (包括5.0新测试框架)
+- 有自动化测试经验更佳
+- 掌握跨版本兼容性测试方法
+
+## 11. 开发里程碑与交付物
+
+### 11.1 里程碑1: 基础框架完成 (第2周末)
+**交付物**:
+- 可运行的项目骨架
+- 双WebView容器实现
+- 基础目录结构
+- 开发环境文档
+
+### 11.2 里程碑2: 核心功能完成 (第5周末)
+**交付物**:
+- 资源下载和管理功能
+- WebView加载和切换
+- 基础JS Bridge API
+- 功能演示版本
+
+### 11.3 里程碑3: 功能集成完成 (第7周末)
+**交付物**:
+- 所有第三方SDK集成
+- 完整的API接口
+- 媒体和定位功能
+- Beta测试版本
+
+### 11.4 里程碑4: 产品就绪 (第10周末)
+**交付物**:
+- 完整功能的应用
+- 性能优化版本
+- 完整测试报告
+- 发布准备材料
+
+## 12. Git版本管理规范
+
+### 12.1 Git工作流策略
+
+采用**Git Flow**工作流模式,确保代码管理的规范性和可追溯性。
+
+#### 12.1.1 分支策略
+
+```
+master (主分支)
+├── develop (开发分支)
+│ ├── feature/user-auth # 功能分支:用户认证
+│ ├── feature/webview-bridge # 功能分支:WebView桥接
+│ ├── feature/media-manager # 功能分支:媒体管理
+│ └── feature/location-service # 功能分支:定位服务
+├── release/v1.0.0 # 发布分支
+└── hotfix/critical-bug-fix # 热修复分支
+```
+
+**分支说明**:
+- **master**: 生产环境分支,只接受release和hotfix的合并
+- **develop**: 开发主分支,集成所有feature分支
+- **feature/***: 功能开发分支,从develop分出,完成后合并回develop
+- **release/***: 发布准备分支,从develop分出,用于发布前的bug修复
+- **hotfix/***: 紧急修复分支,从master分出,修复后合并到master和develop
+
+#### 12.1.2 分支命名规范
+
+```bash
+# 功能分支
+feature/模块名-功能描述
+例:feature/auth-wechat-login
+ feature/webview-js-bridge
+ feature/media-audio-record
+
+# 发布分支
+release/版本号
+例:release/v1.0.0
+ release/v1.1.0
+
+# 热修复分支
+hotfix/问题描述
+例:hotfix/webview-crash-fix
+ hotfix/login-timeout-issue
+```
+
+#### 12.1.3 提交消息规范
+
+```bash
+():
+
+# type类型:
+feat: 新功能
+fix: 修复bug
+docs: 文档更新
+style: 代码格式调整
+refactor: 代码重构
+test: 测试相关
+chore: 构建过程或辅助工具的变动
+
+# 示例:
+feat(webview): 实现双WebView架构
+fix(bridge): 修复JS Bridge通信异常
+docs(api): 更新API接口文档
+```
+
+### 12.2 代码审查规范
+
+1. **必须审查**: 所有PR必须经过代码审查
+2. **审查人员**: 至少需要一名高级开发人员审查
+3. **审查内容**: 代码质量、性能、安全性、规范性
+4. **合并条件**: 审查通过且CI/CD通过后方可合并
+
+## 13. 最佳实践和注意事项
+
+### 13.1 性能优化最佳实践
+
+1. **WebView优化**:
+ - 使用WebView池化管理
+ - 及时释放不使用的WebView
+ - 优化JS Bridge调用频率
+
+2. **内存管理**:
+ - 监控内存使用情况
+ - 及时清理缓存数据
+ - 避免内存泄漏
+
+3. **网络优化**:
+ - 使用CDN加速资源下载
+ - 实现断点续传机制
+ - 合理使用缓存策略
+
+### 13.2 安全最佳实践
+
+1. **数据安全**:
+ - 敏感数据加密存储
+ - 网络传输使用HTTPS
+ - 验证下载包完整性
+
+2. **权限管理**:
+ - 最小权限原则
+ - 动态权限申请
+ - 权限使用说明
+
+3. **代码安全**:
+ - 代码混淆保护
+ - 防止逆向工程
+ - 安全审计
\ No newline at end of file
diff --git a/TSGame_应用启动流程详解.md b/TSGame_应用启动流程详解.md
new file mode 100644
index 0000000..b12d26e
--- /dev/null
+++ b/TSGame_应用启动流程详解.md
@@ -0,0 +1,2535 @@
+# 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版本完全一致的功能特性。
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..ec50c74
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,85 @@
+apply plugin: 'com.android.application'
+
+android {
+ signingConfigs { release {
+ storeFile file('./keystore/gamehall.keystore')
+ storePassword 'tswl2015'
+ keyPassword 'tswl2015'
+ keyAlias = 'gamehall'
+ v1SigningEnabled true
+ v2SigningEnabled true
+ }
+
+ debug {
+ storeFile file('./keystore/gamehall.keystore')
+ storePassword 'tswl2015'
+ keyPassword 'tswl2015'
+ keyAlias = 'gamehall'
+ v1SigningEnabled true
+ v2SigningEnabled true
+ }
+
+ }
+ compileSdk 33
+ buildToolsVersion '33.0.0'
+
+ defaultConfig {
+ applicationId "com.jx.jyhd"
+ minSdkVersion 21
+ targetSdkVersion 32
+ versionCode= 3
+ versionName="3.6.3"
+ multiDexEnabled true
+
+ ndk {
+ abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'//, 'mips', 'mips64'
+ }
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ signingConfig signingConfigs.release
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ lintOptions {
+ abortOnError false
+ checkReleaseBuilds false
+ }
+}
+
+dependencies {
+ //noinspection GradleCompatible
+ implementation 'com.android.support:support-v4:28.0.0'
+ //noinspection GradleCompatible
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.google.code.gson:gson:2.8.9'
+ implementation 'com.yanzhenjie.nohttp:nohttp:1.1.2'
+ implementation 'com.orhanobut:logger:2.2.0'
+ implementation 'com.android.volley:volley:1.2.1'
+ implementation 'com.tencent.bugly:crashreport:3.4.4'
+ implementation 'com.android.support:multidex:1.0.3'
+ implementation 'com.umeng.sdk:common:1.5.0'
+ implementation 'com.umeng.sdk:analytics:7.5.0'
+ implementation 'com.squareup.okhttp3:okhttp:4.12.0'
+ implementation 'com.squareup.okio:okio:3.6.0'
+ implementation 'org.nanohttpd:nanohttpd:2.3.1'
+ implementation 'com.qiniu:happy-dns:2.0.0'
+ implementation ('com.qiniu:qiniu-android-sdk:8.5.0'){
+ exclude (group: 'com.squareup.okhttp3', module: 'okhttp')
+ }
+ implementation('com.qiniu:qiniu-java-sdk:7.7.0') {
+ exclude group: 'com.squareup.okhttp3', module: 'okhttp'
+ }
+ implementation 'android.arch.lifecycle:extensions:1.1.1'
+ api 'com.tencent.tbs:tbssdk:44286'
+ api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.11'
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation files('libs/AMap2DMap_5.2.0_AMapSearch_6.9.2_AMapLocation_4.7.2_20191009.jar')
+}
diff --git a/app/libs/AMap2DMap_5.2.0_AMapSearch_6.9.2_AMapLocation_4.7.2_20191009.jar b/app/libs/AMap2DMap_5.2.0_AMapSearch_6.9.2_AMapLocation_4.7.2_20191009.jar
new file mode 100644
index 0000000..1a61cc7
Binary files /dev/null and b/app/libs/AMap2DMap_5.2.0_AMapSearch_6.9.2_AMapLocation_4.7.2_20191009.jar differ
diff --git a/app/libs/agora-rtc-sdk.jar b/app/libs/agora-rtc-sdk.jar
new file mode 100644
index 0000000..062cd0b
Binary files /dev/null and b/app/libs/agora-rtc-sdk.jar differ
diff --git a/app/libs/android-async-http-1.4.9.jar b/app/libs/android-async-http-1.4.9.jar
new file mode 100644
index 0000000..c2d8b86
Binary files /dev/null and b/app/libs/android-async-http-1.4.9.jar differ
diff --git a/app/libs/android-support-percent.jar b/app/libs/android-support-percent.jar
new file mode 100644
index 0000000..2fa8a9f
Binary files /dev/null and b/app/libs/android-support-percent.jar differ
diff --git a/app/libs/andserver1.0.1.jar b/app/libs/andserver1.0.1.jar
new file mode 100644
index 0000000..3030621
Binary files /dev/null and b/app/libs/andserver1.0.1.jar differ
diff --git a/app/libs/ddmlib.jar b/app/libs/ddmlib.jar
new file mode 100644
index 0000000..3b4d4df
Binary files /dev/null and b/app/libs/ddmlib.jar differ
diff --git a/app/libs/org.apache.http.legacy.jar b/app/libs/org.apache.http.legacy.jar
new file mode 100644
index 0000000..5b89fbd
Binary files /dev/null and b/app/libs/org.apache.http.legacy.jar differ
diff --git a/app/libs/xUtils-2.6.14.jar b/app/libs/xUtils-2.6.14.jar
new file mode 100644
index 0000000..0e8d26f
Binary files /dev/null and b/app/libs/xUtils-2.6.14.jar differ
diff --git a/app/libs/zxing.jar b/app/libs/zxing.jar
new file mode 100644
index 0000000..e7b7afc
Binary files /dev/null and b/app/libs/zxing.jar differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..cda7cd8
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,18 @@
+{
+ "version": 2,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.jx.jyhd",
+ "variantName": "processReleaseResources",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "versionCode": 3,
+ "versionName": "3.6.3",
+ "outputFile": "app-release.apk"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..75a5d47
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/WebViewJavascriptBridge.js b/app/src/main/assets/WebViewJavascriptBridge.js
new file mode 100644
index 0000000..37e9220
--- /dev/null
+++ b/app/src/main/assets/WebViewJavascriptBridge.js
@@ -0,0 +1,145 @@
+//notation: js file can only use this kind of comments
+//since comments will cause error when use in webview.loadurl,
+//comments will be remove by java use regexp
+(function() {
+ if (window.WebViewJavascriptBridge) {
+ return;
+ }
+
+ var messagingIframe;
+ var sendMessageQueue = [];
+ var receiveMessageQueue = [];
+ var messageHandlers = {};
+
+ var CUSTOM_PROTOCOL_SCHEME = 'yy';
+ var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';
+
+ var responseCallbacks = {};
+ var uniqueId = 1;
+
+ // 创建队列iframe
+ function _createQueueReadyIframe(doc) {
+ messagingIframe = doc.createElement('iframe');
+ messagingIframe.style.display = 'none';
+ doc.documentElement.appendChild(messagingIframe);
+ }
+
+ //set default messageHandler 初始化默认的消息线程
+ function init(messageHandler) {
+ if (WebViewJavascriptBridge._messageHandler) {
+ throw new Error('WebViewJavascriptBridge.init called twice');
+ }
+ WebViewJavascriptBridge._messageHandler = messageHandler;
+ var receivedMessages = receiveMessageQueue;
+ receiveMessageQueue = null;
+ for (var i = 0; i < receivedMessages.length; i++) {
+ _dispatchMessageFromNative(receivedMessages[i]);
+ }
+ }
+
+ // 发送
+ function send(data, responseCallback) {
+ _doSend({
+ data: data
+ }, responseCallback);
+ }
+
+ // 注册线程 往数组里面添加值
+ function registerHandler(handlerName, handler) {
+ messageHandlers[handlerName] = handler;
+ }
+ // 调用线程
+ function callHandler(handlerName, data, responseCallback) {
+ _doSend({
+ handlerName: handlerName,
+ data: data
+ }, responseCallback);
+ }
+
+ //sendMessage add message, 触发native处理 sendMessage
+ function _doSend(message, responseCallback) {
+ if (responseCallback) {
+ var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
+ responseCallbacks[callbackId] = responseCallback;
+ message.callbackId = callbackId;
+ }
+
+ sendMessageQueue.push(message);
+ messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
+ }
+
+ // 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
+ function _fetchQueue() {
+ var messageQueueString = JSON.stringify(sendMessageQueue);
+ sendMessageQueue = [];
+ //android can't read directly the return data, so we can reload iframe src to communicate with java
+ messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
+ }
+
+ //提供给native使用,
+ function _dispatchMessageFromNative(messageJSON) {
+ setTimeout(function() {
+ var message = JSON.parse(messageJSON);
+ var responseCallback;
+ //java call finished, now need to call js callback function
+ if (message.responseId) {
+ responseCallback = responseCallbacks[message.responseId];
+ if (!responseCallback) {
+ return;
+ }
+ responseCallback(message.responseData);
+ delete responseCallbacks[message.responseId];
+ } else {
+ //直接发送
+ if (message.callbackId) {
+ var callbackResponseId = message.callbackId;
+ responseCallback = function(responseData) {
+ _doSend({
+ responseId: callbackResponseId,
+ responseData: responseData
+ });
+ };
+ }
+
+ var handler = WebViewJavascriptBridge._messageHandler;
+ if (message.handlerName) {
+ handler = messageHandlers[message.handlerName];
+ }
+ //查找指定handler
+ try {
+ handler(message.data, responseCallback);
+ } catch (exception) {
+ if (typeof console != 'undefined') {
+ console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
+ }
+ }
+ }
+ });
+ }
+
+ //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
+ function _handleMessageFromNative(messageJSON) {
+ console.log(messageJSON);
+ if (receiveMessageQueue) {
+ receiveMessageQueue.push(messageJSON);
+ }
+ _dispatchMessageFromNative(messageJSON);
+
+ }
+
+ var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
+ init: init,
+ send: send,
+ registerHandler: registerHandler,
+ callHandler: callHandler,
+ _fetchQueue: _fetchQueue,
+ _handleMessageFromNative: _handleMessageFromNative
+ };
+
+ var doc = document;
+ _createQueueReadyIframe(doc);
+ var readyEvent = doc.createEvent('Events');
+ readyEvent.initEvent('WebViewJavascriptBridgeReady');
+ readyEvent.bridge = WebViewJavascriptBridge;
+ doc.dispatchEvent(readyEvent);
+})();
diff --git a/app/src/main/assets/agent/BoolTest.java b/app/src/main/assets/agent/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/agent/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/agent/veRa0qrBf0df2K1G4de2tgfmVxB2jxpv/BoolTest.java b/app/src/main/assets/agent/veRa0qrBf0df2K1G4de2tgfmVxB2jxpv/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/agent/veRa0qrBf0df2K1G4de2tgfmVxB2jxpv/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/appversion/1/BoolTest.java b/app/src/main/assets/appversion/1/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/appversion/1/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/appversion/BoolTest.java b/app/src/main/assets/appversion/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/appversion/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/channel/BoolTest.java b/app/src/main/assets/channel/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/channel/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/channel/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK/BoolTest.java b/app/src/main/assets/channel/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/channel/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/demo.html b/app/src/main/assets/demo.html
new file mode 100644
index 0000000..7677f7c
--- /dev/null
+++ b/app/src/main/assets/demo.html
@@ -0,0 +1,146 @@
+
+
+
+
+ js调用java
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/gameconfig/BoolTest.java b/app/src/main/assets/gameconfig/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/gameconfig/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/gameconfig/tsgames.daoqi88.cn-config-update_jsonv2/BoolTest.java b/app/src/main/assets/gameconfig/tsgames.daoqi88.cn-config-update_jsonv2/BoolTest.java
new file mode 100644
index 0000000..fdc7c7f
--- /dev/null
+++ b/app/src/main/assets/gameconfig/tsgames.daoqi88.cn-config-update_jsonv2/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("我行我素购物管理系统》客户管理系统》添加客户信息\n");
+ System.out.print("请输入会员号4位整数:");
+ int huiyuan=input.nextInt();
+ System.out.print("请输入会员生日(月/日两位数表示):");
+ int shenri=input.nextInt();
+ System.out.print("请输入积分:");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("已录入的信息是");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("登入信息失败");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/assets/gamedir/BoolTest.java b/app/src/main/assets/gamedir/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/gamedir/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/gamedir/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK/BoolTest.java b/app/src/main/assets/gamedir/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/gamedir/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/gamehall/gamehall_42_0527.zip b/app/src/main/assets/gamehall/gamehall_42_0527.zip
new file mode 100644
index 0000000..d93c40d
Binary files /dev/null and b/app/src/main/assets/gamehall/gamehall_42_0527.zip differ
diff --git a/app/src/main/assets/gamehall/version.xml b/app/src/main/assets/gamehall/version.xml
new file mode 100644
index 0000000..f8f67ef
--- /dev/null
+++ b/app/src/main/assets/gamehall/version.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/gameid/BoolTest.java b/app/src/main/assets/gameid/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/gameid/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/gameserver/BoolTest.java b/app/src/main/assets/gameserver/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/gameserver/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/gamestart/BoolTest.java b/app/src/main/assets/gamestart/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/gamestart/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/gamestart/gamehall/BoolTest.java b/app/src/main/assets/gamestart/gamehall/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/gamestart/gamehall/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/market/4/BoolTest.java b/app/src/main/assets/market/4/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/market/4/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/market/BoolTest.java b/app/src/main/assets/market/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/market/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/other/BoolTest.java b/app/src/main/assets/other/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/other/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/servertype/BoolTest.java b/app/src/main/assets/servertype/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/servertype/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/assets/weburl/BoolTest.java b/app/src/main/assets/weburl/BoolTest.java
new file mode 100644
index 0000000..f895cd6
--- /dev/null
+++ b/app/src/main/assets/weburl/BoolTest.java
@@ -0,0 +1,24 @@
+import java.util.Scanner;
+public class BoolTest {
+ public static void main (String[]args){
+ Scanner input= new Scanner(System.in);
+ System.out.println("عϵͳͻϵͳӿͻϢ\n");
+ System.out.print("Ա4λ:");
+ int huiyuan=input.nextInt();
+ System.out.print("Ա(/λʾ):");
+ int shenri=input.nextInt();
+ System.out.print(":");
+ int jifen=input.nextInt();
+ if(huiyuan>=1000){
+ System.out.println("¼Ϣ");
+ System.out.print("\t"+huiyuan);
+ System.out.print("\t"+shenri);
+ System.out.print("\t"+jifen);
+
+ }
+ else{
+ System.out.print("Ϣʧ");
+ }
+}
+
+}
diff --git a/app/src/main/java/com/badoo/mobile/util/WeakHandler.java b/app/src/main/java/com/badoo/mobile/util/WeakHandler.java
new file mode 100644
index 0000000..3755814
--- /dev/null
+++ b/app/src/main/java/com/badoo/mobile/util/WeakHandler.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2014 Badoo Trading Limited
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Portions of documentation in this code are modifications based on work created and
+ * shared by Android Open Source Project and used according to terms described in the
+ * Apache License, Version 2.0
+ */
+package com.badoo.mobile.util;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Memory safer implementation of android.os.Handler
+ *
+ * Original implementation of Handlers always keeps hard reference to handler in queue of execution.
+ * If you create anonymous handler and post delayed message into it, it will keep all parent class
+ * for that time in memory even if it could be cleaned.
+ *
+ * This implementation is trickier, it will keep WeakReferences to runnables and messages,
+ * and GC could collect them once WeakHandler instance is not referenced any more
+ *
+ *
+ * @see android.os.Handler
+ *
+ * Created by Dmytro Voronkevych on 17/06/2014.
+ */
+@SuppressWarnings("unused")
+public class WeakHandler {
+ private final Handler.Callback mCallback; // hard reference to Callback. We need to keep callback in memory
+ private final ExecHandler mExec;
+ private Lock mLock = new ReentrantLock();
+ @SuppressWarnings("ConstantConditions")
+ @VisibleForTesting
+ final ChainedRef mRunnables = new ChainedRef(mLock, null);
+
+ /**
+ * Default constructor associates this handler with the {@link Looper} for the
+ * current thread.
+ *
+ * If this thread does not have a looper, this handler won't be able to receive messages
+ * so an exception is thrown.
+ */
+ public WeakHandler() {
+ mCallback = null;
+ mExec = new ExecHandler();
+ }
+
+ /**
+ * Constructor associates this handler with the {@link Looper} for the
+ * current thread and takes a callback interface in which you can handle
+ * messages.
+ *
+ * If this thread does not have a looper, this handler won't be able to receive messages
+ * so an exception is thrown.
+ *
+ * @param callback The callback interface in which to handle messages, or null.
+ */
+ public WeakHandler(@Nullable Handler.Callback callback) {
+ mCallback = callback; // Hard referencing body
+ mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler
+ }
+
+ /**
+ * Use the provided {@link Looper} instead of the default one.
+ *
+ * @param looper The looper, must not be null.
+ */
+ public WeakHandler(@NonNull Looper looper) {
+ mCallback = null;
+ mExec = new ExecHandler(looper);
+ }
+
+ /**
+ * Use the provided {@link Looper} instead of the default one and take a callback
+ * interface in which to handle messages.
+ *
+ * @param looper The looper, must not be null.
+ * @param callback The callback interface in which to handle messages, or null.
+ */
+ public WeakHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) {
+ mCallback = callback;
+ mExec = new ExecHandler(looper, new WeakReference<>(callback));
+ }
+
+ /**
+ * Causes the Runnable r to be added to the message queue.
+ * The runnable will be run on the thread to which this handler is
+ * attached.
+ *
+ * @param r The Runnable that will be executed.
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ */
+ public final boolean post(@NonNull Runnable r) {
+ return mExec.post(wrapRunnable(r));
+ }
+
+ /**
+ * Causes the Runnable r to be added to the message queue, to be run
+ * at a specific time given by uptimeMillis.
+ * The time-base is {@link android.os.SystemClock#uptimeMillis}.
+ * The runnable will be run on the thread to which this handler is attached.
+ *
+ * @param r The Runnable that will be executed.
+ * @param uptimeMillis The absolute time at which the callback should run,
+ * using the {@link android.os.SystemClock#uptimeMillis} time-base.
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting. Note that a
+ * result of true does not mean the Runnable will be processed -- if
+ * the looper is quit before the delivery time of the message
+ * occurs then the message will be dropped.
+ */
+ public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
+ return mExec.postAtTime(wrapRunnable(r), uptimeMillis);
+ }
+
+ /**
+ * Causes the Runnable r to be added to the message queue, to be run
+ * at a specific time given by uptimeMillis.
+ * The time-base is {@link android.os.SystemClock#uptimeMillis}.
+ * The runnable will be run on the thread to which this handler is attached.
+ *
+ * @param r The Runnable that will be executed.
+ * @param uptimeMillis The absolute time at which the callback should run,
+ * using the {@link android.os.SystemClock#uptimeMillis} time-base.
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting. Note that a
+ * result of true does not mean the Runnable will be processed -- if
+ * the looper is quit before the delivery time of the message
+ * occurs then the message will be dropped.
+ *
+ * @see android.os.SystemClock#uptimeMillis
+ */
+ public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
+ return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis);
+ }
+
+ /**
+ * Causes the Runnable r to be added to the message queue, to be run
+ * after the specified amount of time elapses.
+ * The runnable will be run on the thread to which this handler
+ * is attached.
+ *
+ * @param r The Runnable that will be executed.
+ * @param delayMillis The delay (in milliseconds) until the Runnable
+ * will be executed.
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting. Note that a
+ * result of true does not mean the Runnable will be processed --
+ * if the looper is quit before the delivery time of the message
+ * occurs then the message will be dropped.
+ */
+ public final boolean postDelayed(Runnable r, long delayMillis) {
+ return mExec.postDelayed(wrapRunnable(r), delayMillis);
+ }
+
+ /**
+ * Posts a message to an object that implements Runnable.
+ * Causes the Runnable r to executed on the next iteration through the
+ * message queue. The runnable will be run on the thread to which this
+ * handler is attached.
+ * This method is only for use in very special circumstances -- it
+ * can easily starve the message queue, cause ordering problems, or have
+ * other unexpected side-effects.
+ *
+ * @param r The Runnable that will be executed.
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ */
+ public final boolean postAtFrontOfQueue(Runnable r) {
+ return mExec.postAtFrontOfQueue(wrapRunnable(r));
+ }
+
+ /**
+ * Remove any pending posts of Runnable r that are in the message queue.
+ */
+ public final void removeCallbacks(Runnable r) {
+ final WeakRunnable runnable = mRunnables.remove(r);
+ if (runnable != null) {
+ mExec.removeCallbacks(runnable);
+ }
+ }
+
+ /**
+ * Remove any pending posts of Runnable r with Object
+ * token that are in the message queue. If token is null,
+ * all callbacks will be removed.
+ */
+ public final void removeCallbacks(Runnable r, Object token) {
+ final WeakRunnable runnable = mRunnables.remove(r);
+ if (runnable != null) {
+ mExec.removeCallbacks(runnable, token);
+ }
+ }
+
+ /**
+ * Pushes a message onto the end of the message queue after all pending messages
+ * before the current time. It will be received in callback,
+ * in the thread attached to this handler.
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ */
+ public final boolean sendMessage(Message msg) {
+ return mExec.sendMessage(msg);
+ }
+
+ /**
+ * Sends a Message containing only the what value.
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ */
+ public final boolean sendEmptyMessage(int what) {
+ return mExec.sendEmptyMessage(what);
+ }
+
+ /**
+ * Sends a Message containing only the what value, to be delivered
+ * after the specified amount of time elapses.
+ * @see #sendMessageDelayed(android.os.Message, long)
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ */
+ public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
+ return mExec.sendEmptyMessageDelayed(what, delayMillis);
+ }
+
+ /**
+ * Sends a Message containing only the what value, to be delivered
+ * at a specific time.
+ * @see #sendMessageAtTime(android.os.Message, long)
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ */
+ public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
+ return mExec.sendEmptyMessageAtTime(what, uptimeMillis);
+ }
+
+ /**
+ * Enqueue a message into the message queue after all pending messages
+ * before (current time + delayMillis). You will receive it in
+ * callback, in the thread attached to this handler.
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting. Note that a
+ * result of true does not mean the message will be processed -- if
+ * the looper is quit before the delivery time of the message
+ * occurs then the message will be dropped.
+ */
+ public final boolean sendMessageDelayed(Message msg, long delayMillis) {
+ return mExec.sendMessageDelayed(msg, delayMillis);
+ }
+
+ /**
+ * Enqueue a message into the message queue after all pending messages
+ * before the absolute time (in milliseconds) uptimeMillis.
+ * The time-base is {@link android.os.SystemClock#uptimeMillis}.
+ * You will receive it in callback, in the thread attached
+ * to this handler.
+ *
+ * @param uptimeMillis The absolute time at which the message should be
+ * delivered, using the
+ * {@link android.os.SystemClock#uptimeMillis} time-base.
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting. Note that a
+ * result of true does not mean the message will be processed -- if
+ * the looper is quit before the delivery time of the message
+ * occurs then the message will be dropped.
+ */
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ return mExec.sendMessageAtTime(msg, uptimeMillis);
+ }
+
+ /**
+ * Enqueue a message at the front of the message queue, to be processed on
+ * the next iteration of the message loop. You will receive it in
+ * callback, in the thread attached to this handler.
+ * This method is only for use in very special circumstances -- it
+ * can easily starve the message queue, cause ordering problems, or have
+ * other unexpected side-effects.
+ *
+ * @return Returns true if the message was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ */
+ public final boolean sendMessageAtFrontOfQueue(Message msg) {
+ return mExec.sendMessageAtFrontOfQueue(msg);
+ }
+
+ /**
+ * Remove any pending posts of messages with code 'what' that are in the
+ * message queue.
+ */
+ public final void removeMessages(int what) {
+ mExec.removeMessages(what);
+ }
+
+ /**
+ * Remove any pending posts of messages with code 'what' and whose obj is
+ * 'object' that are in the message queue. If object is null,
+ * all messages will be removed.
+ */
+ public final void removeMessages(int what, Object object) {
+ mExec.removeMessages(what, object);
+ }
+
+ /**
+ * Remove any pending posts of callbacks and sent messages whose
+ * obj is token. If token is null,
+ * all callbacks and messages will be removed.
+ */
+ public final void removeCallbacksAndMessages(Object token) {
+ mExec.removeCallbacksAndMessages(token);
+ }
+
+ /**
+ * Check if there are any pending posts of messages with code 'what' in
+ * the message queue.
+ */
+ public final boolean hasMessages(int what) {
+ return mExec.hasMessages(what);
+ }
+
+ /**
+ * Check if there are any pending posts of messages with code 'what' and
+ * whose obj is 'object' in the message queue.
+ */
+ public final boolean hasMessages(int what, Object object) {
+ return mExec.hasMessages(what, object);
+ }
+
+ public final Looper getLooper() {
+ return mExec.getLooper();
+ }
+
+ private WeakRunnable wrapRunnable(@NonNull Runnable r) {
+ //noinspection ConstantConditions
+ if (r == null) {
+ throw new NullPointerException("Runnable can't be null");
+ }
+ final ChainedRef hardRef = new ChainedRef(mLock, r);
+ mRunnables.insertAfter(hardRef);
+ return hardRef.wrapper;
+ }
+
+ private static class ExecHandler extends Handler {
+ private final WeakReference mCallback;
+
+ ExecHandler() {
+ mCallback = null;
+ }
+
+ ExecHandler(WeakReference callback) {
+ mCallback = callback;
+ }
+
+ ExecHandler(Looper looper) {
+ super(looper);
+ mCallback = null;
+ }
+
+ ExecHandler(Looper looper, WeakReference callback) {
+ super(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ if (mCallback == null) {
+ return;
+ }
+ final Handler.Callback callback = mCallback.get();
+ if (callback == null) { // Already disposed
+ return;
+ }
+ callback.handleMessage(msg);
+ }
+ }
+
+ static class WeakRunnable implements Runnable {
+ private final WeakReference mDelegate;
+ private final WeakReference mReference;
+
+ WeakRunnable(WeakReference delegate, WeakReference reference) {
+ mDelegate = delegate;
+ mReference = reference;
+ }
+
+ @Override
+ public void run() {
+ final Runnable delegate = mDelegate.get();
+ final ChainedRef reference = mReference.get();
+ if (reference != null) {
+ reference.remove();
+ }
+ if (delegate != null) {
+ delegate.run();
+ }
+ }
+ }
+
+ static class ChainedRef {
+ @Nullable
+ ChainedRef next;
+ @Nullable
+ ChainedRef prev;
+ @NonNull
+ final Runnable runnable;
+ @NonNull
+ final WeakRunnable wrapper;
+
+ @NonNull
+ Lock lock;
+
+ public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) {
+ this.runnable = r;
+ this.lock = lock;
+ this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this));
+ }
+
+ public WeakRunnable remove() {
+ lock.lock();
+ try {
+ if (prev != null) {
+ prev.next = next;
+ }
+ if (next != null) {
+ next.prev = prev;
+ }
+ prev = null;
+ next = null;
+ } finally {
+ lock.unlock();
+ }
+ return wrapper;
+ }
+
+ public void insertAfter(@NonNull ChainedRef candidate) {
+ lock.lock();
+ try {
+ if (this.next != null) {
+ this.next.prev = candidate;
+ }
+
+ candidate.next = this.next;
+ this.next = candidate;
+ candidate.prev = this;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Nullable
+ public WeakRunnable remove(Runnable obj) {
+ lock.lock();
+ try {
+ ChainedRef curr = this.next; // Skipping head
+ while (curr != null) {
+ if (curr.runnable == obj) { // We do comparison exactly how Handler does inside
+ return curr.remove();
+ }
+ curr = curr.next;
+ }
+ } finally {
+ lock.unlock();
+ }
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/bytedance/sdk/open/aweme/common/handler/IApiEventHandler.java b/app/src/main/java/com/bytedance/sdk/open/aweme/common/handler/IApiEventHandler.java
new file mode 100644
index 0000000..ed26f1d
--- /dev/null
+++ b/app/src/main/java/com/bytedance/sdk/open/aweme/common/handler/IApiEventHandler.java
@@ -0,0 +1,21 @@
+package com.bytedance.sdk.open.aweme.common.handler;
+
+import com.bytedance.sdk.open.aweme.common.model.BaseReq;
+import com.bytedance.sdk.open.aweme.common.model.BaseResp;
+
+/**
+ * 模拟抖音SDK的接口,用于处理抖音分享请求和响应
+ */
+public interface IApiEventHandler {
+ /**
+ * 处理抖音SDK的请求
+ * @param req 请求对象
+ */
+ void onReq(BaseReq req);
+
+ /**
+ * 处理抖音SDK的响应
+ * @param resp 响应对象
+ */
+ void onResp(BaseResp resp);
+}
diff --git a/app/src/main/java/com/bytedance/sdk/open/aweme/common/model/BaseReq.java b/app/src/main/java/com/bytedance/sdk/open/aweme/common/model/BaseReq.java
new file mode 100644
index 0000000..799d414
--- /dev/null
+++ b/app/src/main/java/com/bytedance/sdk/open/aweme/common/model/BaseReq.java
@@ -0,0 +1,16 @@
+package com.bytedance.sdk.open.aweme.common.model;
+
+/**
+ * 模拟抖音SDK的请求基类
+ */
+public class BaseReq {
+ private int type;
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+}
diff --git a/app/src/main/java/com/bytedance/sdk/open/aweme/common/model/BaseResp.java b/app/src/main/java/com/bytedance/sdk/open/aweme/common/model/BaseResp.java
new file mode 100644
index 0000000..0f3eb02
--- /dev/null
+++ b/app/src/main/java/com/bytedance/sdk/open/aweme/common/model/BaseResp.java
@@ -0,0 +1,29 @@
+package com.bytedance.sdk.open.aweme.common.model;
+
+/**
+ * 模拟抖音SDK的响应基类
+ */
+public class BaseResp {
+ private int errorCode;
+ private String errorMsg;
+
+ public static final int SUCCESS = 0;
+ public static final int ERROR = -1;
+ public static final int CANCEL = -2;
+
+ public int getErrorCode() {
+ return errorCode;
+ }
+
+ public void setErrorCode(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ public String getErrorMsg() {
+ return errorMsg;
+ }
+
+ public void setErrorMsg(String errorMsg) {
+ this.errorMsg = errorMsg;
+ }
+}
diff --git a/app/src/main/java/com/ceshi/tsgame/shake/DensityUtil.java b/app/src/main/java/com/ceshi/tsgame/shake/DensityUtil.java
new file mode 100644
index 0000000..2dcb62d
--- /dev/null
+++ b/app/src/main/java/com/ceshi/tsgame/shake/DensityUtil.java
@@ -0,0 +1,21 @@
+package com.ceshi.tsgame.shake;
+
+import android.content.Context;
+
+public class DensityUtil {
+ /**
+ * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+ public static int dip2px(Context context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ /**
+ * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
+ */
+ public static int px2dip(Context context, float pxValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (pxValue / scale + 0.5f);
+ }
+}
diff --git a/app/src/main/java/com/ceshi/tsgame/shake/NeedForSound.java b/app/src/main/java/com/ceshi/tsgame/shake/NeedForSound.java
new file mode 100644
index 0000000..0f782f3
--- /dev/null
+++ b/app/src/main/java/com/ceshi/tsgame/shake/NeedForSound.java
@@ -0,0 +1,74 @@
+package com.ceshi.tsgame.shake;
+
+import java.util.HashMap;
+import com.jx.jyhd.R;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.SoundPool;
+
+/**
+ * 声音所需工具
+ * @author wangyue
+ * */
+public class NeedForSound {
+
+ /** 摇一摇需要的声音池view */
+ private SoundPool sndPool = null;
+
+ private HashMap soundPoolMap = null;
+
+ private static NeedForSound needForSound = null;
+
+ public static NeedForSound getInstance() {
+ if (needForSound == null) {
+ needForSound = new NeedForSound();
+ }
+ return needForSound;
+ }
+
+ /** 添加声音 */
+ public void addSound(final Context context) {
+ soundPoolMap = new HashMap();
+ //第一个参数指定支持多少个声音;第二个参数指定声音类型:第三个参数指定声音品质。
+ sndPool = new SoundPool(3, AudioManager.STREAM_SYSTEM, 5);
+ new Thread() {
+ public void run() {
+ try {
+ //http://www.tuicool.com/articles/yuyQ3a
+ soundPoolMap.put(0, sndPool.load(context.getResources().openRawResourceFd(R.raw.shake_sound_male), 1));
+ soundPoolMap.put(1, sndPool.load(context.getResources().openRawResourceFd(R.raw.shake_match), 1));
+ soundPoolMap.put(2, sndPool.load(context.getResources().openRawResourceFd(R.raw.shake_nomatch), 1));
+ } catch (NullPointerException e) {
+ }
+ }
+ }.start();
+ }
+
+ /** 播放摇一摇开始声音 */
+ public void playStartSound() {
+ try {
+ sndPool.play(soundPoolMap.get(0), (float) 1, (float) 1, 1, 0, (float) 1.2);// 播放摇一摇过程声音
+ } catch (NullPointerException e) {
+ } catch (IndexOutOfBoundsException e) {
+ }
+ }
+
+ /** 播放摇一摇结束声音 */
+ public void playEndSound() {
+ try {
+ //该方法的第一个参数指定播放哪个声音; leftVolume 、 rightVolume 指定左、右的音量: priority 指定播放声音的优先级,数值越大,优先级越高; loop 指定是否循环, 0 为不循环, -1 为循环; rate 指定播放的比率,数值可从 0.5 到 2 , 1 为正常比率。
+ sndPool.play(soundPoolMap.get(1), (float) 1, (float) 1, 0, 0, (float) 1.0);// 播放摇一摇结果声音
+ } catch (NullPointerException e) {
+ } catch (IndexOutOfBoundsException e) {
+ }
+ }
+
+ /** 什么都没摇到 */
+ public void playNothingSound() {
+ try {
+ sndPool.play(soundPoolMap.get(2), (float) 1, (float) 1, 0, 0, (float) 1.0);// 播放摇一摇结果声音
+ } catch (NullPointerException e) {
+ } catch (IndexOutOfBoundsException e) {
+ }
+ }
+}
diff --git a/app/src/main/java/com/ceshi/tsgame/shake/ShakeListener.java b/app/src/main/java/com/ceshi/tsgame/shake/ShakeListener.java
new file mode 100644
index 0000000..77f49d6
--- /dev/null
+++ b/app/src/main/java/com/ceshi/tsgame/shake/ShakeListener.java
@@ -0,0 +1,108 @@
+package com.ceshi.tsgame.shake;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+/**
+ * 检测手机摇晃的监听器
+ */
+public class ShakeListener implements SensorEventListener {
+ // 速度阈值,当摇晃速度达到这值后产生作用
+ private static final int SPEED_SHRESHOLD = 3500;
+ // 两次检测的时间间隔
+ private static final int UPTATE_INTERVAL_TIME = 70;
+ // 传感器管理器
+ private SensorManager sensorManager;
+ // 重力传感器
+ private Sensor sensor;
+ // 重力感应监听器
+ private OnShakeListenerCallBack onShakeListener;
+ // 上下文
+ private Context mContext;
+ // 手机上一个位置时重力感应坐标
+ private float lastX;
+ private float lastY;
+ private float lastZ;
+ // 上次检测时间
+ private long lastUpdateTime;
+
+ // 构造器
+ public ShakeListener(Context context) {
+ // 获得监听对象
+ mContext = context;
+ start();
+ }
+
+ /**开始重力传感器的检测*/
+ public void start() {
+ // 获得传感器管理器
+ sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ if (sensorManager != null) {
+ // 获得重力传感器
+ sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ }
+ // 注册
+ if (sensor != null) {
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
+ }
+ }
+
+ /**停止检测*/
+ public void stop() {
+ sensorManager.unregisterListener(this);
+ }
+
+ /**重力感应器感应获得变化数据*/
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ // 现在检测时间
+ long currentUpdateTime = System.currentTimeMillis();
+ // 两次检测的时间间隔
+ long timeInterval = currentUpdateTime - lastUpdateTime;
+ // 判断是否达到了检测时间间隔
+ if (timeInterval < UPTATE_INTERVAL_TIME)
+ return;
+ // 现在的时间变成last时间
+ lastUpdateTime = currentUpdateTime;
+
+ // 获得x,y,z坐标
+ float x = event.values[0];
+ float y = event.values[1];
+ float z = event.values[2];
+
+ // 获得x,y,z的变化值
+ float deltaX = x - lastX;
+ float deltaY = y - lastY;
+ float deltaZ = z - lastZ;
+
+ // 将现在的坐标变成last坐标
+ lastX = x;
+ lastY = y;
+ lastZ = z;
+
+ double speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) / timeInterval * 10000;
+ // 达到速度阀值,发出提示
+ if (speed >= SPEED_SHRESHOLD) {
+ onShakeListener.onShake();
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+
+ /**摇晃监听接口*/
+ public interface OnShakeListenerCallBack {
+ public void onShake();
+ }
+
+ /**设置重力感应监听器*/
+ public void setOnShakeListener(OnShakeListenerCallBack listener) {
+ onShakeListener = listener;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/game/webgame/network/CustomStringRequest.java b/app/src/main/java/com/game/webgame/network/CustomStringRequest.java
new file mode 100644
index 0000000..313deb1
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/CustomStringRequest.java
@@ -0,0 +1,50 @@
+package com.game.webgame.network;
+import java.util.HashMap;
+import java.util.Map;
+
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.NetworkResponse;
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.Response.ErrorListener;
+/**
+ * Created by Administrator on 2017/4/24.
+ */
+
+public class CustomStringRequest extends Request{
+ private Map mHeaders=new HashMap();
+
+ public CustomStringRequest(String url, ErrorListener listener) {
+ super(url, listener);
+
+ }
+
+ public CustomStringRequest(int method, String url, ErrorListener listener) {
+ super(method, url, listener);
+
+ }
+
+ @Override
+ protected void deliverResponse(String arg0) {
+
+ }
+
+ @Override
+ protected Response parseNetworkResponse(NetworkResponse Response) {
+ //在这里可以的到 response 对象, 然后从response的header中获取cookie信息
+
+ return null;
+ }
+
+ public void setCookie(String cookie) {
+
+ mHeaders.put("Cookie", cookie);
+ }
+
+ @Override
+ public Map getHeaders() throws AuthFailureError {
+
+ return mHeaders;
+ }
+}
diff --git a/app/src/main/java/com/game/webgame/network/HttpUtil.java b/app/src/main/java/com/game/webgame/network/HttpUtil.java
new file mode 100644
index 0000000..26d0fe3
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/HttpUtil.java
@@ -0,0 +1,317 @@
+package com.game.webgame.network;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.util.EntityUtils;
+import com.http.xml.callback.MyHttpCallBack;
+import android.accounts.NetworkErrorException;
+import android.os.AsyncTask;
+
+import okhttp3.Call;
+import okhttp3.FormBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+/**
+ * Created by Administrator on 2017/4/24.
+ */
+
+public class HttpUtil {
+ private MyHttpCallBack callback;
+ private String method;
+ private Map map;
+ private String path;
+ @SuppressWarnings(value = "deprecation")
+ private static HttpClient client;
+
+ private static OkHttpClient okHttpClient;
+
+
+ public static HttpClient gethttp(){
+ if(client==null){
+ client=new DefaultHttpClient();
+ }
+
+ return client;
+
+ }
+ public static OkHttpClient getOkHttpClientttp(){
+ if(okHttpClient==null){
+ okHttpClient=new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
+ }
+
+ return okHttpClient;
+
+ }
+ private HttpUtil() {
+ // TODO Auto-generated constructor stub
+ }
+
+ public HttpUtil(MyHttpCallBack callback,String method,String path,Map map) {
+ this.callback = callback;
+ this.method = method;
+ this.path = path;
+ this.map = map;
+ }
+
+ public void startRequest(){
+ HttpAsyncTask task = new HttpAsyncTask();
+ task.execute();
+ }
+
+ /**
+ * http网络请求,
+ * 两种方式:GET/POST
+ * 方法:很多,1)原生的请求,androidsdk自带的java网络请求类--HttpUrlConnection;
+ * 2)第三方封装的请求:HttpClient(android自带的); NoHttp;volley;OkHttp;XUtil;
+ * @throws Exception
+ */
+
+ public String doGetByHttpUrlConnection(String path,Map map) throws Exception{
+ String params = getParamsFromMap(map);
+ path = path + "?"+params;
+ URL url = new URL(path);
+ //打开连接
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ //设置连接超时时间
+ conn.setConnectTimeout(5000);
+ //设置请求方式
+ conn.setRequestMethod("GET");
+ //判断是否能够连接成功--响应码(200成功;300没有权限;400没有资源--可能是路径不对;
+ //500服务器内部错误----一般是传参名字或者类型出错了or是服务器本身报错)
+ int responseCode = conn.getResponseCode();
+ System.out.println("responseCode:"+responseCode);
+ if(responseCode==200){//ok
+ InputStream is = conn.getInputStream();
+ String result = getStringFromStream(is);
+ return result;
+ }else{//连接出错
+ throw new NetworkErrorException();
+ }
+ }
+
+ private String getStringFromStream(InputStream is) throws Exception{
+ byte[] buffer = new byte[1024];
+ int len = 0;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ while((len = is.read(buffer))!=-1){
+ baos.write(buffer, 0, len);
+ }
+ String result = baos.toString();
+ baos.close();
+ is.close();
+ return result;
+ }
+
+ public String doPostByHttpUrlConnection(String path,Map map) throws Exception{
+ URL url = new URL(path);
+ //打开连接
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ //设置连接超时时间
+ conn.setConnectTimeout(5000);
+ //设置请求方式
+ conn.setRequestMethod("POST");
+ //将参数一起通过post方式传过去,然后再拿响应
+ OutputStream os = conn.getOutputStream();
+ //username=yolanda&pwd=nohttp
+ String params = getParamsFromMap(map);
+ os.write(params.getBytes());
+ int responseCode = conn.getResponseCode();
+ System.out.println("responseCode:"+responseCode);
+ if(responseCode==200){//ok
+ InputStream is = conn.getInputStream();
+ String result = getStringFromStream(is);
+ return result;
+ }else{//连接出错
+ throw new NetworkErrorException();
+ }
+ }
+
+ private String getParamsFromMap(Map map){
+ StringBuffer buffer = new StringBuffer();
+ for (Map.Entry entry : map.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ buffer.append(key).append("=").append(value).append("&");
+ }
+ buffer.deleteCharAt(buffer.length()-1);
+ return buffer.toString();
+ }
+
+ /**
+ * 第二种方式 HTTPCLIENT
+ * @return
+ * @throws Exception
+ * @throws ClientProtocolException
+ */
+
+ @SuppressWarnings("deprecation")
+ public String doGetByHttpClient(String path,Map map) throws ClientProtocolException, Exception{
+ if(map!=null){
+ String params = getParamsFromMap(map);
+ System.out.println("path:"+path);
+ path = path + "?"+params;
+ }
+ System.out.println(path);
+ //得到一个get请求
+ HttpGet get = new HttpGet(path);
+ //得到一个http请求客户端
+ HttpClient client =new DefaultHttpClient();
+ //设置请求超时时间5s
+ get.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);
+ HttpResponse response = client.execute(get);
+ System.out.println("-----"+path);
+ //状态码--响应码
+ int statusCode = response.getStatusLine().getStatusCode();
+
+ System.out.println(statusCode);
+
+ if(statusCode==200){//ok
+// InputStream is = response.getEntity().getContent();
+// String result = getStringFromStream(is);
+ HttpEntity entity = response.getEntity();
+ String result = EntityUtils.toString(entity);
+ System.out.println(result);
+ return result;
+ }else{//连接出错
+ throw new NetworkErrorException();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public String doPostByHttpClient(String path,Map map) throws Exception{
+ //得到一个http请求客户端
+
+ HttpClient client = gethttp();
+
+ HttpPost post = new HttpPost(path);
+ //设置post请求参数
+ List parameters = new ArrayList();
+ for (Map.Entry entry : map.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ BasicNameValuePair pair = new BasicNameValuePair(key, value);
+ parameters.add(pair );
+ }
+ UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");
+ post.setEntity(entity);
+ HttpResponse response = client.execute(post);
+ //状态码--响应码
+ int statusCode = response.getStatusLine().getStatusCode();
+ if(statusCode==200){//ok
+// InputStream is = response.getEntity().getContent();
+// String result = getStringFromStream(is);
+
+ HttpEntity responseEntity = response.getEntity();
+ String result = EntityUtils.toString(responseEntity);
+ return result;
+ }else{//连接出错
+ throw new NetworkErrorException();
+ }
+ }
+
+ public String doPostByOkHttpClient(String path,Map map) throws Exception{
+ //得到一个http请求客户端
+
+ OkHttpClient client = getOkHttpClientttp();
+
+// HttpPost post = new HttpPost(path);
+ FormBody.Builder builder = new FormBody.Builder();
+// FormBody body = new FormBody.Builder();
+ //设置post请求参数
+ List parameters = new ArrayList();
+ for (Map.Entry entry : map.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ BasicNameValuePair pair = new BasicNameValuePair(key, value);
+ parameters.add(pair );
+ builder.add(key,value);
+ }
+ RequestBody requestBodyPost = builder.build();
+ Request requestPost = new Request.Builder()
+ .url(path)
+ .post(requestBodyPost)
+ .build();
+// UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");
+// post.setEntity(entity);
+// HttpResponse response = client.execute(post);
+ Call call = client.newCall(requestPost);
+ Response response = call.execute();
+ //状态码--响应码
+// int statusCode = response.getStatusLine().getStatusCode();
+ int statusCode = response.code();
+ if(statusCode==200){//ok
+// InputStream is = response.getEntity().getContent();
+// String result = getStringFromStream(is);
+
+// HttpEntity responseEntity = response.getEntity();
+// String result = EntityUtils.toString(responseEntity);
+ return response.body().string();
+ }else{//连接出错
+ throw new NetworkErrorException();
+ }
+ }
+ class HttpAsyncTask extends AsyncTask{
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ }
+
+ @Override
+ protected String doInBackground(Void... params) {
+ //登陆
+ try {
+
+ String result = null;
+ if("GET".equals(method)){
+ System.out.println("path:"+path);
+ result = doGetByHttpClient(path, map);
+ }else{
+// Ayni OkHttp doPostByOkHttpClient
+// result = doPostByHttpClient(path, map);
+ result = doPostByOkHttpClient(path, map);
+
+ }
+ return result;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ super.onPostExecute(result);
+ if(result!=null){
+ if("failed".equals(result)){
+ callback.failed(result);
+ }else{
+ callback.success(result);
+ }
+ }
+ }
+
+ }
+}
diff --git a/app/src/main/java/com/game/webgame/network/ImageLoader.java b/app/src/main/java/com/game/webgame/network/ImageLoader.java
new file mode 100644
index 0000000..3b1b44c
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/ImageLoader.java
@@ -0,0 +1,134 @@
+package com.game.webgame.network;
+
+
+import com.android.volley.toolbox.ImageLoader.ImageListener;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.v4.util.LruCache;
+import android.widget.ImageView;
+
+public class ImageLoader {
+ // 导包记得使用v4兼容包会兼容低版本
+ /**
+ * 图片缓存核心类(将最近不怎么使用的图片回收;尽量保持最近使用比较多的)
+ */
+ private static LruCache mMemoryCache;
+
+ private static ImageLoader mImageLoader;
+
+ private ImageLoader() {
+ // 获取系统分配给每个应用程序的最大内存,每个应用系统分配32M
+ // int maxMemory = (int) Runtime.getRuntime().maxMemory();
+ // int mCacheSize = maxMemory / 8;
+ // 给LruCache分配1/8 4M
+ // maxSize最多缓存多大内存(获得应用程序最大可用内存/8)
+
+ int maxMemory = (int) Runtime.getRuntime().maxMemory();//返回 Java
+ // 虚拟机试图使用的最大内存量
+ System.out.println("Runtime.getRuntime().maxMemory()=="
+ + Runtime.getRuntime().maxMemory());
+ System.out.println("Runtime.getRuntime().freeMemory()=="
+ + Runtime.getRuntime().freeMemory());// kb返回 Java 虚拟机中的空闲内存量
+ int maxSize = maxMemory / 8;
+
+ mMemoryCache = new LruCache(maxSize) {
+ @Override
+ protected int sizeOf(String key, Bitmap bitmap) {
+ // 重写此方法来衡量每一张图片的大小,默认返回图片的数量大小
+ return bitmap.getRowBytes() * bitmap.getHeight() / 1024;// 这里是按多少KB来算
+ // 之前是B
+ // byte
+ }
+ };
+ }
+
+ // 单例模式,防止mMemoryCache出现多个
+
+ public static ImageLoader getIntance() {
+ if (mImageLoader == null) {
+ mImageLoader = new ImageLoader();
+ }
+ return mImageLoader;
+ }
+
+ /**
+ * 将一张图片缓存到LruCache中 将图片的路径作为图片缓存的key
+ */
+ public void addBitmapToMemoryCache(String path, Bitmap bitmap) {
+ if (getBitmapFromMemoryCache(path) == null) {
+ mMemoryCache.put(path, bitmap);
+ }
+ }
+
+ /**
+ * 从LrcCache里面取一张图片 读需要图片的路径作为key
+ */
+ public Bitmap getBitmapFromMemoryCache(String path) {
+ return mMemoryCache.get(path);
+ }
+
+ /**
+ * 图片压缩 读取一个图片文件,通过压缩算法压缩后,生成一个Bitmap返回
+ */
+ public static Bitmap decodeBitmapFromResource(String iconPath, int reqWidth) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ // 1.得到原始图片的宽度
+
+ // 不读取图片本身,而是只解析图片的宽高。只是读图片大小,不申请bitmap内存 在decode时将会返回null,
+ options.inJustDecodeBounds = true;
+
+ BitmapFactory.decodeFile(iconPath, options);
+ //BitmapFactory.decodeResource(context.getResources(), id)
+ //BitmapFactory.decodeResource(context.getResources(), R.drawable.applogo, options);
+ // ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ // image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+ // if( baos.toByteArray().length / 1024>1024)
+ // {//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
+ // baos.reset();//重置baos即清空baos
+ // image.compress(Bitmap.CompressFormat.JPEG, 50,
+ // baos);//这里压缩50%,把压缩后的数据存放到baos中
+ // }
+ // ByteArrayInputStream isBm = new
+ // ByteArrayInputStream(baos.toByteArray());
+ // BitmapFactory.decodeStream(is, outPadding, opts);
+ int width = options.outWidth;
+ int height = options.outHeight;
+ int inSampleSize = 1;
+ if (width > reqWidth) {
+ // 计算出实际宽度和目标宽度的比率
+ int widthRatio = Math.round((float) width / (float) reqWidth);
+ inSampleSize = widthRatio;
+ }
+ // 2.压缩后的图片宽度reqWidth
+ // 开始压缩图片
+ // 读图片本身了//设为false,这次不是预读取图片大小,而是返回申请内存,bitmap数据
+ options.inJustDecodeBounds = false;
+ // 设置压缩比例(比如:2,宽和高一起压缩1/2)
+ options.inSampleSize = inSampleSize;// 倍数缩放
+ return BitmapFactory.decodeFile(iconPath, options);
+ }
+ /**
+ * 图片压缩 读取一个图片文件,通过压缩算法压缩后,生成一个Bitmap返回
+ */
+ public static Bitmap decodeBitmapFromResource(Context context,int id, int reqWidth) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(context.getResources(), id, options);
+ int width = options.outWidth;
+ int height = options.outHeight;
+ int inSampleSize = 1;
+ if (width > reqWidth) {
+ int widthRatio = Math.round((float) width / (float) reqWidth);
+ inSampleSize = widthRatio;
+ }
+ options.inJustDecodeBounds = false;
+ // 设置压缩比例(比如:2,宽和高一起压缩1/2)
+ options.inSampleSize = inSampleSize;// 倍数缩放
+ return BitmapFactory.decodeResource(context.getResources(), id, options);
+ }
+
+
+}
diff --git a/app/src/main/java/com/game/webgame/network/JsonObjectPostRequest.java b/app/src/main/java/com/game/webgame/network/JsonObjectPostRequest.java
new file mode 100644
index 0000000..abcad2b
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/JsonObjectPostRequest.java
@@ -0,0 +1,98 @@
+package com.game.webgame.network;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.util.Log;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.NetworkResponse;
+import com.android.volley.ParseError;
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.Response.ErrorListener;
+import com.android.volley.toolbox.HttpHeaderParser;
+
+public class JsonObjectPostRequest extends Request {
+
+// public JsonObjectPostRequest(String url, ErrorListener listener) {
+// super(url, listener);
+// // TODO Auto-generated constructor stub
+// }
+//
+// public JsonObjectPostRequest(int method, String url, ErrorListener listener) {
+// super(method, url, listener);
+// // TODO Auto-generated constructor stub
+// }
+
+ public JsonObjectPostRequest(String url,
+ Response.Listener listener,
+ Response.ErrorListener errorListener, Map map) {
+ super(Request.Method.POST, url, errorListener);
+ mListener = listener;
+ mMap = map;
+ }
+
+ private Map mMap;
+ private Response.Listener mListener;
+ public String cookieFromResponse;
+ private String mHeader;
+ private Map sendHeader = new HashMap(1);
+
+
+ @Override
+ protected Map getParams() throws AuthFailureError {
+ return mMap;
+ }
+
+ @Override
+ protected Response parseNetworkResponse(NetworkResponse response) {
+ try {
+ String jsonString = new String(response.data,
+ HttpHeaderParser.parseCharset(response.headers));
+ mHeader = response.headers.toString();
+ Log.w("LOG", "get headers in parseNetworkResponse "
+ + response.headers.toString());
+
+ Pattern pattern = Pattern.compile("Set-Cookie.*?;");
+ Matcher m = pattern.matcher(mHeader);
+ if (m.find()) {
+ cookieFromResponse = m.group();
+ Log.w("LOG", "cookie from server " + cookieFromResponse);
+ }
+ cookieFromResponse = cookieFromResponse.substring(11,
+ cookieFromResponse.length() - 1);
+ Log.w("LOG", "cookie substring " + cookieFromResponse);
+ JSONObject jsonObject = new JSONObject(jsonString);
+ jsonObject.put("Cookie", cookieFromResponse);
+ Log.w("LOG", "jsonObject " + jsonObject.toString());
+ return Response.success(jsonObject,
+ HttpHeaderParser.parseCacheHeaders(response));
+ } catch (UnsupportedEncodingException e) {
+ return Response.error(new ParseError(e));
+ } catch (JSONException je) {
+ return Response.error(new ParseError(je));
+ }
+ }
+
+ @Override
+ protected void deliverResponse(JSONObject response) {
+ mListener.onResponse(response);
+ }
+
+ @Override
+ public Map getHeaders() throws AuthFailureError {
+ return sendHeader;
+ }
+
+ public void setSendCookie(String cookie) {
+ sendHeader.put("Cookie", cookie);
+ }
+
+}
diff --git a/app/src/main/java/com/game/webgame/network/PreferencesCookieStore.java b/app/src/main/java/com/game/webgame/network/PreferencesCookieStore.java
new file mode 100644
index 0000000..e6cc095
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/PreferencesCookieStore.java
@@ -0,0 +1,218 @@
+package com.game.webgame.network;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.http.client.CookieStore;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.impl.cookie.BasicClientCookie;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+/**
+ * Created by Administrator on 2017/4/24.
+ */
+
+public class PreferencesCookieStore implements CookieStore{
+ private static final String COOKIE_PREFS = "CookiePrefsFile";
+ private static final String COOKIE_NAME_STORE = "names";
+ private static final String COOKIE_NAME_PREFIX = "cookie_";
+
+ private final ConcurrentHashMap cookies;
+ private final SharedPreferences cookiePrefs;
+
+ /**
+ * Construct a persistent cookie store.
+ */
+ public PreferencesCookieStore(Context context) {
+ cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
+ cookies = new ConcurrentHashMap();
+
+ // Load any previously stored cookies into the store
+ String storedCookieNames = cookiePrefs.getString(COOKIE_NAME_STORE, null);
+ if(storedCookieNames != null) {
+ String[] cookieNames = TextUtils.split(storedCookieNames, ",");
+ for(String name : cookieNames) {
+ String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
+ if(encodedCookie != null) {
+ Cookie decodedCookie = decodeCookie(encodedCookie);
+ if(decodedCookie != null) {
+ cookies.put(name, decodedCookie);
+ }
+ }
+ }
+
+ // Clear out expired cookies
+ clearExpired(new Date());
+ }
+ }
+
+ @Override
+ public void addCookie(Cookie cookie) {
+ String name = cookie.getName();
+
+ // Save cookie into local store, or remove if expired
+ if(!cookie.isExpired(new Date())) {
+ cookies.put(name, cookie);
+ } else {
+ cookies.remove(name);
+ }
+
+ // Save cookie into persistent store
+ SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
+ prefsWriter.putString(COOKIE_NAME_STORE, TextUtils.join(",", cookies.keySet()));
+ prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableCookie(cookie)));
+ prefsWriter.commit();
+ }
+
+ @Override
+ public void clear() {
+ // Clear cookies from local store
+ cookies.clear();
+
+ // Clear cookies from persistent store
+ SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
+ for(String name : cookies.keySet()) {
+ prefsWriter.remove(COOKIE_NAME_PREFIX + name);
+ }
+ prefsWriter.remove(COOKIE_NAME_STORE);
+ prefsWriter.commit();
+ }
+
+ @Override
+ public boolean clearExpired(Date date) {
+ boolean clearedAny = false;
+ SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
+
+ for(ConcurrentHashMap.Entry entry : cookies.entrySet()) {
+ String name = entry.getKey();
+ Cookie cookie = entry.getValue();
+ if(cookie.isExpired(date)) {
+ // ���cookies
+ cookies.remove(name);
+
+ // Clear cookies from persistent store
+ prefsWriter.remove(COOKIE_NAME_PREFIX + name);
+
+ // We've cleared at least one
+ clearedAny = true;
+ }
+ }
+
+ // Update names in persistent store
+ if(clearedAny) {
+ prefsWriter.putString(COOKIE_NAME_STORE, TextUtils.join(",", cookies.keySet()));
+ }
+ prefsWriter.commit();
+
+ return clearedAny;
+ }
+
+ @Override
+ public List getCookies() {
+ return new ArrayList(cookies.values());
+ }
+
+
+
+ protected String encodeCookie(SerializableCookie cookie) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream outputStream = new ObjectOutputStream(os);
+ outputStream.writeObject(cookie);
+ } catch (Exception e) {
+ return null;
+ }
+
+ return byteArrayToHexString(os.toByteArray());
+ }
+
+ protected Cookie decodeCookie(String cookieStr) {
+ byte[] bytes = hexStringToByteArray(cookieStr);
+ ByteArrayInputStream is = new ByteArrayInputStream(bytes);
+ Cookie cookie = null;
+ try {
+ ObjectInputStream ois = new ObjectInputStream(is);
+ cookie = ((SerializableCookie)ois.readObject()).getCookie();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return cookie;
+ }
+
+ // Using some super basic byte array <-> hex conversions so we don't have
+ // to rely on any large Base64 libraries. Can be overridden if you like!
+ protected String byteArrayToHexString(byte[] b) {
+ StringBuffer sb = new StringBuffer(b.length * 2);
+ for (byte element : b) {
+ int v = element & 0xff;
+ if(v < 16) {
+ sb.append('0');
+ }
+ sb.append(Integer.toHexString(v));
+ }
+ return sb.toString().toUpperCase();
+ }
+
+ protected byte[] hexStringToByteArray(String s) {
+ int len = s.length();
+ byte[] data = new byte[len / 2];
+ for(int i=0; i mlistener;
+ public static ErrorListener mErrorLisener;
+
+ public Volleyinterface(Context context, Listener mlistener,
+ ErrorListener errorlisener) {
+
+ this.mcontext = context;
+ this.mErrorLisener = errorlisener;
+ this.mlistener = mlistener;
+
+ }
+
+ public abstract void onsuccess(String result);
+
+ public abstract void onerror(VolleyError arg0);
+
+ public Listener loadinglisener() {
+ mlistener = new Listener() {
+
+ @Override
+ public void onResponse(JSONObject arg0) {
+ //System.out.println(arg0);
+ onsuccess(arg0.toString());
+ }
+ };
+ return mlistener;
+ }
+
+ public ErrorListener errorListener() {
+ mErrorLisener = new ErrorListener() {
+
+ @Override
+ public void onErrorResponse(VolleyError arg0) {
+ // System.out.println(arg0.toString());
+ onerror(arg0);
+
+ }
+
+ };
+ return mErrorLisener;
+ }
+}
diff --git a/app/src/main/java/com/game/webgame/network/Volleyinterface1.java b/app/src/main/java/com/game/webgame/network/Volleyinterface1.java
new file mode 100644
index 0000000..3c0266a
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/Volleyinterface1.java
@@ -0,0 +1,44 @@
+package com.game.webgame.network;
+import android.content.Context;
+import com.android.volley.VolleyError;
+import com.android.volley.Response.ErrorListener;
+import com.android.volley.Response.Listener;
+
+public abstract class Volleyinterface1 {
+ public Context mcontext;
+ public static Listener mlistener;
+ public static ErrorListener mErrorLisener;
+
+ public Volleyinterface1(Context context, Listener mlistener,
+ ErrorListener errorlisener) {
+ this.mcontext = context;
+ this.mErrorLisener = errorlisener;
+ this.mlistener = mlistener;
+
+ }
+
+ public abstract void onsuccess(String result);
+
+ public abstract void onerror(VolleyError arg0);
+
+ public Listener loadinglisener() {
+ mlistener = new Listener() {
+
+ @Override
+ public void onResponse(String arg0) {
+ onsuccess(arg0.toString());
+ }
+ };
+ return mlistener;
+ }
+ public ErrorListener errorListener() {
+ mErrorLisener = new ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError arg0) {
+ onerror(arg0);
+ }
+
+ };
+ return mErrorLisener;
+ }
+}
diff --git a/app/src/main/java/com/game/webgame/network/httpxutils.java b/app/src/main/java/com/game/webgame/network/httpxutils.java
new file mode 100644
index 0000000..6d745b7
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/httpxutils.java
@@ -0,0 +1,39 @@
+package com.game.webgame.network;
+
+import com.lidroid.xutils.HttpUtils;
+import com.lidroid.xutils.exception.HttpException;
+import com.lidroid.xutils.http.ResponseInfo;
+import com.lidroid.xutils.http.callback.RequestCallBack;
+import com.lidroid.xutils.http.client.HttpRequest;
+import com.tsgame.tsgame_niuniu.system.Myapplication;
+
+
+public class httpxutils {
+public void xutils(){
+ HttpUtils http = new HttpUtils();
+ http.configCookieStore(new PreferencesCookieStore(Myapplication.application));
+
+ http.send(HttpRequest.HttpMethod.POST, "", new RequestCallBack() {
+ @Override
+ public void onLoading(long total, long current, boolean isUploading) {
+ super.onLoading(total, current, isUploading);
+ }
+ @Override
+ public void onStart() {
+
+ super.onStart();
+ }
+ @Override
+ public void onFailure(HttpException arg0, String arg1) {
+
+ }
+
+ @Override
+ public void onSuccess(ResponseInfo arg0) {
+
+
+ }
+ });
+
+}
+}
diff --git a/app/src/main/java/com/game/webgame/network/register.java b/app/src/main/java/com/game/webgame/network/register.java
new file mode 100644
index 0000000..8352b0d
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/register.java
@@ -0,0 +1,150 @@
+package com.game.webgame.network;
+import java.util.Map;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import com.android.volley.Request.Method;
+import com.android.volley.AuthFailureError;
+import com.android.volley.DefaultRetryPolicy;
+import com.android.volley.Response;
+import com.android.volley.Response.Listener;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.StringRequest;
+
+public class register {
+
+ public static void PostJsonrequest(Map map, String url,
+ Volleyinterface inface) {
+ JSONObject jsonObject = new JSONObject(map);
+ System.out.println(jsonObject.toString());
+
+ JsonObjectRequest JsonObject1 = new JsonObjectRequest(Method.POST, url,
+ jsonObject, inface.loadinglisener(), inface.errorListener());
+ JsonObject1.setShouldCache(false);
+ volleymanager.getInstance().getmRequestQueue().getCache().remove(url);
+ volleymanager.getInstance().getmRequestQueue().add(JsonObject1);
+ }
+
+ public static void PostJsonrequest1(String json, String url,
+ Volleyinterface inface) {
+ JSONObject jsonObject=null;
+ try {
+ if(json!=null){
+ jsonObject = new JSONObject(json);
+ }
+
+ JsonObjectRequest JsonObject1 = new JsonObjectRequest(Method.POST, url,
+ jsonObject, inface.loadinglisener(), inface.errorListener());
+ JsonObject1.setShouldCache(false);
+ volleymanager.getInstance().getmRequestQueue().getCache().remove(url);
+
+ JsonObject1.setRetryPolicy(new DefaultRetryPolicy(
+ 12000,
+ DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
+ DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
+
+ volleymanager.getInstance().getmRequestQueue().add(JsonObject1);
+
+ } catch (JSONException e) {
+
+ e.printStackTrace();
+ }
+ //JSONObject jsonObject = new JSONObject(map);
+
+ }
+
+ public static void webservers(Map map, String url,
+ Volleyinterface inface) {
+ JsonObjectRequest JsonObject;
+ if (map != null) {
+ JSONObject jsonObject = new JSONObject(map);
+ JsonObject = new JsonObjectRequest(Method.POST, url, jsonObject,
+ inface.loadinglisener(), inface.errorListener());
+ } else {
+ JsonObject = new JsonObjectRequest(Method.POST, url, null,
+ inface.loadinglisener(), inface.errorListener());
+ }
+ JsonObject.setShouldCache(false);
+ volleymanager.getInstance().getmRequestQueue().getCache().remove(url);
+ volleymanager.getInstance().getmRequestQueue().add(JsonObject);
+
+ }
+
+ public static void Stringpostrequest(final Map map,
+ String url) {
+
+ StringRequest string = new StringRequest(Method.POST, url,
+ new Listener() {
+
+ @Override
+ public void onResponse(String arg0) {
+ System.out.println(arg0);
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError arg0) {
+
+ }
+ }) {
+ @Override
+ protected Map getParams() throws AuthFailureError {
+ return map;
+ }
+ };
+ string.setShouldCache(false);
+ volleymanager.getInstance().getmRequestQueue().add(string);
+ }
+
+ public static void StringGETrequest(final Map map,
+ String url,Volleyinterface1 inface) {
+
+ if(map!=null){
+ url = url + "?" + getParamsFromMap(map);
+ }
+
+ StringRequest string = new StringRequest(Method.GET, url,inface.loadinglisener(), inface.errorListener());
+ string.setShouldCache(false);
+
+ volleymanager.getInstance().getmRequestQueue().add(string);
+ }
+
+ public static void Getjsonrequest(Map map, String url) {
+
+
+ url = url + "?" + getParamsFromMap(map);
+ System.out.println(url);
+
+ JsonObjectRequest JsonObject = new JsonObjectRequest(Method.GET, url,
+ null, new Response.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject1) {
+
+ if (jsonObject1 == null) {
+ System.out.println("返回值为空");
+ }
+
+ System.out.println(jsonObject1.toString());
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError arg0) {
+
+ }
+ });
+ JsonObject.setShouldCache(false);
+ // Myapplication.getqueue().getCache().remove(url);
+ volleymanager.getInstance().getmRequestQueue().add(JsonObject);
+ }
+
+ private static String getParamsFromMap(Map map) {
+ StringBuffer buffer = new StringBuffer();
+ for (Map.Entry entry : map.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ buffer.append(key).append("=").append(value).append("&");
+ }
+ buffer.deleteCharAt(buffer.length() - 1);
+ return buffer.toString();
+ }
+}
diff --git a/app/src/main/java/com/game/webgame/network/volleymanager.java b/app/src/main/java/com/game/webgame/network/volleymanager.java
new file mode 100644
index 0000000..7ae831e
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/volleymanager.java
@@ -0,0 +1,83 @@
+package com.game.webgame.network;
+
+
+
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.CookiePolicy;
+import java.util.prefs.Preferences;
+
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.HttpParams;
+
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.HttpClientStack;
+import com.android.volley.toolbox.HttpStack;
+import com.android.volley.toolbox.Volley;
+import com.tsgame.tsgame_niuniu.system.Myapplication;
+
+import okhttp3.OkHttpClient;
+
+public class volleymanager {
+ private static volleymanager mInstance = null;
+
+ private static RequestQueue mRequestQueue;
+
+ @SuppressLint("NewApi") @SuppressWarnings("deprecation")
+ private volleymanager(Context context) {
+
+
+
+// DefaultHttpClient httpclient = new DefaultHttpClient();
+ OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
+ //�dz־û��洢(�ڴ�洢) BasicCookieStore | �־û��洢 PreferencesCookieStore
+// CookieStore cookieStore = new BasicCookieStore();
+// httpclient.setCookieStore(cookieStore);
+// HttpStack httpStack = new HttpClientStack(httpclient);
+// mRequestQueue = Volley.newRequestQueue(context.getApplicationContext(),httpStack);
+
+
+ CookieManager manager= new CookieManager(new volleypreferencesCookie(context), CookiePolicy.ACCEPT_ALL);
+ CookieHandler.setDefault(manager);
+
+
+
+// DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
+// ClientConnectionManager mClientConnectionManager = defaultHttpClient.getConnectionManager();
+// HttpParams mHttpParams = defaultHttpClient.getParams();
+// ThreadSafeClientConnManager mThreadSafeClientConnManager = new ThreadSafeClientConnManager( mHttpParams,
+// mClientConnectionManager.getSchemeRegistry() );
+// defaultHttpClient = new DefaultHttpClient( mThreadSafeClientConnManager, mHttpParams );
+// CookieStore cookieStore = new PreferencesCookieStore( context );
+// defaultHttpClient.setCookieStore( cookieStore );
+// HttpStack httpStack = new HttpClientStack( defaultHttpClient );
+
+ mRequestQueue =
+ Volley.newRequestQueue(context.getApplicationContext());
+ }
+
+ public static synchronized volleymanager getInstance() {
+ if (mInstance == null) {
+ mInstance = new volleymanager(Myapplication.application);
+ }
+ return mInstance;
+ }
+
+ public RequestQueue getmRequestQueue() {
+ if (mRequestQueue == null) {
+ mRequestQueue = Volley.newRequestQueue(Myapplication.application
+ .getApplicationContext());
+ }
+
+ return mRequestQueue;
+ }
+
+}
diff --git a/app/src/main/java/com/game/webgame/network/volleypreferencesCookie.java b/app/src/main/java/com/game/webgame/network/volleypreferencesCookie.java
new file mode 100644
index 0000000..3f069cc
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/network/volleypreferencesCookie.java
@@ -0,0 +1,227 @@
+package com.game.webgame.network;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+import android.util.Log;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+@SuppressLint("NewApi") public class volleypreferencesCookie implements CookieStore{
+
+ private static final String LOG_TAG = "PersistentCookieStore";
+ private static final String COOKIE_PREFS = "CookiePrefsFile";
+ private static final String COOKIE_NAME_PREFIX = "cookie_";
+
+ private final HashMap> cookies;
+ private final SharedPreferences cookiePrefs;
+
+ /**
+ * Construct a persistent cookie store.
+ *
+ * @param context Context to attach cookie store to
+ */
+ public volleypreferencesCookie(Context context) {
+ cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
+ cookies = new HashMap>();
+
+ // Load any previously stored cookies into the store
+ Map prefsMap = cookiePrefs.getAll();
+ for(Map.Entry entry : prefsMap.entrySet()) {
+ if (((String)entry.getValue()) != null && !((String)entry.getValue()).startsWith(COOKIE_NAME_PREFIX)) {
+ String[] cookieNames = TextUtils.split((String)entry.getValue(), ",");
+ for (String name : cookieNames) {
+ String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
+ if (encodedCookie != null) {
+ HttpCookie decodedCookie = decodeCookie(encodedCookie);
+ if (decodedCookie != null) {
+ if(!cookies.containsKey(entry.getKey()))
+ cookies.put(entry.getKey(), new ConcurrentHashMap());
+ cookies.get(entry.getKey()).put(name, decodedCookie);
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ @Override
+ public void add(URI uri, HttpCookie cookie) {
+ String name = getCookieToken(uri, cookie);
+
+
+ if (!cookie.hasExpired()) {
+ if(!cookies.containsKey(uri.getHost()))
+ cookies.put(uri.getHost(), new ConcurrentHashMap());
+ cookies.get(uri.getHost()).put(name, cookie);
+ } else {
+ if(cookies.containsKey(uri.toString()))
+ cookies.get(uri.getHost()).remove(name);
+ }
+
+ // Save cookie into persistent store
+ SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
+ prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
+ prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableHttpCookie(cookie)));
+ prefsWriter.apply();
+ }
+
+ protected String getCookieToken(URI uri, HttpCookie cookie) {
+ return cookie.getName() + cookie.getDomain();
+ }
+
+ @Override
+ public List get(URI uri) {
+ ArrayList ret = new ArrayList();
+ if(cookies.containsKey(uri.getHost()))
+ ret.addAll(cookies.get(uri.getHost()).values());
+ return ret;
+ }
+
+ @Override
+ public boolean removeAll() {
+ SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
+ prefsWriter.clear();
+ prefsWriter.apply();
+ cookies.clear();
+ return true;
+ }
+
+
+ @Override
+ public boolean remove(URI uri, HttpCookie cookie) {
+ String name = getCookieToken(uri, cookie);
+
+ if(cookies.containsKey(uri.getHost()) && cookies.get(uri.getHost()).containsKey(name)) {
+ cookies.get(uri.getHost()).remove(name);
+
+ SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
+ if(cookiePrefs.contains(COOKIE_NAME_PREFIX + name)) {
+ prefsWriter.remove(COOKIE_NAME_PREFIX + name);
+ }
+ prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
+ prefsWriter.apply();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public List getCookies() {
+ ArrayList ret = new ArrayList();
+ for (String key : cookies.keySet())
+ ret.addAll(cookies.get(key).values());
+
+ return ret;
+ }
+
+ @Override
+ public List getURIs() {
+ ArrayList ret = new ArrayList();
+ for (String key : cookies.keySet())
+ try {
+ ret.add(new URI(key));
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+
+ return ret;
+ }
+
+ /**
+ * Serializes Cookie object into String
+ *
+ * @param cookie cookie to be encoded, can be null
+ * @return cookie encoded as String
+ */
+ protected String encodeCookie(SerializableHttpCookie cookie) {
+ if (cookie == null)
+ return null;
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream outputStream = new ObjectOutputStream(os);
+ outputStream.writeObject(cookie);
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "IOException in encodeCookie " + e.toString());
+ return null;
+ }
+
+ return byteArrayToHexString(os.toByteArray());
+ }
+
+ /**
+ * Returns cookie decoded from cookie string
+ *
+ * @param cookieString string of cookie as returned from http request
+ * @return decoded cookie or null if exception occured
+ */
+ protected HttpCookie decodeCookie(String cookieString) {
+ byte[] bytes = hexStringToByteArray(cookieString);
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
+ HttpCookie cookie = null;
+ try {
+ ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
+ cookie = ((SerializableHttpCookie) objectInputStream.readObject()).getCookie();
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "IOException in decodeCookie " + e.toString());
+ } catch (ClassNotFoundException e) {
+ Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie " + e.toString());
+ }
+
+ return cookie;
+ }
+
+ /**
+ * Using some super basic byte array <-> hex conversions so we don't have to rely on any
+ * large Base64 libraries. Can be overridden if you like!
+ *
+ * @param bytes byte array to be converted
+ * @return string containing hex values
+ */
+ protected String byteArrayToHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder(bytes.length * 2);
+ for (byte element : bytes) {
+ int v = element & 0xff;
+ if (v < 16) {
+ sb.append('0');
+ }
+ sb.append(Integer.toHexString(v));
+ }
+ return sb.toString().toUpperCase(Locale.US);
+ }
+
+ /**
+ * Converts hex values from strings to byte arra
+ *
+ * @param hexString string of hex-encoded values
+ * @return decoded byte array
+ */
+ protected byte[] hexStringToByteArray(String hexString) {
+ int len = hexString.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
+ }
+ return data;
+ }
+
+
+}
diff --git a/app/src/main/java/com/game/webgame/view/ListViewUtils.java b/app/src/main/java/com/game/webgame/view/ListViewUtils.java
new file mode 100644
index 0000000..af5f985
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/view/ListViewUtils.java
@@ -0,0 +1,39 @@
+package com.game.webgame.view;
+
+import android.view.View;
+import android.widget.Adapter;
+import android.widget.ListView;
+
+/**
+ * Created by Administrator on 2017/4/24.
+ */
+
+public class ListViewUtils {
+ //TODO 解决ScollView、GridView及Expandlistview嵌套ListView显示不全问题
+ /*******************************************************************/
+ private int totalHeight = 0;
+
+ public int setListViewHeight(ListView listView){
+ /*得到适配器*/
+ Adapter adapter = listView.getAdapter();
+
+ /*遍历控件*/
+ for (int i = 0; i < adapter .getCount(); i++) {
+ View view = adapter.getView(i, null, listView);
+ /*测量一下子控件的高度*/
+ view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ totalHeight+=view.getMeasuredHeight();
+ }
+
+ /*控件之间的间隙*/
+ totalHeight+=listView.getDividerHeight()*(listView.getCount()-1);
+
+// /*2、赋值给ListView的LayoutParams对象*/
+// ViewGroup.LayoutParams params = listView.getLayoutParams();
+// params.height = totalHeight;
+//
+// listView.setLayoutParams(params);
+ return totalHeight;
+
+ }
+}
diff --git a/app/src/main/java/com/game/webgame/view/SpUtil.java b/app/src/main/java/com/game/webgame/view/SpUtil.java
new file mode 100644
index 0000000..b2281c8
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/view/SpUtil.java
@@ -0,0 +1,72 @@
+package com.game.webgame.view;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+public class SpUtil {
+
+ private static final String NAME = "erqiwang";
+ private static SpUtil instance;
+ static {
+ instance = new SpUtil();
+ }
+
+ public static SpUtil getInstance() {
+ if (instance == null) {
+ instance = new SpUtil();
+ }
+ return instance;
+ }
+
+ public static SharedPreferences getSharePerference(Context context) {
+ return context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
+ }
+
+ public static boolean isFirst(SharedPreferences sp) {
+ return sp.getBoolean("isFirst", false);
+ }
+
+ public static void setStringSharedPerference(SharedPreferences sp,
+ String key, String value) {
+ Editor editor = sp.edit();
+ editor.putString(key, value);
+
+ editor.commit();
+ }
+ public static void setLongSharedPerference(SharedPreferences sp,
+ String key, Long value) {
+ Editor editor = sp.edit();
+ editor.putLong(key, value);
+
+ editor.commit();
+ }
+ public static void setIntSharedPerference(SharedPreferences sp,
+ String key, int value) {
+ Editor editor = sp.edit();
+ editor.putInt(key, value);
+ editor.commit();
+ }
+
+ public static int getIntSharedPerference(SharedPreferences sp,
+ String key) {
+
+ return sp.getInt(key, 0);
+ }
+ public static String getStringSharedPerference(SharedPreferences sp,
+ String key) {
+ return sp.getString(key, null);
+ }
+ public static Long getLongSharedPerference(SharedPreferences sp,
+ String key) {
+ return sp.getLong(key, 0);
+ }
+ public static void setBooleanSharedPerference(SharedPreferences sp,
+ String key, boolean value) {
+ Editor editor = sp.edit();
+ editor.putBoolean(key, value);
+ editor.commit();
+
+ }
+
+}
diff --git a/app/src/main/java/com/game/webgame/view/dialogexit.java b/app/src/main/java/com/game/webgame/view/dialogexit.java
new file mode 100644
index 0000000..8aa5ddb
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/view/dialogexit.java
@@ -0,0 +1,58 @@
+package com.game.webgame.view;
+
+import com.jx.jyhd.R;
+
+import android.content.Context;
+import android.support.v7.app.AlertDialog;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.TextView;
+
+public class dialogexit {
+ public interface onexitlistener {
+ public void paylistener();
+
+ public void cancellistener();
+
+ };
+
+ public static void show(Context con, String content,
+ final onexitlistener listener) {
+ View v = View.inflate(con,R.layout.titledialog, null);
+ TextView content1 = (TextView) v.findViewById(R.id.text);
+ if (!pmutil.isnullorEmpty(content)) {
+ content1.setText(content);
+ }
+
+ TextView confim = (TextView) v.findViewById(R.id.confim);
+ TextView cancel = (TextView) v.findViewById(R.id.cancel);
+ final AlertDialog dialog = new AlertDialog.Builder(con).create();
+ dialog.setCancelable(false);
+ dialog.show();
+ dialog.setContentView(v);
+ confim.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ dialog.dismiss();
+ listener.paylistener();
+
+ }
+ });
+ cancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ dialog.dismiss();
+ listener.cancellistener();
+
+ }
+ });
+ dialog.getWindow().setLayout(pmutil.pmw() / 3 * 2,
+ LayoutParams.WRAP_CONTENT);
+ dialog.getWindow().setBackgroundDrawableResource(R.drawable.dialogexit);
+ }
+
+}
diff --git a/app/src/main/java/com/game/webgame/view/exitTitle.java b/app/src/main/java/com/game/webgame/view/exitTitle.java
new file mode 100644
index 0000000..b7f06e9
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/view/exitTitle.java
@@ -0,0 +1,38 @@
+package com.game.webgame.view;
+
+
+
+
+
+import com.jx.jyhd.R;
+
+import android.content.Context;
+import android.support.v7.app.AlertDialog;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+import android.widget.LinearLayout.LayoutParams;
+
+public class exitTitle {
+ public static void show(Context con, String content) {
+ View v = View.inflate(con, R.layout.hintdialog, null);
+ TextView content1 = (TextView) v.findViewById(R.id.confim);
+ if (!pmutil.isnullorEmpty(content)) {
+ content1.setText(content);
+ }
+ TextView cancel = (TextView) v.findViewById(R.id.exit);
+ final AlertDialog dialog = new AlertDialog.Builder(con).create();
+ dialog.setCancelable(false);
+ dialog.show();
+ dialog.setContentView(v);
+ cancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+ dialog.getWindow().setLayout(pmutil.pmw() / 3 * 2,
+ LayoutParams.WRAP_CONTENT);
+ dialog.getWindow().setBackgroundDrawableResource(R.drawable.dialogexit);
+ }
+}
diff --git a/app/src/main/java/com/game/webgame/view/gameutil.java b/app/src/main/java/com/game/webgame/view/gameutil.java
new file mode 100644
index 0000000..144c95d
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/view/gameutil.java
@@ -0,0 +1,12 @@
+package com.game.webgame.view;
+
+import com.android.volley.RequestQueue;
+
+public class gameutil {
+
+ public static Boolean isAuto=true;
+ public static Boolean islogin=true;
+
+
+
+}
diff --git a/app/src/main/java/com/game/webgame/view/getAppinfo.java b/app/src/main/java/com/game/webgame/view/getAppinfo.java
new file mode 100644
index 0000000..6897167
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/view/getAppinfo.java
@@ -0,0 +1,60 @@
+package com.game.webgame.view;
+
+
+import com.tsgame.tsgame_niuniu.system.Myapplication;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+public class getAppinfo {
+ public static String getAppVersionName() {
+ PackageInfo packageInfo;
+ try {
+ packageInfo = Myapplication.application.getPackageManager()
+ .getPackageInfo(Myapplication.application.getPackageName(),
+ 0);
+ return packageInfo.versionName;
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return "1.0";
+ }
+ public static String getAppName() {
+ PackageInfo packageInfo;
+ try {
+ packageInfo = Myapplication.application.getPackageManager()
+ .getPackageInfo(Myapplication.application.getPackageName(),
+ 0);
+
+ return packageInfo.applicationInfo.loadLabel(Myapplication.application.getPackageManager()).toString();
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return "1.0";
+ }
+ public static int getAppVersioncode() {
+ PackageInfo packageInfo;
+ try {
+ packageInfo = Myapplication.application.getPackageManager().getPackageInfo(Myapplication.application.getPackageName(),0);
+ return packageInfo.versionCode;
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return 1;
+ }
+ public static String getAppos() {
+
+ return "ANDROID";
+ }
+
+ public static Long getTimeStamp() {
+
+ return System.currentTimeMillis();
+ }
+
+ public static String getSign() {
+
+ return "";
+ }
+
+}
diff --git a/app/src/main/java/com/game/webgame/view/pmutil.java b/app/src/main/java/com/game/webgame/view/pmutil.java
new file mode 100644
index 0000000..00872cd
--- /dev/null
+++ b/app/src/main/java/com/game/webgame/view/pmutil.java
@@ -0,0 +1,63 @@
+package com.game.webgame.view;
+
+
+import com.tsgame.tsgame_niuniu.system.Myapplication;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.WindowManager;
+
+public class pmutil {
+ static String userid;
+ static SharedPreferences sp;
+
+ public static int pmw() {
+
+ WindowManager manager = (WindowManager) Myapplication.application
+ .getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics dm = new DisplayMetrics();
+ manager.getDefaultDisplay().getMetrics(dm);
+ int width2 = dm.widthPixels;
+ return width2;
+ }
+
+ public static int pmh() {
+ WindowManager manager = (WindowManager) Myapplication.application
+ .getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics dm = new DisplayMetrics();
+ manager.getDefaultDisplay().getMetrics(dm);
+
+ int height2 = dm.heightPixels;
+ return height2;
+ }
+
+ public static String Appname(){
+
+ return getAppinfo.getAppName();
+
+
+ }
+
+ public static boolean isnullorEmpty(String name){
+ boolean isflag=false;
+ if(name==null||name.trim().length()==0){
+ isflag=true;
+ }
+ return isflag;
+
+ }
+
+ public static int dp2px(int dpval,Context context) {
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ dpval, context.getResources().getDisplayMetrics());
+ }
+
+ public static int sp2px(int spval,Context context) {
+
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+ spval, context.getResources().getDisplayMetrics());
+
+ }
+}
diff --git a/app/src/main/java/com/http/xml/callback/MyHttpCallBack.java b/app/src/main/java/com/http/xml/callback/MyHttpCallBack.java
new file mode 100644
index 0000000..399185d
--- /dev/null
+++ b/app/src/main/java/com/http/xml/callback/MyHttpCallBack.java
@@ -0,0 +1,8 @@
+package com.http.xml.callback;
+
+public interface MyHttpCallBack {
+
+ void success(String result);
+ void failed(String result);
+
+}
diff --git a/app/src/main/java/com/jx/jyhd/MainActivity.java b/app/src/main/java/com/jx/jyhd/MainActivity.java
new file mode 100644
index 0000000..b17e52d
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/MainActivity.java
@@ -0,0 +1,40 @@
+package com.jx.jyhd;
+
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.jx.jyhd.simcpux.Util;
+
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ Util.verifyStoragePermissions(this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+ if (id == R.id.action_settings) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/app/src/main/java/com/jx/jyhd/sgapi/SGEntryActivity.java b/app/src/main/java/com/jx/jyhd/sgapi/SGEntryActivity.java
new file mode 100644
index 0000000..b6ccb32
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/sgapi/SGEntryActivity.java
@@ -0,0 +1,119 @@
+//package com.jx.jyhd.sgapi;
+//
+//import android.app.Activity;
+//import android.content.Intent;
+//import android.os.Bundle;
+//import android.os.Handler;
+//import android.widget.Toast;
+//
+////import org.xianliao.im.sdk.api.ISGAPI;
+////import org.xianliao.im.sdk.api.ISGAPIEventHandler;
+////import org.xianliao.im.sdk.api.SGAPIFactory;
+////import org.xianliao.im.sdk.constants.SGConstants;
+////import org.xianliao.im.sdk.modelbase.BaseReq;
+////import org.xianliao.im.sdk.modelbase.BaseResp;
+////import org.xianliao.im.sdk.modelmsg.SendAuth;
+//
+//
+//
+///**
+// * Created by nickyang on 2017/1/18.
+// *
+// * 此类用于接收从闲聊返回到应用的返回值
+// *
+// * 注意: "sgapi" 目录名和 "SGEntryActivity" 类名都不能改动
+// *
+// */
+//public class SGEntryActivity extends Activity implements ISGAPIEventHandler {
+//
+// ISGAPI api;
+// public static Handler sharehandler;
+// // 分享成功
+// public static final int sharesucces = 2;
+// // 分享失败
+// public static final int sharecancel = 3;
+//
+// @Override
+// protected void onCreate(Bundle savedInstanceState) {
+// super.onCreate(savedInstanceState);
+//
+// //Constants.SG_APPID 修改成自己申请的appId
+// api = SGAPIFactory.createSGAPI(this, XLConstants.SG_APPID);
+//
+// api.handleIntent(getIntent(),this);
+// }
+//
+// @Override
+// protected void onNewIntent(Intent intent) {
+// super.onNewIntent(intent);
+// setIntent(intent);
+// api.handleIntent(intent, this);
+// }
+// public static void setshareHandler(Handler handler) {
+// sharehandler = handler;
+// }
+// @Override
+// public void onReq(BaseReq req) {
+//
+// }
+//
+// @Override
+// public void onResp(BaseResp resp) {
+// switch (resp.getType()){
+// case SGConstants.COMMAND_AUTH: { //授权登陆
+// SendAuth.Resp respAuth = (SendAuth.Resp) resp;
+// if (resp.errCode == SGConstants.ERR_OK) {
+// Toast.makeText(this, "授权登录成功!" + resp.errCode + "\ncode: " + respAuth.code, Toast.LENGTH_SHORT).show();
+// } else if (resp.errCode == SGConstants.ERR_CANCEL) {
+// Toast.makeText(this, "授权登录取消!" + resp.errCode, Toast.LENGTH_SHORT).show();
+// } else if (resp.errCode == SGConstants.ERR_FAIL) {
+// Toast.makeText(this, "授权登录失败!" + resp.errCode, Toast.LENGTH_SHORT).show();
+// }
+//
+// //传递code到其他页面 (可选)
+//// Intent intent = new Intent(this, MainActivity.class);
+//// intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+//// intent.putExtra("code", respAuth.code);
+//// startActivity(intent);
+//
+// break;
+// }
+// case SGConstants.COMMAND_SHARE: { //分享文本,图片,邀请
+// if (resp.errCode == SGConstants.ERR_OK) {
+// sharehandler.sendEmptyMessage(sharesucces);
+//
+// Toast.makeText(this, "分享成功!", Toast.LENGTH_SHORT).show();
+// } else if (resp.errCode == SGConstants.ERR_CANCEL) {
+// sharehandler.sendEmptyMessage(sharecancel);
+// Toast.makeText(this, "分享取消!" , Toast.LENGTH_SHORT).show();
+// } else if (resp.errCode == SGConstants.ERR_FAIL) {
+// sharehandler.sendEmptyMessage(sharecancel);
+// Toast.makeText(this, "分享失败!" , Toast.LENGTH_SHORT).show();
+// }
+// break;
+// }
+// case SGConstants.COMMAND_INVITE: { //从闲聊点击邀请进入应用,
+// /**
+// * 需要Manifest里面配置特殊 intent-filter 才有用,详情参见AndroidManifest
+// */
+//// InvitationResp invitationResp = (InvitationResp) resp;
+//// Toast.makeText(this, "邀请进入: roomId: " + invitationResp.roomId + " roomToken: " + invitationResp.roomToken, Toast.LENGTH_LONG).show();
+////
+//// //传递roomId roomToken到其他页面
+//// Intent intent = new Intent(this, SDKDemoActivity.class);
+//// intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+//// intent.putExtra("roomId", invitationResp.roomId);
+//// intent.putExtra("roomToken", invitationResp.roomToken);
+//// intent.putExtra("openId", invitationResp.openId);
+//// startActivity(intent);
+// break;
+// }
+// }
+// finish();
+// }
+//
+// @Override
+// protected void onResume() {
+// super.onResume();
+// }
+//}
diff --git a/app/src/main/java/com/jx/jyhd/sgapi/XLConstants.java b/app/src/main/java/com/jx/jyhd/sgapi/XLConstants.java
new file mode 100644
index 0000000..1311ff6
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/sgapi/XLConstants.java
@@ -0,0 +1,9 @@
+package com.jx.jyhd.sgapi;
+
+/**
+ * Created by nickyang on 2017/3/17.
+ */
+
+public class XLConstants {
+ public static final String SG_APPID = "DaxGcxAYUJbcDmiK";
+}
diff --git a/app/src/main/java/com/jx/jyhd/simcpux/AppRegister.java b/app/src/main/java/com/jx/jyhd/simcpux/AppRegister.java
new file mode 100644
index 0000000..d96b002
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/simcpux/AppRegister.java
@@ -0,0 +1,19 @@
+package com.jx.jyhd.simcpux;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+
+import com.tencent.mm.opensdk.openapi.IWXAPI;
+import com.tencent.mm.opensdk.openapi.WXAPIFactory;
+
+public class AppRegister extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final IWXAPI api = WXAPIFactory.createWXAPI(context, null);
+ // 注册
+ api.registerApp(Constants.APP_ID);
+ }
+}
diff --git a/app/src/main/java/com/jx/jyhd/simcpux/Constants.java b/app/src/main/java/com/jx/jyhd/simcpux/Constants.java
new file mode 100644
index 0000000..83de045
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/simcpux/Constants.java
@@ -0,0 +1,27 @@
+package com.jx.jyhd.simcpux;
+
+
+public class Constants {
+
+ public static class ShowMsgActivity {
+ public static final String STitle = "showmsg_title";
+ public static final String SMessage = "showmsg_message";
+ public static final String BAThumbData = "showmsg_thumb_data";
+ }
+
+ //appid
+ public static final String APP_ID = "wxd2bd650e06bdfe58";
+
+ public static final int PERMISSIONS_REQUEST_STORAGE = 1;
+
+ //商户号
+ public static final String MCH_ID = "1448669802";
+
+ //API密钥,在商户平台设置
+ public static final String API_KEY="ClMrQsAcidRa7uJT4TgHgVAOHbzQjdPa";
+
+ //app 微信AppSecret 秘钥 b2792724b9565be23e8f5ba548f117cf
+ public static final String AppSecret="1934a281c82ad1a059130fe51341b74b";
+
+
+}
diff --git a/app/src/main/java/com/jx/jyhd/simcpux/MD5.java b/app/src/main/java/com/jx/jyhd/simcpux/MD5.java
new file mode 100644
index 0000000..a30260f
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/simcpux/MD5.java
@@ -0,0 +1,28 @@
+package com.jx.jyhd.simcpux;
+
+import java.security.MessageDigest;
+
+public class MD5 {
+
+ private MD5() {}
+
+ public final static String getMessageDigest(byte[] buffer) {
+ char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ try {
+ MessageDigest mdTemp = MessageDigest.getInstance("MD5");
+ mdTemp.update(buffer);
+ byte[] md = mdTemp.digest();
+ int j = md.length;
+ char str[] = new char[j * 2];
+ int k = 0;
+ for (int i = 0; i < j; i++) {
+ byte byte0 = md[i];
+ str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+ str[k++] = hexDigits[byte0 & 0xf];
+ }
+ return new String(str);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/jx/jyhd/simcpux/MD5Util.java b/app/src/main/java/com/jx/jyhd/simcpux/MD5Util.java
new file mode 100644
index 0000000..e55cd9a
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/simcpux/MD5Util.java
@@ -0,0 +1,43 @@
+package com.jx.jyhd.simcpux;
+
+import java.security.MessageDigest;
+
+public class MD5Util {
+
+ private static String byteArrayToHexString(byte b[]) {
+ StringBuffer resultSb = new StringBuffer();
+ for (int i = 0; i < b.length; i++)
+ resultSb.append(byteToHexString(b[i]));
+
+ return resultSb.toString();
+ }
+
+ private static String byteToHexString(byte b) {
+ int n = b;
+ if (n < 0)
+ n += 256;
+ int d1 = n / 16;
+ int d2 = n % 16;
+ return hexDigits[d1] + hexDigits[d2];
+ }
+
+ public static String MD5Encode(String origin, String charsetname) {
+ String resultString = null;
+ try {
+ resultString = new String(origin);
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ if (charsetname == null || "".equals(charsetname))
+ resultString = byteArrayToHexString(md.digest(resultString
+ .getBytes()));
+ else
+ resultString = byteArrayToHexString(md.digest(resultString
+ .getBytes(charsetname)));
+ } catch (Exception exception) {
+ }
+ return resultString;
+ }
+
+ private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
+ "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
+
+}
diff --git a/app/src/main/java/com/jx/jyhd/simcpux/Util.java b/app/src/main/java/com/jx/jyhd/simcpux/Util.java
new file mode 100644
index 0000000..7070a1b
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/simcpux/Util.java
@@ -0,0 +1,393 @@
+package com.jx.jyhd.simcpux;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+
+import com.tsgame.tsgame_niuniu.system.Myapplication;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class Util {
+
+ public static final String TAG = "Util";
+ private static final String[] PERMISSIONS_STORAGE = {
+ "android.permission.READ_EXTERNAL_STORAGE",
+ "android.permission.WRITE_EXTERNAL_STORAGE" };
+
+ public static void verifyStoragePermissions(Activity activity) {
+
+ try {
+ //检测是否有写的权限
+ int permission = ContextCompat.checkSelfPermission(activity,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (permission != PackageManager.PERMISSION_GRANTED) {
+ // 没有写的权限,去申请写的权限,会弹出对话框
+ ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,
+ Constants.PERMISSIONS_REQUEST_STORAGE);
+ }
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ String[] permissions = PERMISSIONS_STORAGE;
+ //验证是否许可权限
+ for (String str : permissions) {
+ if (activity.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
+ //申请权限
+ activity.requestPermissions(permissions, Constants.PERMISSIONS_REQUEST_STORAGE);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String GetFileAbsolutePath(){
+ return Objects.requireNonNull(Myapplication.application.getExternalFilesDir(null)).getAbsolutePath();
+ }
+ public static File GetDirectory(){
+ return Myapplication.application.getExternalFilesDir(null);
+ }
+
+ public static byte[] bmpToByteArray(final Bitmap bmp, final boolean needRecycle) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ bmp.compress(Bitmap.CompressFormat.PNG, 100, output);
+ if (needRecycle) {
+ bmp.recycle();
+ }
+
+ byte[] result = output.toByteArray();
+ try {
+ output.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return result;
+ }
+
+ public static byte[] httpGet(final String url) {
+ if (url == null || url.length() == 0) {
+ Log.e(TAG, "httpGet, url is null");
+ return null;
+ }
+
+ HttpClient httpClient = getNewHttpClient();
+ HttpGet httpGet = new HttpGet(url);
+
+ try {
+ HttpResponse resp = httpClient.execute(httpGet);
+ if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ Log.e(TAG, "httpGet fail, status code = " + resp.getStatusLine().getStatusCode());
+ return null;
+ }
+
+ return EntityUtils.toByteArray(resp.getEntity());
+
+ } catch (Exception e) {
+ Log.e(TAG, "httpGet exception, e = " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static byte[] httpPost(String url, String entity) {
+ if (url == null || url.length() == 0) {
+ Log.e(TAG, "httpPost, url is null");
+ return null;
+ }
+
+ HttpClient httpClient = getNewHttpClient();
+
+ HttpPost httpPost = new HttpPost(url);
+
+ try {
+ httpPost.setEntity(new StringEntity(entity));
+ httpPost.setHeader("Accept", "application/json");
+ httpPost.setHeader("Content-type", "application/json");
+
+ HttpResponse resp = httpClient.execute(httpPost);
+ if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ Log.e(TAG, "httpGet fail, status code = " + resp.getStatusLine().getStatusCode());
+ return null;
+ }
+
+ return EntityUtils.toByteArray(resp.getEntity());
+ } catch (Exception e) {
+ Log.e(TAG, "httpPost exception, e = " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static class SSLSocketFactoryEx extends SSLSocketFactory {
+
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+
+ public SSLSocketFactoryEx(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(truststore);
+
+ TrustManager tm = new X509TrustManager() {
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ @SuppressLint("TrustAllX509TrustManager")
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @SuppressLint("TrustAllX509TrustManager")
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+ };
+
+ sslContext.init(null, new TrustManager[] { tm }, null);
+ }
+
+ @Override
+ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+ return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return sslContext.getSocketFactory().createSocket();
+ }
+ }
+
+ private static HttpClient getNewHttpClient() {
+ try {
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+
+ SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore);
+ sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+
+ HttpParams params = new BasicHttpParams();
+ HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+ HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
+
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+ registry.register(new Scheme("https", sf, 443));
+
+ ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
+
+ return new DefaultHttpClient(ccm, params);
+ } catch (Exception e) {
+ return new DefaultHttpClient();
+ }
+ }
+
+ public static byte[] readFromFile(String fileName, int offset, int len) {
+ if (fileName == null) {
+ return null;
+ }
+
+ File file = new File(fileName);
+ if (!file.exists()) {
+ Log.i(TAG, "readFromFile: file not found");
+ return null;
+ }
+
+ if (len == -1) {
+ len = (int) file.length();
+ }
+
+ Log.d(TAG, "readFromFile : offset = " + offset + " len = " + len + " offset + len = " + (offset + len));
+
+ if(offset <0){
+ Log.e(TAG, "readFromFile invalid offset:" + offset);
+ return null;
+ }
+ if(len <=0 ){
+ Log.e(TAG, "readFromFile invalid len:" + len);
+ return null;
+ }
+ if(offset + len > (int) file.length()){
+ Log.e(TAG, "readFromFile invalid file len:" + file.length());
+ return null;
+ }
+
+ byte[] b = null;
+ try {
+ RandomAccessFile in = new RandomAccessFile(fileName, "r");
+ b = new byte[len]; // ´´½¨ºÏÊÊÎļþ´óСµÄÊý×é
+ in.seek(offset);
+ in.readFully(b);
+ in.close();
+
+ } catch (Exception e) {
+ Log.e(TAG, "readFromFile : errMsg = " + e.getMessage());
+ e.printStackTrace();
+ }
+ return b;
+ }
+
+ private static final int MAX_DECODE_PICTURE_SIZE = 1920 * 1440;
+
+ public static Bitmap extractThumbNail(final String path, final int height, final int width, final boolean crop) {
+// Assert.assertTrue(path != null && !path.equals("") && height > 0 && width > 0);
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+
+ try {
+ options.inJustDecodeBounds = true;
+ Bitmap tmp = BitmapFactory.decodeFile(path, options);
+ if (tmp != null) {
+ tmp.recycle();
+ tmp = null;
+ }
+
+ Log.d(TAG, "extractThumbNail: round=" + width + "x" + height + ", crop=" + crop);
+ final double beY = options.outHeight * 1.0 / height;
+ final double beX = options.outWidth * 1.0 / width;
+ Log.d(TAG, "extractThumbNail: extract beX = " + beX + ", beY = " + beY);
+ options.inSampleSize = (int) (crop ? (beY > beX ? beX : beY) : (beY < beX ? beX : beY));
+ if (options.inSampleSize <= 1) {
+ options.inSampleSize = 1;
+ }
+
+ // NOTE: out of memory error
+ while (options.outHeight * options.outWidth / options.inSampleSize > MAX_DECODE_PICTURE_SIZE) {
+ options.inSampleSize++;
+ }
+
+ int newHeight = height;
+ int newWidth = width;
+ if (crop) {
+ if (beY > beX) {
+ newHeight = (int) (newWidth * 1.0 * options.outHeight / options.outWidth);
+ } else {
+ newWidth = (int) (newHeight * 1.0 * options.outWidth / options.outHeight);
+ }
+ } else {
+ if (beY < beX) {
+ newHeight = (int) (newWidth * 1.0 * options.outHeight / options.outWidth);
+ } else {
+ newWidth = (int) (newHeight * 1.0 * options.outWidth / options.outHeight);
+ }
+ }
+
+ options.inJustDecodeBounds = false;
+
+ Log.i(TAG, "bitmap required size=" + newWidth + "x" + newHeight + ", orig=" + options.outWidth + "x" + options.outHeight + ", sample=" + options.inSampleSize);
+ Bitmap bm = BitmapFactory.decodeFile(path, options);
+ if (bm == null) {
+ Log.e(TAG, "bitmap decode failed");
+ return null;
+ }
+
+ Log.i(TAG, "bitmap decoded size=" + bm.getWidth() + "x" + bm.getHeight());
+ final Bitmap scale = Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
+ if (scale != null) {
+ bm.recycle();
+ bm = scale;
+ }
+
+ if (crop) {
+ final Bitmap cropped = Bitmap.createBitmap(bm, (bm.getWidth() - width) >> 1, (bm.getHeight() - height) >> 1, width, height);
+ if (cropped == null) {
+ return bm;
+ }
+
+ bm.recycle();
+ bm = cropped;
+ Log.i(TAG, "bitmap croped size=" + bm.getWidth() + "x" + bm.getHeight());
+ }
+ return bm;
+
+ } catch (final OutOfMemoryError e) {
+ Log.e(TAG, "decode bitmap failed: " + e.getMessage());
+ options = null;
+ }
+
+ return null;
+ }
+
+ public static String sha1(String str) {
+ if (str == null || str.length() == 0) {
+ return null;
+ }
+
+ char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ try {
+ MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
+ mdTemp.update(str.getBytes());
+
+ byte[] md = mdTemp.digest();
+ int j = md.length;
+ char buf[] = new char[j * 2];
+ int k = 0;
+ for (int i = 0; i < j; i++) {
+ byte byte0 = md[i];
+ buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
+ buf[k++] = hexDigits[byte0 & 0xf];
+ }
+ return new String(buf);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static List stringsToList(final String[] src) {
+ if (src == null || src.length == 0) {
+ return null;
+ }
+ final List result = new ArrayList();
+ for (int i = 0; i < src.length; i++) {
+ result.add(src[i]);
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/jx/jyhd/simcpux/Wxistrue.java b/app/src/main/java/com/jx/jyhd/simcpux/Wxistrue.java
new file mode 100644
index 0000000..c28c134
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/simcpux/Wxistrue.java
@@ -0,0 +1,12 @@
+package com.jx.jyhd.simcpux;
+
+public class Wxistrue {
+ public static boolean islogin=true;
+
+ public static boolean isshare=true;
+
+ public static boolean isphotoshare=true;
+
+ public static int sharetype=1;//分享类型1 好友 2 朋友圈
+
+}
diff --git a/app/src/main/java/com/jx/jyhd/simcpux/util/WeChatShareUtil.java b/app/src/main/java/com/jx/jyhd/simcpux/util/WeChatShareUtil.java
new file mode 100644
index 0000000..712135e
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/simcpux/util/WeChatShareUtil.java
@@ -0,0 +1,358 @@
+package com.jx.jyhd.simcpux.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.widget.Toast;
+
+import com.jx.jyhd.simcpux.Constants;
+import com.jx.jyhd.simcpux.Util;
+import com.jx.jyhd.wxapi.WXEntryActivity;
+import com.tagmae.tsgame_erwang.utils.Base64ImageUtil;
+import com.tencent.mm.opensdk.modelmsg.SendMessageToWX;
+import com.tencent.mm.opensdk.modelmsg.WXImageObject;
+import com.tencent.mm.opensdk.modelmsg.WXMediaMessage;
+import com.tencent.mm.opensdk.modelmsg.WXWebpageObject;
+import com.tencent.mm.opensdk.openapi.IWXAPI;
+import com.tencent.mm.opensdk.openapi.WXAPIFactory;
+
+import java.io.File;
+
+/**
+ * 微信分享工具类
+ */
+public class WeChatShareUtil {
+
+ private static final int THUMB_SIZE = 150;
+ private static WeChatShareUtil instance;
+ private final Context mContext;
+ private final IWXAPI mWxApi;
+ private WeChatShareCallback mCallback;
+
+ private WeChatShareUtil(Context context) {
+ this.mContext = context.getApplicationContext();
+ // 初始化微信API
+ mWxApi = WXAPIFactory.createWXAPI(mContext, Constants.APP_ID, true);
+ mWxApi.registerApp(Constants.APP_ID);
+ }
+
+ /**
+ * 获取实例
+ * @param context 上下文
+ * @return WeChatShareUtil实例
+ */
+ public static WeChatShareUtil getInstance(Context context) {
+ if (instance == null && context != null) {
+ instance = new WeChatShareUtil(context);
+ }
+ return instance;
+ }
+
+ /**
+ * 检查微信是否已安装
+ * @return 是否安装
+ */
+ public boolean isWeChatInstalled() {
+ try {
+ return mWxApi.isWXAppInstalled();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 设置分享回调
+ * @param callback 分享回调
+ */
+ public void setShareCallback(WeChatShareCallback callback) {
+ this.mCallback = callback;
+ }
+
+ /**
+ * 获取分享回调
+ * @return 分享回调
+ */
+ public WeChatShareCallback getShareCallback() {
+ return mCallback;
+ }
+
+ /**
+ * 分享网页到微信
+ * @param activity 活动
+ * @param webpageUrl 网页链接
+ * @param title 标题
+ * @param description 描述
+ * @param scene 分享场景(0:好友, 1:朋友圈)
+ */
+ public void shareWebPage(Activity activity, String webpageUrl, String title, String description, int scene) {
+ if (!isWeChatInstalled()) {
+ Toast.makeText(activity, "请先安装微信客户端", Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-1, "未安装微信客户端");
+ }
+ return;
+ }
+
+ try {
+ // 设置分享回调处理
+ // 创建Handler来接收微信分享结果
+ final Handler shareHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ // 处理微信分享回调
+ if (mCallback != null) {
+ if (msg.what == 0) { // 成功
+ mCallback.onSuccess();
+ } else if (msg.what == -2) { // 取消
+ mCallback.onCancel();
+ } else { // 错误
+ mCallback.onError(msg.what, "微信分享失败,错误码:" + msg.what);
+ }
+ }
+ }
+ };
+
+ // 设置分享回调
+ WXEntryActivity.setshareHandler(shareHandler);
+
+ // 创建网页消息对象
+ WXWebpageObject webpage = new WXWebpageObject();
+ webpage.webpageUrl = webpageUrl;
+
+ // 创建多媒体消息
+ WXMediaMessage msg = new WXMediaMessage(webpage);
+ msg.title = title;
+ msg.description = description;
+
+ // 设置缩略图 - 使用应用的启动图标 logo6 而不是 ic_launcher
+ Bitmap thumbBmp = BitmapFactory.decodeResource(mContext.getResources(),
+ mContext.getResources().getIdentifier("logo6", "drawable", mContext.getPackageName()));
+
+ // 如果找不到logo6,则退回到使用ic_launcher
+ if (thumbBmp == null) {
+ thumbBmp = BitmapFactory.decodeResource(mContext.getResources(),
+ com.jx.jyhd.R.mipmap.ic_launcher);
+ }
+
+ if (thumbBmp != null) {
+ Bitmap thumbBitmap = Bitmap.createScaledBitmap(thumbBmp, THUMB_SIZE, THUMB_SIZE, true);
+ thumbBmp.recycle();
+ msg.thumbData = Util.bmpToByteArray(thumbBitmap, true);
+ }
+
+ // 构建发送请求
+ SendMessageToWX.Req req = new SendMessageToWX.Req();
+ req.transaction = buildTransaction("webpage");
+ req.message = msg;
+ req.scene = scene == 0 ? SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline;
+
+ // 发送请求
+ boolean result = mWxApi.sendReq(req);
+
+ if (!result) {
+ Toast.makeText(activity, "微信分享请求发送失败", Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-2, "微信分享请求发送失败");
+ }
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ Toast.makeText(activity, "微信分享失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-3, "微信分享失败: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * 分享图片到微信
+ * @param activity 活动
+ * @param imagePath 图片路径
+ * @param scene 分享场景(0:好友, 1:朋友圈)
+ */
+ public void shareImage(Activity activity, String imagePath, int scene) {
+ if (!isWeChatInstalled()) {
+ Toast.makeText(activity, "请先安装微信客户端", Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-1, "未安装微信客户端");
+ }
+ return;
+ }
+
+ // 使用新的图片处理工具类检查图片是否存在,不存在时可以尝试使用其他备用方案
+ Uri imageFile = Base64ImageUtil.base64ToImageFile(activity,imagePath,null);
+ //Uri imageFile = Uri.parse(imagePath);
+
+ if (imageFile == null) {
+ //Toast.makeText(activity, "分享图片不存在,尝试创建临时图片", Toast.LENGTH_SHORT).show();
+
+ // 如果是Bitmap对象,可以使用ImageProcessUtils保存
+ if (imagePath.startsWith("data:image/") || imagePath.startsWith("base64,")) {
+ // 对于Base64编码的图片,使用ImageProcessUtils保存
+ com.tagmae.tsgame_erwang.utils.ImageProcessUtils.saveBase64ImageToCache(
+ activity,
+ imagePath,
+ new com.tagmae.tsgame_erwang.utils.ImageProcessUtils.ImageSaveCallback() {
+ @Override
+ public void onSuccess(String savedImagePath) {
+ // 保存成功,使用新路径进行分享
+ shareImageInternal(activity, savedImagePath, scene);
+ }
+
+ @Override
+ public void onError(String errorMsg) {
+ Toast.makeText(activity, errorMsg, Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-2, errorMsg);
+ }
+ }
+ }
+ );
+ return;
+ } else {
+ // 图片不存在且不能处理
+ if (mCallback != null) {
+ mCallback.onError(-2, "图片文件不存在");
+ }
+ return;
+ }
+ }
+
+ // 直接使用现有图片路径分享
+ shareImageInternal(activity, imageFile.getPath(), scene);
+ }
+
+ /**
+ * 内部图片分享实现
+ */
+ private void shareImageInternal(Activity activity, String imagePath, int scene) {
+ try {
+ // 设置分享回调处理
+ // 创建Handler来接收微信分享结果
+ final Handler shareHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ // 处理微信分享回调
+ if (mCallback != null) {
+ if (msg.what == 0) { // 成功
+ mCallback.onSuccess();
+ } else if (msg.what == -2) { // 取消
+ mCallback.onCancel();
+ } else { // 错误
+ mCallback.onError(msg.what, "微信分享失败,错误码:" + msg.what);
+ }
+ }
+ }
+ };
+
+ // 设置分享回调
+ WXEntryActivity.setshareHandler(shareHandler);
+
+ // 加载要分享的图片
+ Bitmap bmp = BitmapFactory.decodeFile(imagePath);
+ if (bmp == null) {
+ Toast.makeText(activity, "图片加载失败", Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-3, "图片加载失败");
+ }
+ return;
+ }
+
+ // 创建图片对象
+ WXImageObject imgObj = new WXImageObject(bmp);
+
+ // 创建多媒体消息
+ WXMediaMessage msg = new WXMediaMessage();
+ msg.mediaObject = imgObj;
+
+ // 设置缩略图
+ Bitmap thumbBmp = Bitmap.createScaledBitmap(bmp, THUMB_SIZE, THUMB_SIZE, true);
+ bmp.recycle();
+ msg.thumbData = Util.bmpToByteArray(thumbBmp, true);
+
+ // 构建发送请求
+ SendMessageToWX.Req req = new SendMessageToWX.Req();
+ req.transaction = buildTransaction("img");
+ req.message = msg;
+ req.scene = scene == 0 ? SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline;
+
+ // 发送请求
+ boolean result = mWxApi.sendReq(req);
+
+ if (!result) {
+ Toast.makeText(activity, "微信分享请求发送失败", Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-4, "微信分享请求发送失败");
+ }
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ Toast.makeText(activity, "微信分享失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-5, "微信分享失败: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * 分享Bitmap到微信
+ * @param activity 活动
+ * @param bitmap 位图对象
+ * @param scene 分享场景(0:好友, 1:朋友圈)
+ */
+ public void shareBitmap(Activity activity, Bitmap bitmap, int scene) {
+ if (bitmap == null) {
+ Toast.makeText(activity, "分享图片为空", Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-1, "分享图片为空");
+ }
+ return;
+ }
+
+ // 使用ImageProcessUtils保存Bitmap
+ com.tagmae.tsgame_erwang.utils.ImageProcessUtils.saveBitmapToCache(
+ activity,
+ bitmap,
+ new com.tagmae.tsgame_erwang.utils.ImageProcessUtils.ImageSaveCallback() {
+ @Override
+ public void onSuccess(String imagePath) {
+ // 保存成功,使用生成的图片路径分享
+ shareImageInternal(activity, imagePath, scene);
+ }
+
+ @Override
+ public void onError(String errorMsg) {
+ Toast.makeText(activity, errorMsg, Toast.LENGTH_SHORT).show();
+ if (mCallback != null) {
+ mCallback.onError(-2, errorMsg);
+ }
+ }
+ }
+ );
+ }
+
+ /**
+ * 构建交易ID
+ */
+ private String buildTransaction(String type) {
+ return type + System.currentTimeMillis();
+ }
+
+ /**
+ * 微信分享回调接口
+ */
+ public interface WeChatShareCallback {
+ void onSuccess();
+ void onError(int code, String message);
+ void onCancel();
+ }
+}
diff --git a/app/src/main/java/com/jx/jyhd/wxapi/WXEntryActivity.java b/app/src/main/java/com/jx/jyhd/wxapi/WXEntryActivity.java
new file mode 100644
index 0000000..75735c8
--- /dev/null
+++ b/app/src/main/java/com/jx/jyhd/wxapi/WXEntryActivity.java
@@ -0,0 +1,421 @@
+package com.jx.jyhd.wxapi;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.volley.VolleyError;
+import com.game.webgame.network.Volleyinterface1;
+import com.game.webgame.network.register;
+import com.game.webgame.view.SpUtil;
+import com.jx.jyhd.R;
+import com.jx.jyhd.simcpux.Constants;
+import com.tencent.mm.opensdk.constants.ConstantsAPI;
+import com.tencent.mm.opensdk.modelbase.BaseReq;
+import com.tencent.mm.opensdk.modelbase.BaseResp;
+import com.tencent.mm.opensdk.modelmsg.SendAuth;
+import com.tencent.mm.opensdk.modelmsg.ShowMessageFromWX;
+import com.tencent.mm.opensdk.openapi.IWXAPI;
+import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler;
+import com.tencent.mm.opensdk.openapi.WXAPIFactory;
+import com.jx.jyhd.R;
+import com.jx.jyhd.simcpux.Constants;
+import com.tsgame.tsgame_niuniu.system.WX_Myurl;
+import com.tsgame.tsgame_niuniu.util.apputil;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
+
+ public static final String TAG = "WXEntryActivity";
+ private IWXAPI api;
+ TextView text;
+ ImageView image;
+ public static Handler sharehandler;
+
+ SharedPreferences sp;
+
+ // 分享成功
+ public static final int sharesucces = 2;
+ // 分享失败
+ public static final int sharecancel = 3;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.wxuserinfo);
+
+ api = WXAPIFactory.createWXAPI(this, Constants.APP_ID, false);
+ try {
+ Intent intent = getIntent();
+ api.handleIntent(intent, this);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ sp = SpUtil.getSharePerference(WXEntryActivity.this);
+ initview();
+ }
+
+ private void initview() {
+ text = (TextView) findViewById(R.id.user);
+ image = (ImageView) findViewById(R.id.image);
+
+ }
+
+ public static void setshareHandler(Handler handler) {
+ sharehandler = handler;
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+
+ setIntent(intent);
+ api.handleIntent(intent, this);
+ }
+
+ // 微信发送请求到第三方应用时,会回调到该方法
+ @Override
+ public void onReq(BaseReq req) {
+ switch (req.getType()) {
+ case ConstantsAPI.COMMAND_GETMESSAGE_FROM_WX:
+ // goToGetMsg();
+ break;
+ case ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX:
+ // goToShowMsg((ShowMessageFromWX.Req) req);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // 第三方应用发送到微信的请求处理后的响应结果,会回调到该方法
+ @Override
+ public void onResp(BaseResp resp) {
+ int result = 0;
+ switch (resp.errCode){
+ case BaseResp.ErrCode.ERR_OK:
+ if (apputil.wxtype == 1) {// 分享
+ sharehandler.sendEmptyMessage(sharesucces);
+ }
+ result = R.string.errcode_success;
+ break;
+ case BaseResp.ErrCode.ERR_USER_CANCEL:
+ result = R.string.errcode_cancel;
+ break;
+ case BaseResp.ErrCode.ERR_AUTH_DENIED:
+ result = R.string.errcode_deny;
+ break;
+ case BaseResp.ErrCode.ERR_UNSUPPORT:
+ result = R.string.errcode_unsupport;
+ break;
+ default:
+ result = R.string.errcode_unknown;
+ break;
+ }
+ Log.e(TAG, getString(result) + ", type=" + resp.getType());
+ if (resp.getType() == ConstantsAPI.COMMAND_SENDAUTH) {
+ SendAuth.Resp authResp = (SendAuth.Resp)resp;
+ final String code = authResp.code;
+ getaccess_token(code);
+ }
+ finish();
+ }
+
+ private void getaccess_token(String code) {
+ Map map = new HashMap();
+ map.put("appid", Constants.APP_ID);
+ map.put("secret", Constants.AppSecret);
+ map.put("code", code);
+ map.put("grant_type", "authorization_code");
+
+ register.StringGETrequest(map, WX_Myurl.getwxaccess_token,
+ new Volleyinterface1(this, Volleyinterface1.mlistener,
+ Volleyinterface1.mErrorLisener) {
+
+ @Override
+ public void onsuccess(String result) {
+ try {
+
+ JSONObject object = new JSONObject(result);
+ String access_token = object
+ .optString("access_token");
+
+ SpUtil.setStringSharedPerference(sp,
+ "access_token", access_token);
+
+ int expires_in = object.optInt("expires_in");
+
+ String refresh_token = object
+ .optString("refresh_token");
+
+ SpUtil.setStringSharedPerference(sp,
+ "refresh_token", refresh_token);
+
+ String openid = object.optString("openid");
+ // getuserinfo(access_token,openid);
+
+ SpUtil.setStringSharedPerference(sp, "openid",
+ openid);
+
+ getUserInfo(access_token, openid);
+
+ // Toast.makeText(
+ // WXEntryActivity.this,
+ // "access_token+=" + expires_in
+ // + "refresh_token" + refresh_token,
+ // Toast.LENGTH_LONG).show();
+
+ } catch (JSONException e) {
+
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void onerror(VolleyError arg0) {
+ Log.i("WxError","Start");
+ arg0.printStackTrace();
+ Log.i("WxError",arg0.toString());
+ Log.i("WxError",arg0.getMessage());
+ Log.i("WxError","End");
+ }
+ });
+
+ }
+
+ protected void getuserinfo(String access_token, String openid) {
+ Map map = new HashMap();
+ map.put("access_token", access_token);
+ map.put("openid", openid);
+
+ register.StringGETrequest(map, WX_Myurl.getwxuserinfo,
+ new Volleyinterface1(this, Volleyinterface1.mlistener,
+ Volleyinterface1.mErrorLisener) {
+ @Override
+ public void onsuccess(String result) {
+ String str = null;
+ try {
+ try {
+ // URLEncoder、URLDecoder进行URL参数的转码与解码
+ URLEncoder.encode(result, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ // EntityUtils.toString(response.getEntity(),HTTP.UTF_8);//在这里转换
+ JSONObject object = new JSONObject(result);
+ // Toast.makeText(WXEntryActivity.this,
+ // "result=" + str, Toast.LENGTH_LONG).show();
+ } catch (JSONException e) {
+
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void onerror(VolleyError arg0) {
+
+ }
+ });
+
+ }
+
+ @SuppressWarnings("deprecation")
+ public static String getWeixinUserinfo(String access_token, String openid) {
+ String URLs = WX_Myurl.getwxuserinfo;// 微信登录地址
+ String result = "";
+ List list = new ArrayList();
+
+ NameValuePair pairappid = new BasicNameValuePair("access_token",
+ access_token);
+ NameValuePair pairsecret = new BasicNameValuePair("openid", openid);
+ list.add(pairappid);
+ list.add(pairsecret);
+
+ try {
+ HttpEntity requestHttpEntity = new UrlEncodedFormEntity(list,
+ HTTP.UTF_8);
+ URL url = new URL(URLs);
+ URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(),
+ url.getQuery(), null);
+ HttpPost httpPost = new HttpPost(uri);
+ httpPost.setEntity(requestHttpEntity);
+ HttpClient httpClient = new DefaultHttpClient();
+ HttpResponse response = httpClient.execute(httpPost);
+ if (response.getStatusLine().getStatusCode() == 200) {
+ result = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);// 在这里转换
+ }
+ return result;
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public interface OnGetUserInfoListener {
+ void onGetUserInfo(String userInfo); // json字符串
+
+ void onNetError();
+ }
+
+ static class GetUserInfoTask extends AsyncTask