- Published on
从浏览器或者Webview 中唤醒APP
- Authors
- Name
- Will Liu
- @satlxq111
移动互联时代,很多互联网服务都会同时具备网站以及移动客户端,很多人认为 APP 的能帮助建立更稳固的用户关系,于是经常会接到各种从浏览器、webview 中唤醒 APP 的需求,显然,这对于前端开发人员来说,是一件很纠结的事。
唤醒 APP
目前常见的主动唤醒 APP 方式有几种:
Url scheme
Url scheme 是 iOS,Android 平台都支持,只需要原生 APP 开发时注册 scheme, 那么用户点击到此类链接时,会自动跳到 APP。比如
<!-- 打开考拉APP首页 --><a href="kaola://www.kaola.com">打开APP</a><!-- 呼叫号码 --><a href="tel://13788889999">打开拨号</a>如果配置 scheme 的路径,并在 app 中识别,则可以直接打开 APP 特定页面,如下:
<!-- 打开考拉APP商品详情 --><a href="kaola://www.kaola.com/product/8342.html">打开APP商品详情</a>上述的链接,需要考虑手机是否支持此 Scheme: 支持:弹出相应程序; 不支持:错误处理情况因平台而异,部分 app 会直接跳错误页(比如 Android Chrome/41,iOS 中老版的 Lofter); 也有的停留在原页面,但弹出提示“无法打开网页”(比如 iOS7);iOS8 以及最新的 Android Chrome/43 目前都是直接停留在当前页,不会跳出错误提示。 总体来看, iOS 的支持程度比 Android 好,iOS 在实际使用中,除非明确禁止的(比如微信),很少碰到不支持的情况;Android 平台则各个 app 厂商差异很大,比如 Chrome 从 25 及以后就不再支持通过 js 触发(非用户点击),设置 iframe src 地址等来触发 scheme 跳转。
Android intent
这是 Android 平台独有的,使用方式如下
intent: HOST/URI-path // Optional host #Intent; package=[string]; action=[string];category=[string]; component=[string]; scheme=[string]; end;这里的 HOST/URI-path, 与普通 http URL 的 host/path 书写方式相同, package 是 Android app 的包名,其它参数如 action、category、component 不是很理解, 具体见文档 , 比如打开考拉 app 的商品详情,代码如下
<!-- 打开考拉APP --><a href="intent://www.kaola.com/product/8342.html#Intent;scheme=kaola;package=com.kaola;end" >打开APP</a>如果手机能匹配到相应的 APP,则会直接打开;如没有安装,则会跳到手机默认的应用商店,比如 Google 原生系统 Nexus 5,将会直接跳到 Google Play, 对于国内各厂商定制过的系统,则跳转到各自的默认应用商店,或者弹出商店供选择。
intent 比 scheme 相对完善的一点是,提供一个打开失败去向 URL 的选项,可以通过指定参数 S.browser_fallback_url 来指定去向 URL。
比如如下的打开 APP 动作,如果打开失败,则跳转到 app 下载页,这对于国内的特殊网络环境,还是挺有用的。
<!-- 打开考拉APP --><a href="intent://www.kaola.com/product/8342.html#Intent;scheme=kaola;package=com.kaola;S.browser_fallback_url=http%3A%2F%2Fapp.kaola.com;end" >打开APP</a>HTTP URL 订阅
Android Chrome 平台独有,app 中订阅自有内容相关的 URL,在 Chrome 中浏览相关网页时,会自动弹出提示,让用户选择用浏览器还是 APP 打开,通用性有限。
iOS 内置 APP 广告条
在页面 Head 中增加 meta, 添加智能 App 广告条 (iOS 6+ Safari), 如下
<meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData,app-argument=myURL"可以自动判断是否已安装应用, 可惜只能用于 iOS+Safari, 在第三方应用中就不行了。
效果如下:

