Files
youle_app_ohos/docs/设计文档/TSGame_原生与H5接口契约总规范.md
T
lanterngamescnandClaude Opus 4.8 82459c708c 横屏改为 auto_rotation_landscape:横屏两朝向跟随传感器旋转,不进竖屏
- module.json5:landscape → auto_rotation_landscape(横屏正/反向 180° 跟随重力
  感应自动旋转,但不进入竖屏),build 校验取值合法、安装启动通过
- 同步契约 §7 与框架 §7.4:'不跟随传感器' 修正为 '横屏两朝向内跟随旋转'
- H5 显式方向控制(含竖屏)仍由 setPreferredOrientation 保留,零改动不变

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:38:05 +08:00

855 lines
51 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TSGame 原生 ↔ H5 接口契约与启动流程总规范(跨平台适配版)
> **文档目的**:本规范以现有 Android 工程的**真实代码**为唯一依据,抽象出"原生容器"与"H5 游戏内容"之间的**完整接口契约**和**启动/资源加载流程**。
> 目标是:在一个全新的原生平台(HarmonyOS)上重新实现"原生容器",使**现有 H5 一行代码都不改动**即可正常运行。
>
> **适配原则**
> - 本文档只规定"契约"(协议、接口名、参数结构、调用时机、数据流),**不规定原生内部如何实现**——原生侧(包括 HarmonyOS)可根据平台能力自由实现,只要对外行为与本契约一致即可。
> - 文中所有接口名、参数字段、常量值均来自真实源码,并标注了来源文件与行号,便于核对。
> - 凡现有 Android 代码中"未实现/已注释/无回传"的部分,本文明确标注,作为新平台需要**补齐或决策**的事项。
---
## 目录
1. [总体架构](#1-总体架构)
2. [核心:JS 通信桥协议(WebViewJavascriptBridge](#2-核心js-通信桥协议webviewjavascriptbridge)
3. [应用启动流程](#3-应用启动流程)
4. [配置体系](#4-配置体系)
5. [资源(大厅/子游戏)管理](#5-资源大厅子游戏管理)
6. [H5 数据注入:app_data.js](#6-h5-数据注入app_datajs)
7. [WebView 容器能力要求](#7-webview-容器能力要求)
8. [接口契约一:H5 → 原生(入站 Handler 全集)](#8-接口契约一h5--原生入站-handler-全集)
9. [接口契约二:原生 → H5(出站 Handler 全集)](#9-接口契约二原生--h5出站-handler-全集)
10. [原生能力专题](#10-原生能力专题)
11. [子游戏跳转与通用网页容器](#11-子游戏跳转与通用网页容器)
12. [数据结构汇总](#12-数据结构汇总)
13. [关键常量与第三方账号](#13-关键常量与第三方账号)
14. [HarmonyOS 适配检查清单与待决策项](#14-harmonyos-适配检查清单与待决策项)
---
## 1. 总体架构
整个 App 本质上是一个**多 WebView 容器 + 资源管理器**,所有业务逻辑(大厅、棋牌子游戏)都是 H5,原生只提供"启动引导 + 资源下载解压 + 设备能力桥接"。
```
┌──────────────────────────────────────────────────────────────┐
│ 原生容器(可在任意平台重写) │
│ │
│ [启动引导页] ──► 读本地配置 ──► 请求远程配置 ──► 下载/解压资源 │
│ │ │ │
│ └──────────────► 注入 app_data.js ─────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ WebView 容器(大厅 / 子游戏) │ │
│ │ 加载 file://.../index.html?Launchtype=X │ │
│ │ │ │
│ │ ◄──── JS 通信桥(WebViewJavascriptBridge ────► │ │
│ │ H5 ⇄ 原生:登录/分享/支付/定位/音频/震动… │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ 设备能力:微信/QQ/抖音分享、微信支付登录、高德定位、 │
│ 录音、音效、摇一摇、扫码、相机、剪贴板、震动… │
└──────────────────────────────────────────────────────────────┘
```
### 1.1 容器角色划分(现有 Android 实现)
| 角色 | 现有 Android 类(仅作来源标注,新平台无需同名) | 说明 |
|---|---|---|
| 启动引导页 | `weclomeactivity1`(Launcher,强制横屏) | 读配置、请求远程配置、下载解压资源、注入 `app_data.js`,完成后跳大厅 |
| 大厅容器 | `webviewActivity` | 加载大厅 H5`.../gamehall/index.html` |
| 子游戏/内置网页容器 | `openwebActivity1` | 由大厅通过桥接口 `OpenurlTitleData` 打开任意 H5 url |
| 微信回调容器 | `NewwebviewActivity` | 与大厅容器接口集几乎完全相同,承担微信 scheme 回调 |
> **重要说明**`webviewActivity` 与 `NewwebviewActivity` 注册的桥接口(44 个入站 handler)**名称、参数结构、回调名几乎完全一致**,差异仅 3 处(见 §8.x 标注)。新平台**只需实现一套统一的桥接口契约**即可同时覆盖大厅与子游戏容器。本文档给出的是这套统一契约。
---
## 2. 核心:JS 通信桥协议(WebViewJavascriptBridge
> 这是整个适配的**重中之重**。H5 完全依赖此协议与原生通信,**不可更改**。新平台必须 1:1 实现此协议,包括其 URL scheme、消息格式、JS 全局对象与注入时机。
> 来源:`com/tagmae/jsbridge/``BridgeWebView`、`BridgeWebViewClient`、`BridgeUtil`、`Message`)。本协议是开源库 lzyzsd/JsBridge 的实现。
### 2.1 协议总览
> ⚠️ **重要范围说明**:本章描述的 `WebViewJavascriptBridge` 协议适用于**大厅容器与子游戏容器**(基于 `BridgeWebView`,即 `webviewActivity` / `NewwebviewActivity`)。
> **另有一个独立的通用网页容器 `openwebActivity1`**(由桥接口 `OpenurlTitleData` 打开,用于活动页/收银台/客服等"打开网页"场景),它**不使用本协议**,而是用传统的"**JS 接口对象注入(`@JavascriptInterface`,对象名 `settings`+ `javascript:` 直接函数调用**"。该容器的接口契约见 [§11.3](#113-通用网页容器-openwebactivity1-的独立接口契约),新平台**两套机制都要实现**。
在基于 `BridgeWebView` 的两个主容器里,业务通信**不使用**平台的"JS 接口注入"机制(如 Android `@JavascriptInterface`、HarmonyOS `javaScriptProxy`)作为通道——其中所有 `addJavascriptInterface(new settings(), "settings")` 均已注释失效,仅保留的崩溃监控用途与业务无关。**这两个主容器的业务通信 100% 走下述 URL scheme 拦截协议。**
> 另:源码中存在 `ShareJavascriptInterface`(注入名 `NativeShare`,方法 `shareToQQ`/`shareToDouYin`),但其注入入口 `ShareActivityPatch` 在全工程**无任何引用,是死代码**,H5 无法依赖,新平台**无需实现**。
通信靠两条单向通道拼成双向:
1. **原生 → H5**:原生执行 JS`WebViewJavascriptBridge._handleMessageFromNative('<消息JSON>')`
2. **H5 → 原生**:H5 改变一个隐藏 iframe 的 `src``yy://...` 触发导航;原生在"资源加载拦截/URL 跳转拦截"里识别 `yy://` 前缀并阻断真实导航,转而处理消息。
### 2.2 URL scheme 约定(必须原样实现)
来源:`BridgeUtil.java`
| 常量 | 值 | 含义 |
|---|---|---|
| 协议前缀 | `yy://` | H5 发起的所有桥调用 |
| 返回数据前缀 | `yy://return/` | H5 对原生调用的回执 / 取队列回执 |
| 取消息队列 | `yy://return/_fetchQueue/` | 原生通知 H5"把待发消息队列交出来" |
原生侧 URL 拦截逻辑(必须复刻):
```
拦截到 url(需先 URLDecode):
if url 以 "yy://return/" 开头:
→ 解析出 functionName 与 data,执行对应回调,阻断导航
else if url 以 "yy://" 开头:
→ 调用 _fetchQueue()(见下),阻断导航
else:
→ 正常导航
```
### 2.3 原生 → H5 的两条 JS 调用(必须原样实现)
来源:`BridgeUtil.java`
- 下发单条消息:`javascript:WebViewJavascriptBridge._handleMessageFromNative('<JSON字符串>');`
- 抽取 H5 待发队列:`javascript:WebViewJavascriptBridge._fetchQueue();`
- 该调用本身被当作一次"原生→H5"调用,其回执由 H5 通过 `yy://return/_fetchQueue/<JSON数组>` 送回原生。
### 2.4 消息体(MessageJSON 结构
来源:`Message.java`。原生与 H5 之间所有消息都序列化为如下结构(字段按需出现):
| 字段 | 类型 | 含义 |
|---|---|---|
| `handlerName` | String | 目标 handler 名(指定调用哪个已注册的 handler;无则走 defaultHandler |
| `data` | String | 业务数据载荷(**注意:可能是裸字符串,也可能是 JSON 字符串**,逐接口而定) |
| `callbackId` | String | 本条消息期望对端回执时带回的 id(发起方生成) |
| `responseId` | String | 回执消息:对应此前收到的 `callbackId` |
| `responseData` | String | 回执消息的返回数据载荷 |
原生侧生成 callbackId 的格式:`JAVA_CB_<自增id>_<时间戳>`(来源 `BridgeWebView.doSend`)。新平台可用任意唯一字符串格式,H5 不关心其内部格式,只负责原样回带。
### 2.5 JS 端 APIH5 使用,原生必须保证这些 API 存在且行为一致)
H5 通过全局对象 `window.WebViewJavascriptBridge` 调用以下方法(由注入的 `WebViewJavascriptBridge.js` 提供):
| JS API | 作用 |
|---|---|
| `registerHandler(handlerName, function(data, responseCallback){...})` | H5 注册一个 handler,供**原生调用**(即接收"原生→H5"消息)。`responseCallback(retData)` 用于把结果回给原生。 |
| `callHandler(handlerName, data, function(responseData){...})` | H5 **调用原生**注册的 handler`responseData` 回调接收原生的同步返回。 |
| `init(function(message, responseCallback){...})` | 设置默认 handler(接收未指定 handlerName 的消息)。 |
| `send(data, responseCallback)` | 向原生默认 handler 发消息。 |
H5 监听桥就绪事件后才开始调用(典型写法,原生需保证此事件/回调时序):
```javascript
function connectBridge(cb){
if (window.WebViewJavascriptBridge) { cb(WebViewJavascriptBridge); }
else { document.addEventListener('WebViewJavascriptBridgeReady',
function(){ cb(WebViewJavascriptBridge); }, false); }
}
```
> 新平台需提供与 lzyzsd/JsBridge **同名同行为**的 `WebViewJavascriptBridge.js`,确保 H5 中已有的 `registerHandler`/`callHandler` 调用全部生效。建议直接复用该库的 `WebViewJavascriptBridge.js` 原文件。
### 2.6 JS 注入时机(关键时序)
来源:`BridgeWebViewClient.onPageFinished`
- 在**每个页面加载完成(onPageFinished)时**,原生把 `WebViewJavascriptBridge.js` 内容注入页面(作为 JS 执行)。
- 注入后,原生把"启动消息队列"(页面加载完成前积压的待发消息)逐条 `_handleMessageFromNative` 下发。
新平台实现要点:
1. 监听页面加载完成事件 → 注入桥 JS。
2. 维护"启动前积压消息队列":页面就绪前原生若调用 H5 handler,先入队,页面就绪后补发。
3. 注入的 JS 解析时会**去除以 `//` 开头的注释行**`BridgeUtil.assetFile2Str`),新平台若直接读取 js 文件注入,需保持等价(或直接内联完整 js)。
### 2.7 一次完整调用的数据流示例
**H5 调用原生(带返回)** —— 例:H5 获取系统时间
```
H5: bridge.callHandler('getTime', '', function(ret){ /* ret = "1700000000000" */ });
└─ 桥生成 message {handlerName:'getTime', data:'', callbackId:'cb_1'}
入队 → 触发 iframe.src='yy://__queue__'
原生: 拦截 yy:// → _fetchQueue() 取出该 message
→ 找到名为 'getTime' 的 handler 执行 → responseCallback("1700000000000")
→ 原生把 {responseId:'cb_1', responseData:'1700000000000'} 经
_handleMessageFromNative 下发
H5: 桥按 responseId 找到 cb_1 回调并执行
```
**原生调用 H5(推送)** —— 例:定位结果推送
```
原生: callHandler('getlocationinfo', '<MaplocationInfo的JSON>', null)
→ message {handlerName:'getlocationinfo', data:'<json>'} 经
_handleMessageFromNative 下发
H5: 桥查到 H5 用 registerHandler('getlocationinfo', ...) 注册的处理函数并执行
```
---
## 3. 应用启动流程
> 来源:`weclomeactivity1.java`、`initwebviewutil`、`GameupdateUtil.java`、`webviewActivity.initwebview`。
> 下述为**行为时序**,新平台按此顺序复刻即可,内部实现自由。
### 3.1 启动总时序
```
启动引导页 onCreate
│ 全屏、强制横屏、保持屏幕常亮
读取本地配置(资源目录名编码,见 §4.1)
│ initfile = "gamehall"gamestart 配置)
│ filestart = "FtJf...ziK"gamedir 配置)
申请运行时权限(存储 / 电话状态 / 定位)
拼接远程配置 URL(见 §4.3
│ configUrl = "http://" + (gameconfig 配置去'-'换'/') + ".txt"
首次安装?
├─ 是:把内置预置包(gamehall)拷贝到内部存储并解压
└─ 否:比较内置 version.xml 与已解压 version.xml 版本,必要时重新拷贝
检测网络 → 请求远程配置(HTTP GET,禁缓存,URL 追加 "?a=<时间戳>"
解析远程配置(JSON
├─ 2.0 格式(含 gamelist):拉取代理二级配置 + 游戏二级配置 → 分层版本计算
└─ 1.0 格式:单文件逐层匹配
版本决策:远程 game_version > 本地 version.xml 的 version
├─ 需要更新:下载 game_download 指向的 zip → 删旧目录 → 解压到资源目录
└─ 无需更新:直接用本地资源
(如有)APK 自升级:app_version 高于本地 appversion → 下载 APK 安装
注入 app_data.js(把配置写进大厅资源目录,见 §6)
跳转大厅容器(不带 Intent extra;配置经 app_data.js 与桥接口获取)
大厅容器加载 file://.../gamehall/index.html?Launchtype=0
```
### 3.2 阻断式提示
远程配置 JSON 顶层若含非空 `showmessage` 字段,启动流程会**弹窗提示并阻断**(用于公告/停服)。配置内容长度过短(< 30 字符)时视为错误文案,直接弹窗。新平台需复刻此"全局公告阻断"行为。
---
## 4. 配置体系
### 4.1 本地配置编码机制(资源目录名编码)
> 来源:`getAllFilename(String name)``weclomeactivity1` / `webviewActivity` 均有同名实现)。
现有 Android 把每个配置项做成一个**资源目录**,目录下放一个**以"配置值"命名的子文件夹**(外加一个占位文件 `BoolTest.java` 需被跳过)。读取逻辑:
```
列出 assets/<配置名>/ 下的条目
跳过名为 "BoolTest.java" 的占位文件
剩下那个"子文件夹的名字"即为配置值
返回该名字(找不到则为空字符串)
```
> **HarmonyOS 适配建议**:这种"用目录名编码配置"的做法是历史包袱。新平台**推荐改为正规的键值配置文件**(如随包内置一个 JSON),只要能提供下表中的同名配置值即可,H5 完全无感知。
### 4.2 配置项清单(本仓库实测值)
| 配置键 | 本仓库实际值 | 含义 / 用途 |
|---|---|---|
| `agent` | `veRa0qrBf0df2K1G4de2tgfmVxB2jxpv` | 代理商 ID(远程配置分层匹配 key) |
| `channel` | `FtJf073aa0d6rI1xD8J1Y42fINTm0ziK` | 渠道 ID(分层匹配 key) |
| `gamedir` | `FtJf073aa0d6rI1xD8J1Y42fINTm0ziK` | 资源解压**父目录名** |
| `gamestart` | `gamehall` | 资源解压后的**游戏目录名**(大厅入口所在目录) |
| `appversion` | `49` | 当前 App 版本号(数字,与远程 `app_version` 比较决定是否升级 APK |
| `market` | `3` | 市场 ID(分层匹配 key;也经桥 `getmarketname` 暴露给 H5 |
| `gameid` | (空,仅占位) | 游戏 ID(空时回退用 version.xml 中的 game id |
| `weburl` | (空) | 大厅 H5 远程地址;**空 → 本地 file:// 加载**(本仓库走本地) |
| `gameconfig` | `tsgames.daoqi88.cn-config_test-update_jsonv2_test` | 远程配置地址(`-``/` 后拼 `.txt` |
| `other` | (空) | 业务自定义值,经桥 `getOther`/`getothername` 暴露给 H5 |
| `tuiguang` | (目录不存在 → 空) | 推广/邀请码,写入 app_data.js 的 `app_invitationcode` |
| `servertype` | (空,**无代码读取**) | 历史预留,启动流程不使用,可不实现 |
| `gameserver` | (空,**无代码读取**) | 历史预留,启动流程不使用,可不实现 |
### 4.3 远程配置请求
**URL 拼装**(来源 `weclomeactivity1.init`):
```
gameconfig 值 = "tsgames.daoqi88.cn-config_test-update_jsonv2_test"
→ 把 '-' 替换为 '/' = "tsgames.daoqi88.cn/config_test/update_jsonv2_test"
→ configUrl = "http://" + 上一步 + ".txt"
= "http://tsgames.daoqi88.cn/config_test/update_jsonv2_test.txt"
请求时再追加防缓存参数: configUrl + "?a=" + 当前毫秒时间戳
请求方式:HTTP GET,强制不走缓存
```
**返回体**:一个 `.txt` 文件,内容为 **JSON**(解析前用 JSON 解析校验;长度 < 30 视为错误提示文案直接弹窗)。存在两套并存格式:
#### 2.0 配置格式(主路径,含 `gamelist` 字段时)
```jsonc
{
"showmessage": "", // 非空 → 弹窗阻断(公告/停服)
"agentlist": [
{
"agentid": "", "agentname": "",
"app_version": "", "app_download": "", "app_size": "",
"game_version": "", "game_download": "", "game_size": "",
"url": "", // 指向"代理专属二级配置"地址,非空则二次请求
"channellist": [ /* 渠道层,结构含 marketlist */ ]
}
],
"gamelist": [
{
"gameid": "", "url": "", // url 指向"游戏专属二级配置",非空则二次请求
"agentlist": [ /* 内含 channellist marketlist */ ]
}
]
}
```
解析流程:
1. 用本机 `agent` 值在 `agentlist` 命中项;若其 `url` 非空 → 二次请求拉**代理二级配置**。
2. 用本机 `gameid` 值在 `gamelist` 命中项;若其 `url` 非空 → 二次请求拉**游戏二级配置**。
3. 两个二级配置都到齐后,做最终版本决策(见 §4.4)。
#### 1.0 配置格式(兼容老格式,无 `gamelist` 时)
单文件内嵌全部层级,直接在一个循环里逐层匹配,无二次请求。
### 4.4 分层覆盖逻辑(agent → channel → market → game
> **此分层覆盖逻辑真实存在**,来源 `GameupdateUtil.agentUtil`(已核实)。匹配 key 为 `{agentid, gameid, channelid, marketid}`(来自本地配置)。
- **代理配置树**`agentid 命中` → 遍历 `channellist`(channelid 命中) → 遍历 `marketlist`(marketid 命中,可设 app 升级) → 遍历该市场 `gamelist`(gameid 命中,设 app/game 升级)。**越深层越后赋值,覆盖前层**。
- **游戏配置树**`gameid 命中``agentlist`(agentid) → `channellist`(channelid) → `marketlist`(marketid,可设 app+game 升级)。同样**后层覆盖前层**。
- **最终合并**:app 升级与 game 升级各自取"代理树与游戏树中 version 更高"者的下载地址,再分别与本地版本比较。
每层可携带的可覆盖字段:`app_version``app_download``app_size``game_version``game_download``game_size``url``showmessage`
> **与旧参考文档的差异提示**:旧版 `TSGame_应用启动流程详解.md` 中给出的配置 JSON`data.agentlist[].channellist[].marketlist[]`、`config_download`、`configVersion` 等字段)是**示意性质,与真实字段名不完全一致**。请以本节真实字段(`agentlist` / `gamelist` 两棵树并存、二级 `url` 二次请求、`game_download`/`game_version` 等)为准。
---
## 5. 资源(大厅/子游戏)管理
### 5.1 真实文件系统路径
> 来源 `initwebviewutil.getdate`。
```
内部存储根 = <App内部files目录> // 例如 Android: /data/data/<包名>/files
父目录(upurlpath) = <files>/tsgames/<包名>/<启动毫秒时间戳>
解压根(urlpath) = <父目录>/<gamedir值> // = .../<时间戳>/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK
游戏内容目录 = <解压根>/<gamestart值> // = .../FtJf...ziK/gamehall
大厅入口 = <解压根>/gamehall/index.html
```
两个路径以键值对持久化(供容器读取):`urlpath`(解压根)、`upurlpath`(父目录)。
> 注意:解压根含**启动时间戳**,意味着每次全新解压会落到新目录。新平台可沿用或简化为固定目录,对 H5 无影响(H5 只看到自己被以 `file://` 加载)。
### 5.2 内置预置包(首次安装)
- 现有工程随包内置 `gamehall` 目录,首启时整体拷贝到解压根,并对其中的 `.zip` / `.so` 解压,然后删除压缩包。
- **本仓库内置包 `gamehall_42_0527.zip` 仅 553 字节,里面只有一个 `version.xml`,是占位/演示包**——真正可运行的大厅 H5(index.html 等)由远程 zip 下载提供。新平台需注意:**不能假设内置包里有完整大厅页面**。
### 5.3 版本文件 version.xml
> 资源目录与内置包内均含 `version.xml`,结构如下(实测):
```xml
<game>
<agent id="..." name="天盛网络"/>
<game id="..." name="友乐游戏"/>
<channel id="..." name="友乐互动游戏"/>
<version value="42" name="1.42"/>
</game>
```
- `version value` 为整数,用于与远程配置 `game_version` 比较(远程更高则下载更新)。
- `agent/game/channel` 的 id 作为资源自带的归属标识。
### 5.4 zip 下载与解压
```
下载 URL = 远程配置 game_download 字段值 + "?a=<时间戳>"
下载到 = <App外部files目录>/dowlod_zip/<时间戳>Projects.zip (目录名拼写即代码原样 "dowlod_zip"
解压步骤:
1. 删除旧的 <解压根>/gamehall 内容
2. 解压 zip 到 <解压根>/ zip 内顶层即含 gamehall/...
3. 删除 zip 文件
4. 进入"注入 app_data.js → 跳大厅"收尾
```
### 5.5 APK 自升级
- 远程 `app_version` 高于本地 `appversion` 时,下载 `app_download` 指向的安装包并触发系统安装。
- HarmonyOS 适配:改为对应平台的应用更新方式即可,与 H5 契约无关。
---
## 6. H5 数据注入:app_data.js
> 来源 `weclomeactivity1.inith5data`。**这是把原生配置传给 H5 的关键机制之一,H5 直接读取这些全局变量,必须复刻。**
跳转大厅前,原生在大厅资源目录生成/改写 `app_data.js`
```
路径:<解压根>/gamehall/app_data.js
```
文件内容为一组全局 JS 变量声明(H5 直接引用),字段如下:
| 全局变量 | 来源 | 含义 |
|---|---|---|
| `app_version` | version.xml 的 version | 资源版本号 |
| `app_gameconfig` | `gameconfig` 配置 | 远程配置地址标识 |
| `app_gamedir` | `gamedir` 配置 | 资源父目录名 |
| `app_gamestart` | `gamestart` 配置 | 游戏目录名(gamehall |
| `app_agent` | `agent` 配置 | 代理商 ID |
| `app_appversion` | `appversion` 配置 | App 版本号 |
| `app_market` | `market` 配置 | 市场 ID |
| `app_channel` | `channel` 配置 | 渠道 ID |
| `app_invitationcode` | `tuiguang` 配置 | 推广/邀请码 |
| `app_Launchtype` | 固定 `'0'`(大厅) | 启动类型 |
| `app_gamename` | 资源/配置 | 游戏名 |
| `app_getwifisignalLevel` | 运行时 | WiFi 信号等级初值 |
> 新平台只需在加载大厅页面前,保证大厅目录下存在等价的 `app_data.js`(同名全局变量、同含义取值)。内部如何生成文件不限。
---
## 7. WebView 容器能力要求
> 来源 `webviewActivity.initWebViewSettings` / `NewwebviewActivity.initWebViewSettings`。下列为 H5 正常运行所需的**能力要求**,新平台用等价配置满足即可。
| 能力 | 要求 | 说明 |
|---|---|---|
| JavaScript | 必须启用 | — |
| DOM Storage / 本地数据库 | 必须启用 | H5 用 localStorage 等持久化 |
| 本地文件访问 | 必须允许 | 加载 `file://` 本地页面 |
| `file://` 跨源访问 | 必须允许(universal access from file URLs | H5 从 file 页面请求本地/远程资源 |
| 混合内容(http+https | 允许(always allow | 本地页面内嵌 http 资源 |
| 明文 HTTP 流量 | 允许 | 远程配置/资源走 http |
| 缓存 | 不使用缓存(每次走网络/本地最新) | — |
| 定位 | 启用(H5 geolocation + 桥定位) | — |
| 缩放 | 关闭(禁缩放、禁内置缩放控件、禁 wide viewport | 固定布局游戏 |
| 多窗口 | 关闭 | — |
| UserAgent | **未自定义**(用平台默认) | H5 不依赖特定 UA |
| Cookie | 未特殊配置 | — |
| 远程调试 | 启用(开发期) | — |
| 屏幕方向 | **横屏**,在横屏两朝向间跟随传感器自动旋转,不进入竖屏 | 横屏项目;H5 仍可经方向接口显式切换(见下) |
> **🔴 屏幕方向约束(横屏项目)**:本项目为**横屏项目**,应用**固定横屏**——在横屏的两个朝向(正向 / 反向 180°)之间**跟随设备重力传感器自动旋转**,但**不进入竖屏**HarmonyOS `auto_rotation_landscape`)。
> 但**遵守 H5 零改动铁律**:H5 经方向接口发起的显式切换**行为完全不变**,与原 Android 一致——
> - `orientation` handler(§8.4):`"1"` → 横屏,**非 `"1"` → 竖屏**(原生仍真实切竖屏,对齐 Android `setRequestedOrientation`);
> - 子游戏 `SwitchOverGameData` 的 `webtype`(§11.1):`"3"` 横屏 / `"2"` 竖屏;
> - 通用网页 `OpenurlTitleData` 的 `orientation`(§11.2):`"1"` 竖屏 / 其他 横屏。
>
> 即"不支持竖屏"指 **默认横屏、不随传感器自动转屏**,**不是**禁止 H5 控制方向。原生以"默认横屏 + 按 H5 请求动态切换方向"落地(见框架文档 §7.4),从而既是横屏项目,又不破坏 H5 零改动。
容器还需提供:
- **本机 HTTP 服务**:用于"截图分享上传"。容器启动一个本机 HTTP server,把地址(`http://<本机IP>:<端口>/testurl`)通过桥 `setPostUrl` 推给 H5(见 §9)。新平台需提供等价的本机上传端点能力,否则截图分享链路不通。
---
## 8. 接口契约一:H5 → 原生(入站 Handler 全集)
> H5 通过 `bridge.callHandler('<名称>', '<data>', responseCallback)` 调用。
> **data 列**标注是裸字符串还是 JSON 字符串;**同步返回列**标注是否通过 `responseCallback` 同步返回(裸字符串/JSON);多数能力的结果是**异步**通过 §9 的出站 handler 推回。
> 来源:`webviewActivity.initwebjh`(行 21853012/ `NewwebviewActivity.initwebjh`(行 21643129)。两容器一致,差异在表后标注。
### 8.1 账号 / 社交
| Handler | data | 同步返回 | 异步回传 | 功能 |
|---|---|---|---|---|
| `accreditlogin` | 裸字符串(`"1"`=QQ,其他=微信;QQ 实为空实现) | 无 | `sharelogin` | 触发授权登录(实际仅微信) |
| `friendsSharetypeUrlToptitleDescript` | JSON`sharetypeBean`,见 §12 | 无 | `sharesuccess`(仅微信有结果) | 分享(微信/QQ/抖音) |
| `getphoto` | 裸字符串(图片下载 JSON 数组) | 无 | `getphoto` | 批量下载图片/头像到本地 |
### 8.2 支付
| Handler | data | 同步返回 | 异步回传 | 功能 |
|---|---|---|---|---|
| `paybrowser` | 裸字符串(收银台 URL) | 无 | 无(H5 收银台页自理) | 浏览器方式支付(**当前生效**) |
| `getGameplay` | JSON`price/body/typeplay/type/user` | 无 | `PayuserPaytypePaystate` | App 内微信支付(**仅旧版 `webviewActivity` 生效**`NewwebviewActivity` 已注释) |
### 8.3 设备 / 系统信息
| Handler | data | 同步返回 | 异步回传 | 功能 |
|---|---|---|---|---|
| `getTime` | 忽略 | 裸字符串(毫秒时间戳) | — | 获取系统时间 |
| `getphonestate` | 忽略 | 裸字符串 int(0 挂断/1 接起/2…) | — | 获取电话状态 |
| `getbattery` | 忽略 | 无 | `getBattery` | 主动获取电量 |
| `getwifiLevel` | 忽略 | 无 | `getwifiLevel` | 主动获取 WiFi 信号 |
| `getnetwork` | 忽略 | 裸字符串(`1`无网/`2`WiFi/`3`移动) | — | 主动获取网络状态 |
| `getcompareCode` | 忽略 | 裸字符串 int(1=本地版本>网络版本,0=否) | — | App 版本比较结果 |
| `getphoneInfo` | 忽略 | 无 | `getphoneinfo` | 获取手机配置信息(型号/IMEI 等) |
| `getmarketname` | 忽略 | 裸字符串(market 值) | — | 获取市场 ID |
| `getothername` | 裸字符串(配置名) | 裸字符串(该配置值) | — | 获取任意本地配置值 |
| `getOther` | 忽略 | 裸字符串(other 值) | — | 获取 other 配置值 |
### 8.4 交互 / 反馈
| Handler | data | 同步返回 | 异步回传 | 功能 |
|---|---|---|---|---|
| `orientation` | 裸字符串(`"1"`横屏,其他竖屏) | 无 | — | 切换屏幕方向 |
| `vibrator` | 裸字符串 long(毫秒) | 无 | — | 震动 |
| `repeatvibrator` | 裸字符串 int`-1`否/`1`重复) | 无 | — | 重复震动 |
| `canclevibrator` | 忽略 | 无 | — | 取消震动 |
| `gameCopytext` | 裸字符串(文本) | 无 | — | 写入剪贴板 |
| `gamepastetext` | 忽略 | 裸字符串(剪贴板内容) | — | 读取剪贴板 |
| `notification` | 裸字符串 | 无 | — | 发送通知(**空实现,未落地**) |
### 8.5 摇一摇
| Handler | data | 异步回传 | 功能 |
|---|---|---|---|
| `startshake` | 忽略 | `shakeEnd` | 开始监听摇一摇 |
| `SwitchShake` | 裸字符串(`"1"`开声音震动/其他关) | — | 摇一摇声音开关 |
| `stopshake` | 忽略 | — | 停止监听 |
### 8.6 定位
| Handler | data | 同步返回 | 异步回传 | 功能 |
|---|---|---|---|---|
| `startlocation` | 裸字符串(`"1"`连续/其他单次) | 无 | `getlocationinfo` | 开启定位 |
| `getlocationinfo` | 忽略 | JSON`MaplocationInfo`;未就绪返回错误码 JSON) | — | 主动取最近一次定位 |
### 8.7 音频
| Handler | data | 异步回传 | 功能 |
|---|---|---|---|
| `prepareaudio` | 忽略 | — | 准备/开始录音(长按) |
| `mediaTypeAudio` | JSON`audiourl/type/user` | `gameui_play_voice` / `gameui_stop_voice` | 播放语音消息 |
| `srcIsloop` | JSON`src/isloop`) | 无 | 播放游戏音效/背景乐 |
| `voicePlaying` | 裸字符串(`"1"`开/其他静音) | — | 语音播放总开关 |
| `getaudiourl` 录音结果 | (由 `prepareaudio` 触发) | `getaudiourl` | 录音上传后回传地址 |
### 8.8 扫码 / 相机 / 浏览器 / 网页
| Handler | data | 同步返回 | 异步回传 | 功能 |
|---|---|---|---|---|
| `opensaoma` | 忽略 | 无 | `getsaomaData` | 打开扫一扫 |
| `opencamera` | 忽略 | 无 | `getcameraaAddress` | 打开相机拍照 |
| `browser` | 裸字符串(URL) | 无 | — | 系统/QQ 浏览器打开链接 |
| `OpenurlTitleData` | JSON`url/title/data` | 无 | `getWebdata` | 打开内置网页容器(子游戏跳转,见 §11) |
| `openApplyDownloadpath` | JSON`packagename/downloadpath`) | 无 | — | 按包名启动 App,失败则浏览器打开下载地址 |
### 8.9 子游戏切换 / 房间(音视频)
| Handler | data | 同步返回 | 异步回传 | 功能 |
|---|---|---|---|---|
| `SwitchOverGameData` | JSON`webtype/Gamedirectory/gamedownloadurl/data`) | 无 | — | 切换子游戏(容器内切换) |
| `getGameinstall` | 裸字符串(目录名) | 裸字符串(`1`已装/`0`未装) | — | 判断子游戏资源是否已就绪 |
| `createRoom` | 裸字符串(`videoinfobean` JSON) | 无 | — | 加入/创建音视频房间(声网) |
| `exitRoom` | 裸字符串 | 无 | — | 退出房间 |
| `getVideoinfo` | 裸字符串(`Othervideoinfo` JSON | 无 | `getVideoinfo` | 请求/绑定远端视频窗口 |
| `DragViewvideoIsshow` | 裸字符串(`"1"`显示/其他隐藏) | 无 | — | 视频悬浮框显隐 |
### 8.10 退出 / 返回
| Handler | data | 异步回传 | 功能 | 备注 |
|---|---|---|---|---|
| `finsh` | 忽略 | — | 退出游戏 | 名字即代码原样(拼写如此) |
| `backgameData` | 裸字符串(data) | — | 子游戏带数据返回大厅 | **仅 `NewwebviewActivity` 有**;以结果码 101 回传给上层容器 |
| `getAddressBook` | 忽略 | `getAddressbook` 已注释) | 获取通讯录 | **未实现** |
### 8.11 两容器差异小结
| 接口 | `webviewActivity`(大厅) | `NewwebviewActivity`(子游戏/微信回调) |
|---|---|---|
| `getGameplay`(App 内微信支付) | ✅ 生效 | ❌ 已注释(改用 `paybrowser` |
| `backgameData`(带数据返回) | ❌ 无 | ✅ 有 |
| `getlocationinfo` 错误码 | 未就绪返回裸 `null` | 返回结构化错误码 JSON`-1` 未就绪/`-2` 权限拒绝) |
| 分享内部分流 | 直连微信 SDK 多分支 | 按 `sharefriend` 分流到统一分享面板 |
| 其余 ~40 个 handler | 完全一致 | 完全一致 |
> 适配结论:实现**统一一套**入站 handler,并对差异项采用更完整的一侧(如 `getlocationinfo` 用结构化错误码、同时支持 `paybrowser` 与 `backgameData`)即可兼容两个容器。
---
## 9. 接口契约二:原生 → H5(出站 Handler 全集)
> 原生通过 `callHandler('<名称>', '<data>', null)` 调用;**H5 必须用 `registerHandler('<名称>', ...)` 注册才能收到**。
> 来源:两容器内 `callHandler` 调用点。
| Handler | data | 调用时机 |
|---|---|---|
| `appservice` | 裸字符串(`"1"`前台/`"2"`后台) | 页面首次加载到 100%、以及前后台切换(resume/pause/stop、息屏/解锁) |
| `setPostUrl` | 裸字符串(`http://<本机IP>:<端口>/testurl`) | 页面加载完成时,下发截图上传地址 |
| `getWebdata` | 裸字符串(网页回传 text / Intent 透传 data) | 页面首次加载完成;或内置网页关闭回传(结果码 101) |
| `getphoto` | JSON 数组(`[{pid, photourl}]` | 图片/头像下载完成 |
| `sharelogin` | JSON`openid/headimgurl/nickname/sex/city/province/unionid` | 微信授权登录成功后 |
| `sharesuccess` | JSON`{success:int, type:int}`success 2 成功/3 取消) | 分享结果回调(仅微信完整) |
| `shakeEnd` | 空字符串 `""` | 摇一摇触发后约 1 秒 |
| `getBattery` | 裸字符串 float(0~1) | H5 主动取电量时;或电量变化广播 |
| `getwifiLevel` | JSON`{ssidname:String, signalLevel:int(0~4)}`) | H5 主动取时;或 WiFi 变化广播 |
| `getnetwork` | 裸字符串 int`1`断网/`2`WiFi/`3`移动) | 网络状态变化广播 |
| `getaudiourl` | JSON`{audiourl:String, time:float}`;旧容器 `webviewActivity` 录音成功时**额外携带 `filepath:String`**,值与 `audiourl` 相同;取消时 audiourl="" time=0 | 录音上传完成 / 录音取消 |
| `gameui_play_voice` | 裸字符串(`user` 透传) | 语音开始播放 |
| `gameui_stop_voice` | 裸字符串(`user` 透传) | 语音播放完成 / 被打断 |
| `getphoneinfo` | JSON`phoneInfoBean`,见 §12 | H5 调 `getphoneInfo` 后 |
| `getVideoinfo` | 裸字符串 int(远端 uid) | 音视频房间其他用户加入时 |
| `PayuserPaytypePaystate` | JSON`{user, state, typeplay, type, data}`state 1 成功/0 取消) | App 内微信支付结果(仅旧版容器) |
| `getlocationinfo` | JSON`MaplocationInfo`;或错误码 JSON) | 定位成功(单次 1 次/连续每 5s);或权限被拒 |
| `phonestate` | 裸字符串 int(来电状态) | 来电状态广播 |
| `getsaomaData` | 裸字符串(扫码结果) | 扫码返回 |
| `getcameraaAddress` | 裸字符串(照片路径) | 拍照返回 |
> 名称陷阱:`NewwebviewActivity` 中有一处出站调用名为 `backgameData-`**末尾带连字符**,data 为空字符串),用于返回键确认对话框流程;与入站的 `backgameData`(无连字符)是两个不同名字。适配时按代码原样区分。
---
## 10. 原生能力专题
> 本节给出各设备能力的"H5 触发 → 入参 → 回传时机/结构",原生内部如何对接第三方 SDK 不限。
### 10.1 分享(微信 / QQ / 抖音)
- **触发**`friendsSharetypeUrlToptitleDescript`,入参 `sharetypeBean`(§12)。
- `sharefriend=="2"` → 微信朋友圈直发;`=="1"` → 弹分享面板由用户选目标(微信好友/QQ/抖音)。
- `type``"1"`网页/文本,`"2"`Canvas 截图(原生实时截当前 WebView 的 canvas 转 base64),`"3"`图片链接,抖音另有 `"4"`视频。
- **回传**:仅微信完整 → `sharesuccess``{success,type}`2 成功/3 取消)。
- **现状/待补**:QQ、抖音当前**无结果回传**(QQ 回调被注释,抖音仅 Toast / 走外部 App scheme)。若 H5 依赖分享回调,新平台需为三端补齐统一 `sharesuccess` 回传。
### 10.2 微信登录
- **触发**`accreditlogin`(非 `"1"` 即微信),scope=`snsapi_userinfo`
- **回传**`sharelogin`,回传**用户资料**openid/unionid/nickname/headimgurl/sex/city/province),非 code。
- **安全提示**:现有实现用客户端硬编码 AppSecret 换 token(见 §13),新平台**建议改为回传 code、由服务端换取**。
### 10.3 支付
- **当前生效**`paybrowser`(data 为收银台 URL,原生开浏览器,无回传,H5 收银台自理)。
- **旧版 App 内支付**`getGameplay` → 微信 SDK → `PayuserPaytypePaystate` 回传结果(仅旧版容器)。`PayReq` 字段:`appId/partnerId/prepayId/packageValue("Sign=WXPay")/nonceStr/timeStamp/sign(MD5大写)`
### 10.4 抖音 / 快手登录
- **未实现**:现有 `sgapi` 实为"闲聊(XianliaoSDK"且整文件注释,无桥接入。新平台如需,须全新对接抖音开放平台/快手 SDK,建议回传 code 给 H5。
### 10.5 截图 / Canvas
- 截图当前**耦合在分享流程内部**,无独立 handler。原生注入 JS 调 H5 `canvas.toDataURL`(默认 image/jpeg 720×405;指定 id 则 png),失败回退原生绘制;结果直接交分享,不回传 H5。
- **待补建议**:若 H5 需主动截图,新增 handler(如 `getCanvasBase64`,入参 canvasId/格式/尺寸,返回纯 base64 或 dataURL)。
### 10.6 音频
- 远程语音消息:`mediaTypeAudio` 播放 → `gameui_play_voice` / `gameui_stop_voice` 回传(透传 `user`)。
- 游戏音效/背景乐:`srcIsloop``src` 为 wav 文件名(非 URL),路径 `<解压根>/<game>/assets/wav/<src>``isloop``0` 播一次 / `>0` 循环 / `<0` 停止。无回传。
- 静音总开关:`voicePlaying`
- 录音:`prepareaudio` 录音 → 上传 → `getaudiourl` 回传 `{audiourl,time}`
### 10.7 摇一摇
- `startshake` 开始 → 触发后约 1 秒 `callHandler('shakeEnd','')` → 自动重新监听。`SwitchShake` 控声音,`stopshake` 停止。
- 灵敏度等参数原生内部决定(现有实现加速度阈值约 3500)。
### 10.8 定位(高德)
- `startlocation``"1"`连续/5s 间隔,其他单次)→ `getlocationinfo``MaplocationInfo`
- `getlocationinfo` 也可同步主动取(一名两用)。
- 错误码约定:`0` 成功 / 高德原始码 / `-1` 未就绪 / `-2` 权限被拒。
- 新平台用本平台定位服务实现,**只要回传 `MaplocationInfo` 同结构同字段即可**。
---
## 11. 子游戏跳转与通用网页容器
> H5 从大厅进入"另一个页面"有**两条独立路径**,机制完全不同,新平台都要实现:
> - **路径 A(容器内切换子游戏)**`SwitchOverGameData` → 在**同一个 BridgeWebView 容器**内重载到另一个游戏目录,仍走 §2 Bridge 协议。
> - **路径 B(打开通用网页)**`OpenurlTitleData` → 打开**独立的通用网页容器 `openwebActivity1`**,该容器用 §11.3 的**另一套接口机制**`settings` 对象注入 + `javascript:` 直调)。
### 11.1 路径 A:容器内切换子游戏(SwitchOverGameData
> 来源:`webviewActivity`/`NewwebviewActivity` 的 `SwitchOverGameData` → `initgame()`。
- H5 调用:`bridge.callHandler('SwitchOverGameData', JSON.stringify({webtype, Gamedirectory, gamedownloadurl, data}))`
- `webtype``"2"`竖屏 / `"3"`横屏
- `Gamedirectory`:目标游戏目录名
- `gamedownloadurl`:游戏 id(用于判断/下载)
- `data`:交换数据
- 原生在**当前 BridgeWebView 容器内**重载到 `file://<解压根>/<Gamedirectory>/index.html?Launchtype=1`(必要时先下载解压该游戏 zip),**桥协议与全部 handler 不变**,对 H5 透明。
**关键结论**:本路径下子游戏 H5 通常已包含在 §5 下载解压的整包内;原生按 `Gamedirectory` 切换目录加载,仍复用同一套 Bridge 接口。
### 11.2 路径 B:打开通用网页(OpenurlTitleData
> 来源:`webviewActivity`/`NewwebviewActivity` 的 `OpenurlTitleData` → 打开 `openwebActivity1`。
```
大厅/游戏 H5 拼好目标地址(本地 file:// 或远程 http://
bridge.callHandler('OpenurlTitleData', JSON.stringify({url, title, data}))
原生打开通用网页容器 openwebActivity1,加载该 urlIntent extraurl/title/data/orientation
│ orientation"1"=竖屏,其他=横屏
页面加载到 100% → 原生用【javascript: 直调】getWebdata('<data>') 把交互数据交给页面
页面内 H5 调 settings.backgameData('<data>')(或返回键确认)→ 原生 setResult(101, data) 退出
回到上层 Bridge 容器 → 上层收到出站 getWebdata(携带回传 data
```
> 注意:`openwebActivity1` **不是 BridgeWebView**,页面里**无 `window.WebViewJavascriptBridge`**H5 在该容器内只能用 §11.3 的 `settings` 接口和被原生 `javascript:` 直调的全局函数。
### 11.3 通用网页容器 openwebActivity1 的独立接口契约
> 来源:`openwebActivity1.java`。机制 = **普通 WebView + 注入对象 `settings``@JavascriptInterface`+ 原生 `javascript:` 直接调用全局函数**。新平台需为该容器单独实现这套契约(等价于 HarmonyOS 的 `javaScriptProxy` + `runJavaScript`)。
#### (1) H5 → 原生:全局注入对象 `settings`(对象名固定为 `settings`
H5 直接调用 `window.settings.<方法>(...)`
| 方法签名 | 参数 | 功能 |
|---|---|---|
| `settings.backgameData(data)` | `data`:String | 带数据返回上层容器(原生 `setResult(101,{data})` 并关闭本页) |
| `settings.loadurl(urls)` | `urls`:String | 在本容器加载新 url |
| `settings.browser(browserurl)` | `browserurl`:String | 用系统/QQ 浏览器打开链接 |
| `settings.finishweb()` | 无 | 直接关闭本页 |
| `settings.isexitdialogeshow()` | 无 | 开启"返回时弹退出确认框"行为 |
| `settings.isbackfinishweb()` | 无 | 设置返回键改为交给 H5(触发下方 `gamebackkeydown()`)而非直接关闭 |
#### (2) 原生 → H5`javascript:` 直接调用的全局函数(H5 需在 window 上定义同名函数)
| 全局函数(H5 须实现) | 参数 | 调用时机 |
|---|---|---|
| `getWebdata('<data>')` | 字符串 data(来自打开时传入的 `data`) | 页面首次加载到 100% 时 |
| `gamebackkeydown()` | 无 | 按下返回键、且 H5 已通过 `settings.isbackfinishweb()` 接管返回键、且 WebView 可后退时 |
| `backgameData()` | 无(**注意:与 H5→原生的 `settings.backgameData(data)` 同名但无参、方向相反**) | "返回退出确认框"点确认时,原生回调通知页面即将退出 |
#### (3) 该容器的 WebView 设置差异(相对 §7 主容器)
- **不注入** `WebViewJavascriptBridge.js`;无 `yy://` 拦截。
- 缓存策略不同:有网时用默认缓存(`LOAD_DEFAULT`),无网时用缓存优先(`LOAD_CACHE_ELSE_NETWORK`);启用 AppCache。
- 同样启用:JavaScript、DOMStorage、文件访问、file:// 跨源访问、定位;禁缩放/禁多窗口。
- WebViewClient 自行拦截 `weixin:` / `alipayqr:` / `alipays:` / `tel:` 等 scheme 跳转到系统处理。
> 适配要点:新平台需提供**两类容器**——
> ① BridgeWebView 容器(大厅+子游戏,§2 协议 + §8/§9 全部 handler);
> ② 通用网页容器(§11.3 的 `settings` 注入对象 6 方法 + `getWebdata`/`gamebackkeydown`/`backgameData` 三个 `javascript:` 直调)。
> 两类容器都实现到位,现有 H5(含其打开的活动页/收银台/客服等)才能**零改动**运行。
---
## 12. 数据结构汇总
> 字段名严格对应代码,新平台序列化必须保持**同名同语义**。
### sharetypeBean(分享入参,全 String
```jsonc
{
"sharefriend": "1", // "1" 好友(弹面板) / "2" 朋友圈(直发微信)
"type": "1", // "1" 网页/文本 / "2" Canvas截图 / "3" 图片链接 / "4" 视频(抖音)
"sharetype": "", // 分享子类型
"webpageUrl": "https://...", // 分享链接 或 图片地址
"title": "标题",
"description": "描述"
}
```
### MaplocationInfo(定位回传)
```jsonc
{
"latitude": 0.0, "longitude": 0.0, // double
"address": "", "country": "", "province": "", "city": "",
"district": "", "street": "", "cityCode": "", "streetNum": "",
"adCode": "", "aoiName": "", // 以上 String
"accuracy": 0.0, // float
"locationType": 0, // int
"errorCode": 0, // int0 成功 / -1 未就绪 / -2 权限拒绝 / 其他=底层错误码
"errorMsg": "" // String
}
```
### phoneInfoBean(手机信息回传,全 String
```jsonc
{
"PhoneVersion": "", "PhoneAdresseMAC": "", "PhoneModel": "",
"PhoneDeviceBrand": "", "PhoneProvidersName": "",
"PhoneIMEI": "", "PhoneIMSI": ""
}
```
### savephotoURLBean(图片下载,getphoto 回传数组元素)
```jsonc
{ "pid": "", "photourl": "" }
```
### videoinfobeancreateRoom 入参,全 String
```jsonc
{ "playerid":"", "roomid":"", "agentid":"", "gameid":"",
"left":"", "top":"", "pmw":"", "pmh":"", "width":"", "height":"" }
```
### OthervideoinfogetVideoinfo 入参)
```jsonc
{ "playerid":"", "left":"", "top":"", "width":"", "height":"", "pmw":"", "pmh":"" }
```
### 支付相关
- `getGameplay` 入参:`{ "price":"", "body":"", "typeplay":int, "type":"", "user":int }`
- `PayuserPaytypePaystate` 回传:`{ "user":"", "state":int(1成功/0取消), "typeplay":"", "type":"", "data":"" }`
### sharelogin(微信登录回传,全 String
```jsonc
{ "openid":"", "headimgurl":"", "nickname":"", "sex":"", "city":"", "province":"", "unionid":"" }
```
### sharesuccess(分享回传)
```jsonc
{ "success": 2, "type": 1 } // success: 2成功/3取消;type: 1好友/2朋友圈
```
---
## 13. 关键常量与第三方账号
> 来源:`simcpux/Constants.java`、`AndroidManifest.xml`。新平台需用自己的应用账号重新申请,下列仅说明依赖项。
| 项 | 值 | 说明 |
|---|---|---|
| 微信 AppID | `wxd2bd650e06bdfe58` | 分享/登录/支付 |
| 微信商户号 MCH_ID | `1448669802` | App 内支付 |
| 微信 API_KEY | `ClMrQsAcidRa7uJT4TgHgVAOHbzQjdPa` | 支付签名(**应下沉服务端**) |
| 微信 AppSecret | `1934a281c82ad1a059130fe51341b74b` | 登录换 token**应下沉服务端**) |
| 高德定位 API Key | `4f92bacc16cace69a6045a544d6e3c7d` | 定位 |
| 微信回调 scheme | `wxd2bd650e06bdfe58` | 第三方回调 |
| 自定义 scheme | `gamepaywelcome``gamepaywxd2bd650e06bdfe58` | H5/外部唤起原生页 |
**关键权限**:存储读写、读电话状态、定位(精确+粗略)、安装应用、网络、相机、录音、允许明文 HTTP。
> ⚠️ 安全提示:现有工程把微信 AppSecret / API_KEY 明文硬编码在客户端,存在风险。HarmonyOS 新版应将签名与换 token 逻辑下沉到服务端,登录改为回传 code。
---
## 14. HarmonyOS 适配检查清单与待决策项
### 14.1 必做(保证 H5 零改动运行)
- [ ] **实现 WebViewJavascriptBridge 协议**URL scheme `yy://` 拦截、`_handleMessageFromNative` / `_fetchQueue`、Message JSON 结构、`onPageFinished` 注入 `WebViewJavascriptBridge.js`、启动消息队列补发(§2)。直接复用该库的 `WebViewJavascriptBridge.js`
- [ ] **实现全部入站 handler(§8**:44 个,名称/参数结构/同步返回 1:1 对齐;差异项取更完整一侧。
- [ ] **实现全部出站 handler 调用(§9)**:在对应时机以同名 `callHandler` 推送同结构 data。
- [ ] **配置体系(§4**:提供 §4.2 全部配置键取值(建议改用 KV 配置文件,无需沿用目录名编码)。
- [ ] **远程配置请求与分层覆盖(§4.3/4.4)**:URL 拼装、二级 url 二次请求、agent→channel→market→game 后层覆盖、`showmessage` 阻断。
- [ ] **资源管理(§5**:内置包拷贝、version.xml 版本比较、远程 zip 下载/删旧/解压到资源目录。
- [ ] **app_data.js 注入(§6**:加载大厅前在大厅目录生成同名全局变量。
- [ ] **大厅入口(§3**`weburl` 空 → `file://.../gamehall/index.html?Launchtype=0`;非空 → `http://<weburl去-换/>?Launchtype=0`
- [ ] **WebView 能力(§7**JS、DOMStorage、file/universal-file 访问、混合内容、明文 HTTP、禁缓存、禁缩放、定位。
- [ ] **路径 A 子游戏切换(§11.1)**`SwitchOverGameData` 在 BridgeWebView 容器内按目录重载、横竖屏切换。
- [ ] **路径 B + 通用网页容器(§11.2/§11.3**`OpenurlTitleData` 打开独立网页容器;该容器实现 **`settings` 注入对象 6 方法**`backgameData(data)`/`loadurl(urls)`/`browser(url)`/`finishweb()`/`isexitdialogeshow()`/`isbackfinishweb()`+ **3 个 `javascript:` 直调全局函数**`getWebdata(data)`/`gamebackkeydown()`/`backgameData()`)+ 结果码 101 回传上层 → 上层出站 `getWebdata`。**这是与主容器不同的第二套机制,勿遗漏。**
- [ ] **本机 HTTP 上传端点 + `setPostUrl`(§7/§9**:支撑截图分享上传。
- [ ] **设备能力(§10**:分享、微信登录/支付、定位、录音/音效、摇一摇、扫码、相机、震动、剪贴板、网络/电量/WiFi/电话状态。
### 14.2 待决策 / 需补齐(现有 Android 即缺失,新平台应主动完善)
| 事项 | 现状 | 建议 |
|---|---|---|
| QQ / 抖音分享结果回传 | 无回传 | 补齐统一 `sharesuccess` 回传 |
| 抖音 / 快手登录 | 完全未实现(sgapi 是闲聊且注释) | 按需全新对接,回传 code |
| 主动截图能力 | 仅耦合在分享内 | 新增独立 `getCanvasBase64` handler |
| 通讯录 `getAddressBook` | 空实现 | 按需实现或保持空 |
| `notification` 通知 | 空实现 | 按需实现 |
| 微信 AppSecret/API_KEY 明文 | 客户端硬编码 | 下沉服务端,登录改 code 模式 |
| `servertype` / `gameserver` 配置 | 有目录无代码读取 | 可不实现 |
| App 内微信支付 vs 浏览器支付 | 新旧容器不一致 | 统一用 `paybrowser`,按需保留 App 内支付 |
### 14.3 接口名"原样保留"陷阱清单(拼写以代码为准,不可纠正)
- `finsh`(退出,非 finish
- `accreditlogin``friendsSharetypeUrlToptitleDescript``SwitchOverGameData``DragViewvideoIsshow`
- `getcameraaAddress`(双 a)、`getsaomaData``opensaoma`
- `PayuserPaytypePaystate``gameui_play_voice` / `gameui_stop_voice`
- `canclevibrator`(非 cancel)、`repeatvibrator`
- 出站 `backgameData-`(末尾连字符)≠ 入站 `backgameData`
- 下载目录名 `dowlod_zip`(拼写如此)
---
> **结论**:H5 与原生之间的契约 = §2 桥协议 + §8 入站 44 handler + §9 出站约 21 handler + §6 `app_data.js` 注入 + §3~§5 启动与资源流程。新平台(HarmonyOS)只要让这套契约对外表现完全一致,现有 H5 即可**零改动**运行;原生内部实现方式不受任何限制。