H5 唤起 App 完整方案:Scheme、Universal Link、wx-open-launch-app 与微信兜底
H5 唤起原生 App,是移动端前端开发最经典的难题之一。
问题不在于”怎么做”,而在于”怎么做才能在所有场景下都有效”:
- iOS:Apple 从 iOS 9 起就废弃了 scheme 弹窗,改用 Universal Link,但配置门槛高、排查困难
- Android:Intent 和 scheme 行为碎片化,Chrome、系统浏览器、第三方浏览器的表现各不相同
- 微信:内置浏览器对 scheme 几乎全面拦截,Universal Link 也不生效
- 兜底:唤起失败后,怎么判断?怎么引导用户?怎么跳转到应用商店?
这篇文章给出一个经过生产验证的组合方案:两套并行,互为主备。系统浏览器走 Scheme / Universal Link,微信内走 wx-open-launch-app + 右上角浏览器引导兜底。
一、核心思路
为什么是两套而不是一套?
微信内置浏览器对 scheme 几乎全面拦截。用 iframe 或 location.href 尝试唤起,成功率极低。微信官方提供的唯一可靠方案是 wx-open-launch-app 开放标签,但它需要公众号 + 开放平台移动应用 + JS-SDK 签名,门槛不低。
所以最优策略是:微信内优先用 wx-open-launch-app,失败后引导用户到系统浏览器;系统浏览器里用 Scheme / Universal Link 唤起,失败后跳应用商店。
二、方案一:Scheme / Universal Link(系统浏览器为主)
2.1 总览流程
2.2 iOS:Universal Link 细节
Universal Link 是 Apple 官方推荐的 H5 → App 唤起方式。它的工作原理是:浏览器访问一个 https 链接,系统自动识别该链接是否被某个已安装的 App 注册,如果匹配,直接打开 App 而不是继续加载网页。
iOS 必备配置:
| 配置项 | 说明 | 关键细节 |
|---|---|---|
| 服务器 | 部署 apple-app-site-association | JSON 文件,无后缀,HTTPS,根目录或 .well-known/ |
| Xcode | Associated Domains → applinks:你的域名 | 不需要 https:// 前缀 |
| 链接 | 使用 https:// 同一域名下的路径 | 路径需匹配 AASA 文件中的 paths |
AASA 文件示例:
{ "applinks": { "details": [{ "appID": "TEAM_ID.com.your.app", "paths": ["/deep/*", "/app/*"] }] }}常见坑:AASA 文件必须返回
Content-Type: application/json,不能带.json后缀。用https://域名/apple-app-site-association直接访问,应该返回 JSON 而不是 404。
2.3 Android:Intent / Scheme 细节
Intent 链接格式:
const intentUrl = 'intent://path#Intent;' + 'scheme=yourapp;' + 'package=com.xxx.app;' + 'S.browser_fallback_url=https://xxx.com/download;end';location.href = intentUrl;Intent 的优势是自带 browser_fallback_url,未安装时自动跳转到指定页面(通常是下载页)。但部分定制 ROM 浏览器对此支持不一致。
纯 Scheme 格式:
location.href = 'yourapp://path?id=123';Scheme 的缺点是:未安装时,部分浏览器会弹”找不到应用”错误框,体验较差。
2.4 成功/失败判断逻辑
这是整个方案的核心——怎么知道唤起成功了还是失败了?
核心原理: 当 App 被唤起时,浏览器页面会被切到后台,document.hidden 变为 true,同时触发 visibilitychange 或 pagehide 事件。如果在计时窗口内检测到页面隐藏,就判定为唤起成功。
代码实现:
function openApp() { var scheme = 'yourapp://page?id=123'; var universal = 'https://m.xxx.com/app/page?id=123'; var hidden = false;
document.addEventListener('visibilitychange', function () { if (document.hidden) hidden = true; });
if (isWeChat()) { // 微信内:尝试 iframe 唤起,2.5秒后判断 tryIframeScheme(scheme); setTimeout(function () { if (!hidden) showBrowserGuide(); }, 2500); return; }
// 系统浏览器 if (isIOS()) { location.href = universal; // iOS 优先 UL } else { location.href = scheme; // Android 用 scheme }
setTimeout(function () { if (!hidden) location.href = APP_STORE_URL; // 兜底跳商店 }, 2500);}2.5 微信 vs 系统浏览器 对照
| 环境 | 主路径 | 失败兜底 |
|---|---|---|
| 微信内 | iframe / scheme(多数被拦) | 右上角浏览器引导 → 切系统浏览器 |
| 系统浏览器 iOS | Universal Link | 应用商店 |
| 系统浏览器 Android | Intent / scheme | 应用商店 |
三、方案二:wx-open-launch-app + 浏览器引导兜底(微信内为主)
3.1 总览流程
3.2 前置条件
使用 wx-open-launch-app 需要满足以下所有条件,缺一不可:
| 序号 | 条件 | 说明 |
|---|---|---|
| 1 | 微信开放平台移动应用 | appid 填移动应用 AppID,不是公众号 AppID |
| 2 | 公众号 + JS 安全域名 | 后端出签名,前端 wx.config |
| 3 | 移动应用与公众号绑定 | 开放平台里完成绑定 |
| 4 | H5 部署 https | 本地 file:// 无效 |
3.3 时序图:从签名到唤起
3.4 失败兜底:右上角浏览器引导
什么时候显示兜底 UI?
| 时机 | 是否显示浏览器引导 |
|---|---|
wx.config 失败 | ✅ 立刻显示 |
开放标签 error 事件 | ✅ 立刻显示 |
| 开放标签未渲染 | ✅ 立刻显示 |
| 用户点「无法打开?」 | ✅ 手动显示 |
launch 成功 | ❌ 不显示 |
| 系统浏览器环境 | ❌ 用 scheme/UL,不用开放标签 |
兜底 UI 结构:
┌─────────────────────────────────────┐│ ↗ 箭头 ││ ││ 请在浏览器中打开 ││ ││ ① 点右上角 ⋯ ││ ② 选「在浏览器中打开」 ││ ③ 再点「打开 App」 ││ ││ [ 复制链接 ] [ 去下载 App ] ││ ││ 点击空白处关闭 │└─────────────────────────────────────┘3.5 代码实现
开放标签 HTML:
<wx-open-launch-app id="launch-app" appid="wx_移动应用appid" extinfo="goods_id=123"> <template> <style>.btn { display:block; width:100%; height:48px; background:#e02e24; color:#fff; border:none; border-radius:8px; }</style> <button class="btn">打开 App</button> </template></wx-open-launch-app>JS 配置与事件监听:
wx.config({ appId: 'wx_公众号appid', timestamp, nonceStr, signature, jsApiList: [], openTagList: ['wx-open-launch-app']});
document.getElementById('launch-app') .addEventListener('error', function () { showBrowserGuide(); });兜底蒙层:
<div id="browser-guide" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.88); z-index:9999; color:#fff; padding:24px;"> <div style="text-align:right; font-size:48px;">↗</div> <h2>请在浏览器中打开</h2> <p>① 点右上角 <strong>⋯</strong></p> <p>② 选择 <strong>在浏览器中打开</strong></p> <p>③ 再点击 <strong>打开 App</strong></p></div>四、生产环境推荐组合
五、两套方案对比
| 对比项 | Scheme / Universal Link | wx-open-launch-app + 兜底 |
|---|---|---|
| 适用环境 | 系统浏览器(主) | 微信内置浏览器(主) |
| 是否需要开放平台 | 否 | 是(移动应用 + 绑定公众号) |
| 是否需要公众号 | 否 | 是(JS-SDK 签名) |
| 微信内直接唤起 | ❌ 多数被拦截 | ✅ 用户点按钮可唤起 |
| 失败兜底 | 右上角浏览器引导 + 应用商店 | 同上 + 复制链接 |
| 实现复杂度 | 低 | 中高 |
六、检查清单
Scheme / Universal Link
- iOS:
apple-app-site-association已部署,无后缀,Content-Type 正确 - iOS:Xcode Associated Domains 已配置
- Android:Intent-filter / scheme 已在 AndroidManifest 注册
- H5:不跳转易白屏的外链(如部分短链域名)
- 监听
visibilitychange做失败判断 - 微信内:准备浏览器引导蒙层
wx-open-launch-app
- 开放平台移动应用已创建并审核通过
- 公众号已认证,JS 安全域名已配置
- 移动应用与公众号已在开放平台绑定
-
wx.config含openTagList: ['wx-open-launch-app'] - 开放标签
appid为移动应用 AppID(不是公众号) - 监听
error事件 →showBrowserGuide() - 系统浏览器备用 scheme / UL 流程已就绪
七、总结
- 核心难点:微信拦截 scheme、iOS UL 配置繁琐、Android 碎片化
- 方案一:Scheme / Universal Link,适合系统浏览器,实现简单
- 方案二:wx-open-launch-app,适合微信内,需要开放平台 + 公众号 + 签名
- 推荐组合:微信内优先 wx-open-launch-app,失败后引导到系统浏览器走 Scheme/UL
- 成功判断:
visibilitychange+ 2.5 秒超时,页面 hidden = 成功 - 兜底策略:右上角浏览器引导 → 系统浏览器 → 应用商店
H5 唤起 App 没有银弹。正确的做法是分层:微信内用微信的能力,浏览器用系统的能力,每一层都准备好失败的退路。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!