Android Chrome 内置 app 安装提示
这个是 Mobile Chrome 43 beta 新加入的特性,在用户浏览某一个网站多次后,如果 Chrome 发现该站点有原生 APP,则会提示用户下载原生 APP,此项特性开发者无法干预,完全是 Google 的推荐行为,在项目中用不上,具体见新闻报道
实际应用中存在的问题
移动平台提供这么多唤醒 APP 的方法,但是功能还不够完善,以下情况 JS 无法检测并做处理:
- APP 如果唤醒失败,很多时候都会跳到错误页,影响用户体验,而我们的需求很可能是需要跳到下载或者其它页。
- APP 成功唤醒,页面无法直接得知,系统没有提供此类回调。
实际需求、解决方案
- 要在打开 APP 失败时,不能使当前页面跳到错误页,且打开失败时,有失败函数回调。
- 如果成功打开 APP,有成功函数回调。 针对第一点,可以将打开动作放到 iframe 中,就算跳转失败仍能停留在当前页面;那剩下的问题就是如何检测 APP 是否成功打开; 网上常见解决方案如下:
//创建一个隐藏的iframevar ifr = document.createElement('iframe')ifr.src = 'com.baidu.tieba://'ifr.style.display = 'none'document.body.appendChild(ifr)//记录唤醒时间var openTime = +new Date()window.setTimeout(function () { document.body.removeChild(ifr) //如果setTimeout 回调超过2500ms,则弹出下载 if (+new Date() - openTime > 2500) { window.location = 'http://exam.com/xxxx.apk' }}, 2000)此脚本利用了程序切换到后台时,计时器回调会被推迟的原理,如果 APP 被唤醒,那么此网页必然进入后台,如果用户从 APP 再切换回来,时间一般也会超过 2.5s;如果 app 没有唤醒,则 setTimeout 基本上会准时回调,时间不会超过 2s。但是实际上,这个仅仅在 iOS 平台有效,Android 由于是多任务的,应用放到后台,setTimeout 基本上还是会准时触发,所以这个逻辑还不够完善。
那 Android 浏览器有没有方法检测应用是否进入了后台呢? 页面可见性 API(Page Visibility API), 可以通过检测 document.hidden 或 document.[webkit|moz|ms]Hidden 来检查页面是否可见,或者订阅页面的visibilitychange事件; 如果仅仅应用在新版 Android 及 Chrome 上,这个是很美好的,对于老版本(低于 4.4)及 Android Webview, 则不可用。
暮然回首,那人却在灯火阑珊处,setInterval,对,就setInterval, 如果设置比较小的运行间隔(小于 30ms),在浏览器或者 webview 中,应用切换到后台,setInterval会被很明显的延迟执行,比如设置一个运行间隔 20ms,总计运行 100 次的定时器,如果页面一直处于前台,则 100 次跑完,总耗时与 100x20=2000ms 不会有太大差异,但页面在后台运行时,此时间会明显超过 2000ms。可以利用这一点来实现是否成功打开 APP 检测及回调。
代码如下:
function openApp(openUrl, appUrl, action, callback) { //检查app是否打开 function checkOpen(cb) { var _clickTime = +new Date() function check(elsTime) { if (elsTime > 3000 || document.hidden || document.webkitHidden) { cb(1) } else { cb(0) } } //启动间隔20ms运行的定时器,并检测累计消耗时间是否超过3000ms,超过则结束 var _count = 0, intHandle intHandle = setInterval(function () { _count++ var elsTime = +new Date() - _clickTime if (_count >= 100 || elsTime > 3000) { clearInterval(intHandle) check(elsTime) } }, 20) }
//在iframe 中打开APP var ifr = document.createElement('iframe') ifr.src = openUrl ifr.style.display = 'none' if (callback) { checkOpen(function (opened) { callback && callback(opened) }) }
document.body.appendChild(ifr) setTimeout(function () { document.body.removeChild(ifr) }, 2000)}iframe 方式打开 APP 的问题:
- Android Chrome/25+ 无法打开 APP,所以最好是 APP 配合监听 http URL 来实现。
- iOS、Android 平台,近期发现在没有安装对应 app 时尝试唤醒,有少数 APP 会连当前页面(iframe 的父页面)也变成错误页的情况。
其它问题:
微信无法打开或者下载,打开 APP 这个基本无解,下载则只能让应用进驻应用宝市场,然后检测到在微信中运行时,跳转到应用宝页面下载。
2015-11-01 补充
随着 iOS9, Android M 的推出, 原先的 Scheme URL 唤醒 app 的方式成功率以及很低,并且苹果还加入了确认机制,使得通过 javascript 来自动唤醒 APP 的方式基本不可用。 这两大平台都推出了自己的 WEB 与 APP 连接的方式: Universal Links, App Link, 这部分需要 App 的开发人员来做了。