Files
youle_app_ohos/docs/设计文档/TSGame_原生与H5接口契约总规范.md
T
lanterngamescnandClaude Opus 4.8 3a333bfe32 docs: 补充横屏项目约束(默认横屏、不支持竖屏自动旋转)
- 契约规范 §7:新增屏幕方向约束——默认横屏、不跟随传感器自动旋转;
  但遵守 H5 零改动铁律,保留 orientation/SwitchOverGameData webtype/通用网页
  orientation 三处 H5 显式方向控制(含竖屏),与原 Android 一致
- 框架指南 §7.4:实现落地——module.json5 orientation:landscape 默认横屏 +
  运行时 window.setPreferredOrientation 按 H5 请求动态切换
- 经核对原 Android:上述三处确为真实竖屏切换(非死代码),故定调为
  '默认横屏 + 不破坏 H5 方向控制'

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

51 KiB
Raw Blame History

TSGame 原生 ↔ H5 接口契约与启动流程总规范(跨平台适配版)

文档目的:本规范以现有 Android 工程的真实代码为唯一依据,抽象出"原生容器"与"H5 游戏内容"之间的完整接口契约启动/资源加载流程。 目标是:在一个全新的原生平台(HarmonyOS)上重新实现"原生容器",使现有 H5 一行代码都不改动即可正常运行。

适配原则

  • 本文档只规定"契约"(协议、接口名、参数结构、调用时机、数据流),不规定原生内部如何实现——原生侧(包括 HarmonyOS)可根据平台能力自由实现,只要对外行为与本契约一致即可。
  • 文中所有接口名、参数字段、常量值均来自真实源码,并标注了来源文件与行号,便于核对。
  • 凡现有 Android 代码中"未实现/已注释/无回传"的部分,本文明确标注,作为新平台需要补齐或决策的事项。

