# 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('');` - 抽取 H5 待发队列:`javascript:WebViewJavascriptBridge._fetchQueue();` - 该调用本身被当作一次"原生→H5"调用,其回执由 H5 通过 `yy://return/_fetchQueue/` 送回原生。 ### 2.4 消息体(Message)JSON 结构 来源:`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 端 API(H5 使用,原生必须保证这些 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', '', null) → message {handlerName:'getlocationinfo', data:''} 经 _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`。 ``` 内部存储根 = // 例如 Android: /data/data/<包名>/files 父目录(upurlpath) = /tsgames/<包名>/<启动毫秒时间戳> 解压根(urlpath) = <父目录>/ // = .../<时间戳>/FtJf073aa0d6rI1xD8J1Y42fINTm0ziK 游戏内容目录 = <解压根>/ // = .../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 ``` - `version value` 为整数,用于与远程配置 `game_version` 比较(远程更高则下载更新)。 - `agent/game/channel` 的 id 作为资源自带的归属标识。 ### 5.4 zip 下载与解压 ``` 下载 URL = 远程配置 game_download 字段值 + "?a=<时间戳>" 下载到 = /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('<名称>', '', responseCallback)` 调用。 > **data 列**标注是裸字符串还是 JSON 字符串;**同步返回列**标注是否通过 `responseCallback` 同步返回(裸字符串/JSON);多数能力的结果是**异步**通过 §9 的出站 handler 推回。 > 来源:`webviewActivity.initwebjh`(行 2185–3012)/ `NewwebviewActivity.initwebjh`(行 2164–3129)。两容器一致,差异在表后标注。 ### 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('<名称>', '', 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` 实为"闲聊(Xianliao)SDK"且整文件注释,无桥接入。新平台如需,须全新对接抖音开放平台/快手 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),路径 `<解压根>//assets/wav/`;`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://<解压根>//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,加载该 url(Intent extra:url/title/data/orientation) │ orientation:"1"=竖屏,其他=横屏 ▼ 页面加载到 100% → 原生用【javascript: 直调】getWebdata('') 把交互数据交给页面 ▼ 页面内 H5 调 settings.backgameData('')(或返回键确认)→ 原生 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`) | 页面首次加载到 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, // int:0 成功 / -1 未就绪 / -2 权限拒绝 / 其他=底层错误码 "errorMsg": "" // String } ``` ### phoneInfoBean(手机信息回传,全 String) ```jsonc { "PhoneVersion": "", "PhoneAdresseMAC": "", "PhoneModel": "", "PhoneDeviceBrand": "", "PhoneProvidersName": "", "PhoneIMEI": "", "PhoneIMSI": "" } ``` ### savephotoURLBean(图片下载,getphoto 回传数组元素) ```jsonc { "pid": "", "photourl": "" } ``` ### videoinfobean(createRoom 入参,全 String) ```jsonc { "playerid":"", "roomid":"", "agentid":"", "gameid":"", "left":"", "top":"", "pmw":"", "pmh":"", "width":"", "height":"" } ``` ### Othervideoinfo(getVideoinfo 入参) ```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://?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 即可**零改动**运行;原生内部实现方式不受任何限制。