first commit

This commit is contained in:
2026-06-25 06:25:30 +08:00
commit 561af47817
46 changed files with 2719 additions and 0 deletions
@@ -0,0 +1,845 @@
# 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 | 未特殊配置 | — |
| 远程调试 | 启用(开发期) | — |
容器还需提供:
- **本机 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 即可**零改动**运行;原生内部实现方式不受任何限制。