目录

  1. 总体架构
  2. 核心:JS 通信桥协议(WebViewJavascriptBridge
  3. 应用启动流程
  4. 配置体系
  5. 资源(大厅/子游戏)管理
  6. H5 数据注入:app_data.js
  7. WebView 容器能力要求
  8. 接口契约一:H5 → 原生(入站 Handler 全集)
  9. 接口契约二:原生 → H5(出站 Handler 全集)
  10. 原生能力专题
  11. 子游戏跳转与通用网页容器
  12. 数据结构汇总
  13. 关键常量与第三方账号
  14. HarmonyOS 适配检查清单与待决策项

1. 总体架构

整个 App 本质上是一个多 WebView 容器 + 资源管理器,所有业务逻辑(大厅、棋牌子游戏)都是 H5,原生只提供"启动引导 + 资源下载解压 + 设备能力桥接"。

┌──────────────────────────────────────────────────────────────┐
│                        原生容器(可在任意平台重写)              │
│                                                                │
│  [启动引导页] ──► 读本地配置 ──► 请求远程配置 ──► 下载/解压资源 │
│       │                                              │         │
│       └──────────────► 注入 app_data.js ─────────────┘         │
│                              │                                 │
│                              ▼                                 │
│   ┌────────────────────────────────────────────────────┐     │
│   │              WebView 容器(大厅 / 子游戏)            │     │
│   │   加载 file://.../index.html?Launchtype=X            │     │
│   │                                                      │     │
│   │   ◄──── JS 通信桥(WebViewJavascriptBridge ────►   │     │
│   │        H5 ⇄ 原生:登录/分享/支付/定位/音频/震动…     │     │
│   └────────────────────────────────────────────────────┘     │
│                              │                                 │
│   设备能力:微信/QQ/抖音分享、微信支付登录、高德定位、          │
│             录音、音效、摇一摇、扫码、相机、剪贴板、震动…       │
└──────────────────────────────────────────────────────────────┘

1.1 容器角色划分(现有 Android 实现)

角色 现有 Android 类(仅作来源标注,新平台无需同名) 说明
启动引导页 weclomeactivity1Launcher,强制横屏) 读配置、请求远程配置、下载解压资源、注入 app_data.js,完成后跳大厅
大厅容器 webviewActivity 加载大厅 H5.../gamehall/index.html
子游戏/内置网页容器 openwebActivity1 由大厅通过桥接口 OpenurlTitleData 打开任意 H5 url
微信回调容器 NewwebviewActivity 与大厅容器接口集几乎完全相同,承担微信 scheme 回调

重要说明webviewActivityNewwebviewActivity 注册的桥接口(44 个入站 handler)名称、参数结构、回调名几乎完全一致,差异仅 3 处(见 §8.x 标注)。新平台只需实现一套统一的桥接口契约即可同时覆盖大厅与子游戏容器。本文档给出的是这套统一契约。


2. 核心:JS 通信桥协议(WebViewJavascriptBridge

这是整个适配的重中之重。H5 完全依赖此协议与原生通信,不可更改。新平台必须 1:1 实现此协议,包括其 URL scheme、消息格式、JS 全局对象与注入时机。 来源:com/tagmae/jsbridge/BridgeWebViewBridgeWebViewClientBridgeUtilMessage)。本协议是开源库 lzyzsd/JsBridge 的实现。

2.1 协议总览

⚠️ 重要范围说明:本章描述的 WebViewJavascriptBridge 协议适用于大厅容器与子游戏容器(基于 BridgeWebView,即 webviewActivity / NewwebviewActivity)。 另有一个独立的通用网页容器 openwebActivity1(由桥接口 OpenurlTitleData 打开,用于活动页/收银台/客服等"打开网页"场景),它不使用本协议,而是用传统的"JS 接口对象注入(@JavascriptInterface,对象名 settings+ javascript: 直接函数调用"。该容器的接口契约见 §11.3,新平台两套机制都要实现

在基于 BridgeWebView 的两个主容器里,业务通信不使用平台的"JS 接口注入"机制(如 Android @JavascriptInterface、HarmonyOS javaScriptProxy)作为通道——其中所有 addJavascriptInterface(new settings(), "settings") 均已注释失效,仅保留的崩溃监控用途与业务无关。这两个主容器的业务通信 100% 走下述 URL scheme 拦截协议。

另:源码中存在 ShareJavascriptInterface(注入名 NativeShare,方法 shareToQQ/shareToDouYin),但其注入入口 ShareActivityPatch 在全工程无任何引用,是死代码H5 无法依赖,新平台无需实现

通信靠两条单向通道拼成双向:

  1. 原生 → H5:原生执行 JSWebViewJavascriptBridge._handleMessageFromNative('<消息JSON>')
  2. H5 → 原生:H5 改变一个隐藏 iframe 的 srcyy://... 触发导航;原生在"资源加载拦截/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 端 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 调用原生注册的 handlerresponseData 回调接收原生的同步返回。
init(function(message, responseCallback){...}) 设置默认 handler(接收未指定 handlerName 的消息)。
send(data, responseCallback) 向原生默认 handler 发消息。

H5 监听桥就绪事件后才开始调用(典型写法,原生需保证此事件/回调时序):

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.javainitwebviewutilGameupdateUtil.javawebviewActivity.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 字段时)

