乐于分享
好东西不私藏

H5跳转APP的3种方法,看看你掌握了几种?

H5跳转APP的3种方法,看看你掌握了几种?

按钮点了,页面没反应。

产品说“是不是前端没写跳转”。我一般不急着背锅,先看当前页面在哪个容器里:Safari、Chrome、微信、支付宝、App 内 WebView,结果可能完全不一样。

H5 拉起 App 这事,看着就是一行 location.href,真上线后经常变成玄学。

最常见的第一种,是 URL Scheme。

比如你们 App 约定了一个协议:

myshop://order/detail?id=98765

H5 里直接跳:

functionopenByScheme(orderId{const scheme = `myshop://order/detail?id=${encodeURIComponent(orderId)}`;window.location.href = scheme;}

这东西优点很明显,简单,老系统也能接。

但我不太信这种裸跳。因为用户没装 App 的时候,浏览器不会老老实实告诉你“没装”,有的没反应,有的弹提示,有的直接报一嘴没用的错误。

所以线上一般要加一个兜底,跳失败就去下载页。

functionopenAppByScheme({ orderId, downloadUrl }{const startAt = Date.now();const scheme = `myshop://order/detail?id=${encodeURIComponent(orderId)}`;let hidden = false;const markHidden = () => {    hidden = true;  };document.addEventListener('visibilitychange', () => {if (document.hidden) markHidden();  }, { oncetrue });window.location.href = scheme;  setTimeout(() => {const cost = Date.now() - startAt;// 页面切后台了,大概率已经拉起 Appif (hidden || cost > 1800return;window.location.href = downloadUrl;  }, 1200);}

这里有个细节,别用“定时器到了就跳下载页”这种写法。

有些手机拉起 App 慢一点,H5 还没来得及切后台,你下载页已经怼上去了。用户体验就是:App 被拉起来了,回来发现页面也跳没了。

第二种,是 Universal Link / App Link。

iOS 叫 Universal Link,Android 叫 App Link。它不是自定义协议,而是正常 HTTPS 链接。

比如:

https://m.example.com/app/order/98765

用户装了 App,并且系统校验通过,就直接打开 App。没装 App,就当普通网页打开。

H5 代码反而更普通:

functionopenByUniversalLink(orderId{const url = `https://m.example.com/app/order/${encodeURIComponent(orderId)}`;window.location.href = url;}

这方式我更愿意推荐给新项目。

因为它不像 Scheme 那么“野”。链接本身能访问,能被分享,能被搜索引擎识别,没装 App 也能落到 H5 页面。

但它麻烦在配置,不在 JS。

iOS 要配 apple-app-site-association,Android 要配 assetlinks.json。证书、域名、路径、App 包名、签名,任何一个对不上,就只会打开网页。

前端排这种问题,别只盯代码。先拿手机直接访问这个文件,看是不是能拿到正确 JSON。再让客户端确认 App 里关联的路径有没有覆盖。

我见过不少问题,最后不是前端错,是服务端把 JSON 返回成了 text/html,或者 CDN 给加了跳转。

第三种,是 Android 的 intent 跳转。

这个主要用在 Android Chrome 里,格式长这样:

functionopenByIntent(orderId{const fallback = encodeURIComponent('https://m.example.com/download');const intentUrl =`intent://order/detail?id=${encodeURIComponent(orderId)}` +`#Intent;scheme=myshop;package=com.example.myshop;` +`S.browser_fallback_url=${fallback};end`;window.location.href = intentUrl;}

这个写法看着丑,但在 Android Chrome 里挺好用。

它能指定包名,还能带 fallback 地址。没装 App 时,浏览器知道该去哪儿,不用你自己猜。

不过这玩意别拿去 iOS 用,也别指望所有国产浏览器都完全按规矩来。有些浏览器会拦,有些 WebView 直接吞掉。

所以我线上一般会包一层判断,不让调用散落在业务代码里。

functionopenNativePage({ orderId }{const ua = navigator.userAgent.toLowerCase();const ios = /iphone|ipad|ipod/.test(ua);const android = /android/.test(ua);const inWechat = /micromessenger/.test(ua);if (inWechat) {// 微信里很多拉起会被拦,直接给引导页更省事window.location.href = `/open-guide?orderId=${encodeURIComponent(orderId)}`;return;  }if (ios) {window.location.href = `https://m.example.com/app/order/${encodeURIComponent(orderId)}`;return;  }if (android) {    openByIntent(orderId);return;  }  openAppByScheme({    orderId,downloadUrl'https://m.example.com/download'  });}

真正做 H5 跳 App,难点不在“怎么跳”,而在“失败后别傻等”。

Scheme 适合老项目,快,但兜底要自己处理。

Universal Link / App Link 更正规,适合长期维护,但配置链路长,出问题别只查前端。

Intent 适合 Android Chrome 场景,能把包名和 fallback 写清楚,但兼容性别想得太美。

我一般会把这三种都收口到一个 openNativePage 里。业务侧只传页面参数,不关心设备、不关心浏览器、不关心下载页。

否则后面改一次拉起策略,全项目搜 myshop://,那场面不会太好看。