H5唤醒APP

0

H5 唤醒APP功能

最近遇到一个需求,需要在从APP分享出去的H5页面中,带有一个立即打开的按钮,如果本地安装了app,那么就直接唤起本地的app,如果没有安装,则跳转到下载。这是一个很正常的推广和导流量的策略。前端小白从来没有做过这个需求,只能开始哼唧哼唧地开启自己的度娘和谷歌之旅。

经过一段时间的探索之旅发现里面的学问很多,要做一个兼容性很好的方案,就需要考虑各种情况,在不同的情况适配不同的方案,比方说用户是在手机浏览器打开还是微信中打开,或者是在pc中打开,universal腾讯应用宝直接打开 APP link是否被关闭等,这就使代码实现变得复杂,且容易出错,且还有安卓平台机型众多、浏览器众多等导致的兼容问题。由于时间有限,这次主要先介绍一个比较普遍的使用URL Scheme进行App跳转的方法。

URL Scheme —— 唤端媒介

一般来说,我们使用的智能设备上有许多我们的个人信息。比如:联系方式、银行卡/信用卡信息、支付宝/Paypal/各大商城的账户密码、照片甚至行程与位置信息等。

如果说,你设备上的每一个应用,不管是官方的还是你从任何商城安装的应用都可以随意地获取这些信息,那么你轻则收到骚扰信息和邮件、重则后果不堪设想。如何让这些信息不被其它应用随意使用,或者说,如何让这些信息仅在设备所有者本人知情并允许的情况下被使用,是所有智能设备与操作系统所要在乎的核心安全问题。针对这个问题,苹果使用了名为「沙盒」的机制:应用只能访问它声明可能访问的资源。一切提交到 App Store 的应用都必须遵守这个机制。

在安全方面沙盒是个很好的解决办法,但是有些矫枉过正。敏感的个人信息我们不愿意透露,却不代表所有的信息我们都不想与其它应用共享。因此,我们急需要一个辅助工具来帮助我们实现应用通信, URL Schemes 就是这个工具。

URL Schemes是什么

[scheme]://[host]/[path]?[query]

我们拿 https://www.baidu.com 来举例,scheme 自然就是 https 了,后面拼接的是传递的参数。URL Schemes 没有特别严格的规范,所以后面参数的具体定义是app开发者去自定义。

就像给服务器资源分配一个 URL,以便我们去访问它一样,我们同样也可以给手机APP分配一个特殊格式的 URL,用来访问这个APP或者这个APP中的某个功能(来实现通信)。APP得有一个标识,好让我们可以定位到它,它就是 URL 的 Scheme 部分。

但是,两者还有几个重要的区别:

  1. 所有网页都一定有网址,不管是首页还是子页。但未必所有的应用都有自己的 URL Schemes,更不是每个应用的每个功能都有相应的 URL Schemes。几乎没有所有功能都有对应 URL 的应用。一个 App 是否支持 URL Schemes 要看那个 App 的作者是否在自己的作品里添加了 URL Schemes 相关的代码。
  2. 一个网址只对应一个网页,但并非每个 URL Schemes 都只对应一款应用。这点是因为苹果没有对 URL Schemes 有不允许重复的硬性要求,所以曾经出现过有 App 使用支付宝的 URL Schemes 拦截支付帐号和密码的事件。
  3. 一般网页的 URL 比较好预测,而URL Scheme 因为没有统一标准,所以非常难猜,通过猜来获取 应用的 URL Schemes 是不现实的。

前面普及了一下URL Schemes的相关知识,作为个前端开发者,就不去深究其中的原理,都交给app开发者吧。接下来开始我们的正题。首先当然是要客户端提供App的Url Schemes。

用浏览器去打开scheme

在浏览器中打开 scheme 就像打开一个不同的http地址一样。可以在一个 a 标签中打开。

<a href="app://" id="open">打开应用</a>

点击上面的H5页面中的链接将会尝试唤醒对应app,在一些浏览器中,可能会弹出一个提示框,询问用户是否允许打开应用。
如果打开的 scheme 在本地没有对应的 app,则点击不会反应。
当然还可以使用 JavaScript 代码打开,只需要添加相应的事件触发和处理即可。

在JavaScript代码中打开连接有以下几种方式:

  1. 新建一个隐藏的 iframe ,地址指向需要打开的url
  2. 使用 window.location 或者 window.location.href 刷新当前页面
  3. 新建一个隐藏的 a 标签,地址指向打开的url,并触发打开链接事件
  4. 动态创建一个script脚本,在这个脚本中新建一个a标签并打开
// 打开url方式
var urlOpen = {
  // ios体验差
  iframe : url => {
    var iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = url;
    document.body.appendChild(iframe);
  },
  location: url => {
    window.location.href = url;
  },
  href: url => {
    var a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    document.body.appendChild(a);
    a.click();
  },
  script: url => {
    var script = document.createElement('script');
    script.setAttribute('type','test/javascript');
    script.innerHtml = `(function(){
      var a = document.createElement("a");
      a.style.display = "none";
      'a.href = " + `url.replace(/"/g,'\\"')` + ";
      document.body.appendChild(a);
      a.click();
      })()`;
    document.body.appendChild(script);
  },
  open: url => {
    window.open(url);
  }

}

以上方法是只是解决了在已安装App设备唤醒App的功能,并不能判断是否已安装App,没有安装即跳转至下载链接。

浏览器判断是否安装应用

在JavaScript中判断页面是否进入后台来判断打开成功。Html5提供了下列事件和属性可以利用:

  1. pagehide : 页面隐藏时触发
  2. visibilitychange : 页面隐藏没有在当前显示时触发(切换tab也会触发该事件)
  3. document.hidden : 当页面隐藏时,该值为true,显示时为false
var downloader,
    scheme = 'app://',
    // 需要打开的app scheme 地址
    iosDownload = 'http://xxx.com',
    // 如果打开scheme失效的app下载地址
    andDownload = 'http://xxx.com';   
var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1;//android
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);//ios终端

// 给id为openApp的按钮添加点击事件
document.getElementById('openApp').onclick = function (){
  window.location.href = scheme; //尝试打开 scheme

  // 设置定时任务,3秒后打开下载链接
  downloader = window.setTimeOut(function(){
    if(isAndroid){
      window.location.href = andDownload;
    }
    if(isIOS){
      window.location.href = iosDownload;
    }
  },3000);

  // 如果当前页面隐藏,推断打开 scheme成功,清除定时任务
  document.addEventListener('visibilitychange webkitvisibitychange',function(){
    if(document.hidden || document.webkitHidden){
      clearTimeout(downloader);
    }
  });

  window.addEventListener('pagehide',function(){
    clearTimeout(downloader');
  })

}

注意

微信中无法唤醒App,需要“用浏览器打开”是因为微信对所有的分享链接接做了scheme屏蔽,也就是说分享连接中所有对于scheme的调用都被微信封掉了。有些app是能在微信打开是因为微信有一个白名单。