{
  "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_versionapp_downloadapp_sizegame_versiongame_downloadgame_sizeurlshowmessage

与旧参考文档的差异提示:旧版 TSGame_应用启动流程详解.md 中给出的配置 JSONdata.agentlist[].channellist[].marketlist[]config_downloadconfigVersion 等字段)是示意性质,与真实字段名不完全一致。请以本节真实字段(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,结构如下(实测):

<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 仍可经方向接口显式切换(见下)

🔴 屏幕方向约束(横屏项目):本项目为横屏项目,应用默认横屏、不跟随设备重力传感器自动旋转。 但遵守 H5 零改动铁律H5 经方向接口发起的显式切换行为完全不变,与原 Android 一致——

  • orientation handler(§8.4):"1" → 横屏,"1" → 竖屏(原生仍真实切竖屏,对齐 Android setRequestedOrientation);
  • 子游戏 SwitchOverGameDatawebtype(§11.1):"3" 横屏 / "2" 竖屏;
  • 通用网页 OpenurlTitleDataorientation(§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(行 2164–3129)。两容器一致,差异在表后标注。

8.1 账号 / 社交

Handler data 同步返回 异步回传 功能
accreditlogin 裸字符串("1"=QQ,其他=微信;QQ 实为空实现) sharelogin 触发授权登录(实际仅微信)
friendsSharetypeUrlToptitleDescript JSONsharetypeBean,见 §12 sharesuccess(仅微信有结果) 分享(微信/QQ/抖音)
getphoto 裸字符串(图片下载 JSON 数组) getphoto 批量下载图片/头像到本地

8.2 支付

Handler data 同步返回 异步回传 功能
paybrowser 裸字符串(收银台 URL 无(H5 收银台页自理) 浏览器方式支付(当前生效
getGameplay JSONprice/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无网/2WiFi/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 忽略 JSONMaplocationInfo;未就绪返回错误码 JSON 主动取最近一次定位

8.7 音频

Handler data 异步回传 功能
prepareaudio 忽略 准备/开始录音(长按)
mediaTypeAudio JSONaudiourl/type/user gameui_play_voice / gameui_stop_voice 播放语音消息
srcIsloop JSONsrc/isloop 播放游戏音效/背景乐
voicePlaying 裸字符串("1"开/其他静音) 语音播放总开关
getaudiourl 录音结果 (由 prepareaudio 触发) getaudiourl 录音上传后回传地址

8.8 扫码 / 相机 / 浏览器 / 网页

Handler data 同步返回 异步回传 功能
opensaoma 忽略 getsaomaData 打开扫一扫
opencamera 忽略 getcameraaAddress 打开相机拍照
browser 裸字符串(URL 系统/QQ 浏览器打开链接
OpenurlTitleData JSONurl/title/data getWebdata 打开内置网页容器(子游戏跳转,见 §11)
openApplyDownloadpath JSONpackagename/downloadpath 按包名启动 App,失败则浏览器打开下载地址

8.9 子游戏切换 / 房间(音视频)

Handler data 同步返回 异步回传 功能
SwitchOverGameData JSONwebtype/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(子游戏/微信回调)
getGameplayApp 内微信支付) 生效 已注释(改用 paybrowser
backgameData(带数据返回)
getlocationinfo 错误码 未就绪返回裸 null 返回结构化错误码 JSON-1 未就绪/-2 权限拒绝)
分享内部分流 直连微信 SDK 多分支 sharefriend 分流到统一分享面板
其余 ~40 个 handler 完全一致 完全一致

适配结论:实现统一一套入站 handler,并对差异项采用更完整的一侧(如 getlocationinfo 用结构化错误码、同时支持 paybrowserbackgameData)即可兼容两个容器。


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 JSONopenid/headimgurl/nickname/sex/city/province/unionid 微信授权登录成功后
sharesuccess JSON{success:int, type:int}success 2 成功/3 取消) 分享结果回调(仅微信完整)
shakeEnd 空字符串 "" 摇一摇触发后约 1 秒
getBattery 裸字符串 float0~1 H5 主动取电量时;或电量变化广播
getwifiLevel JSON{ssidname:String, signalLevel:int(0~4)} H5 主动取时;或 WiFi 变化广播
getnetwork 裸字符串 int1断网/2WiFi/3移动) 网络状态变化广播
getaudiourl JSON{audiourl:String, time:float};旧容器 webviewActivity 录音成功时额外携带 filepath:String,值与 audiourl 相同;取消时 audiourl="" time=0 录音上传完成 / 录音取消
gameui_play_voice 裸字符串(user 透传) 语音开始播放
gameui_stop_voice 裸字符串(user 透传) 语音播放完成 / 被打断
getphoneinfo JSONphoneInfoBean,见 §12 H5 调 getphoneInfo
getVideoinfo 裸字符串 int(远端 uid 音视频房间其他用户加入时
PayuserPaytypePaystate JSON{user, state, typeplay, type, data}state 1 成功/0 取消) App 内微信支付结果(仅旧版容器)
getlocationinfo JSONMaplocationInfo;或错误码 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)。
  • 游戏音效/背景乐:srcIsloopsrc 为 wav 文件名(非 URL),路径 <解压根>/<game>/assets/wav/<src>isloop0 播一次 / >0 循环 / <0 停止。无回传。
  • 静音总开关:voicePlaying
  • 录音:prepareaudio 录音 → 上传 → getaudiourl 回传 {audiourl,time}

10.7 摇一摇

  • startshake 开始 → 触发后约 1 秒 callHandler('shakeEnd','') → 自动重新监听。SwitchShake 控声音,stopshake 停止。
  • 灵敏度等参数原生内部决定(现有实现加速度阈值约 3500)。

10.8 定位(高德)

  • startlocation"1"连续/5s 间隔,其他单次)→ getlocationinfoMaplocationInfo
  • getlocationinfo 也可同步主动取(一名两用)。
  • 错误码约定:0 成功 / 高德原始码 / -1 未就绪 / -2 权限被拒。
  • 新平台用本平台定位服务实现,只要回传 MaplocationInfo 同结构同字段即可

11. 子游戏跳转与通用网页容器

H5 从大厅进入"另一个页面"有两条独立路径,机制完全不同,新平台都要实现:

  • 路径 A(容器内切换子游戏)SwitchOverGameData → 在同一个 BridgeWebView 容器内重载到另一个游戏目录,仍走 §2 Bridge 协议。
  • 路径 B(打开通用网页)OpenurlTitleData → 打开独立的通用网页容器 openwebActivity1,该容器用 §11.3 的另一套接口机制settings 对象注入 + javascript: 直调)。

11.1 路径 A:容器内切换子游戏(SwitchOverGameData

来源:webviewActivity/NewwebviewActivitySwitchOverGameDatainitgame()

  • 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/NewwebviewActivityOpenurlTitleData → 打开 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) 原生 → H5javascript: 直接调用的全局函数(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

{
  "sharefriend": "1",      // "1" 好友(弹面板) / "2" 朋友圈(直发微信)
  "type": "1",             // "1" 网页/文本 / "2" Canvas截图 / "3" 图片链接 / "4" 视频(抖音)
  "sharetype": "",         // 分享子类型
  "webpageUrl": "https://...", // 分享链接 或 图片地址
  "title": "标题",
  "description": "描述"
}

MaplocationInfo(定位回传)

{
  "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)

{
  "PhoneVersion": "", "PhoneAdresseMAC": "", "PhoneModel": "",
  "PhoneDeviceBrand": "", "PhoneProvidersName": "",
  "PhoneIMEI": "", "PhoneIMSI": ""
}

savephotoURLBean(图片下载,getphoto 回传数组元素)

{ "pid": "", "photourl": "" }

videoinfobeancreateRoom 入参,全 String

{ "playerid":"", "roomid":"", "agentid":"", "gameid":"",
  "left":"", "top":"", "pmw":"", "pmh":"", "width":"", "height":"" }

OthervideoinfogetVideoinfo 入参)

{ "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)

{ "openid":"", "headimgurl":"", "nickname":"", "sex":"", "city":"", "province":"", "unionid":"" }

sharesuccess(分享回传)

{ "success": 2, "type": 1 }   // success: 2成功/3取消;type: 1好友/2朋友圈

13. 关键常量与第三方账号

来源:simcpux/Constants.javaAndroidManifest.xml。新平台需用自己的应用账号重新申请,下列仅说明依赖项。

说明
微信 AppID wxd2bd650e06bdfe58 分享/登录/支付
微信商户号 MCH_ID 1448669802 App 内支付
微信 API_KEY ClMrQsAcidRa7uJT4TgHgVAOHbzQjdPa 支付签名(应下沉服务端
微信 AppSecret 1934a281c82ad1a059130fe51341b74b 登录换 token应下沉服务端
高德定位 API Key 4f92bacc16cace69a6045a544d6e3c7d 定位
微信回调 scheme wxd2bd650e06bdfe58 第三方回调
自定义 scheme gamepaywelcomegamepaywxd2bd650e06bdfe58 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:加载大厅前在大厅目录生成同名全局变量。
  • 大厅入口(§3weburl 空 → file://.../gamehall/index.html?Launchtype=0;非空 → http://<weburl去-换/>?Launchtype=0
  • WebView 能力(§7JS、DOMStorage、file/universal-file 访问、混合内容、明文 HTTP、禁缓存、禁缩放、定位。
  • 路径 A 子游戏切换(§11.1SwitchOverGameData 在 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
  • accreditloginfriendsSharetypeUrlToptitleDescriptSwitchOverGameDataDragViewvideoIsshow
  • getcameraaAddress(双 a)、getsaomaDataopensaoma
  • PayuserPaytypePaystategameui_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 即可零改动运行;原生内部实现方式不受任何限制。