H5唤起App

最近总接到落地页的需求,落地页的职责主要是引流,有以下几种类型
1、引导已经下载App的用户打开App
2、引导未下载App的用户下载App
3、引导未注册的用户注册
4、引导已经注册的用户进入我们的主页或者其他的操作
从数据上可以体现在用户停留在App的时间多了,或者增加了用户量

唤起App主要的媒介是什么呢?

URL Scheme

URL Scheme的组成

[scheme:][//authority][path][?query][#fragment]
App 微信 支付宝 淘宝 微博
URL Scheme weixin:// alipay:// taobao:// sinaweibo://
     行为(应用的某个功能)    
            |
scheme://[path][?query]
   |               |
应用标识       功能需要的参数

URL Scheme 在 ios 9+ 上诸如 safari、UC、QQ浏览器中, iframe 均无法成功唤起 APP,只能通过 window.location 才能成功唤端。

Intent

安卓的原生谷歌浏览器自从 chrome25 版本开始对于唤端功能做了一些变化,URL Scheme 无法再启动Android应用。 例如,通过 iframe 指向 weixin://,即使用户安装了微信也无法打开。所以,APP需要实现谷歌官方提供的 intent: 语法,或者实现让用户通过自定义手势来打开APP。
安卓版本 4.4.4 以上机型的安卓自带浏览器、chrome 浏览器,需要通过 intent 跳转
intents文档

  • Intent 语法
intent:
   HOST/URI-path // Optional host 
   #Intent; 
      package=[string]; 
      action=[string]; 
      category=[string]; 
      component=[string]; 
      scheme=[string]; 
   end;

如果用户未安装 APP,则会跳转到系统默认商店。当然,如果你想要指定一个唤起失败的跳转地址,添加下面的字符串在 end; 前就可以了:

S.browser_fallback_url=[encoded_full_url]
  • 示例
    下面是打开 Zxing 二维码扫描 APP 的 intent。
intent:
   //scan/
   #Intent; 
      package=com.google.zxing.client.android; 
      scheme=zxing; 
   end;
 Take a QR code 

Universal Link

为什么要使用 Universal Link

传统的 Scheme 链接有以下几个痛点:

  • 在 ios 上会有确认弹窗提示用户是否打开,对于用户来说唤端,多出了一步操作。若用户未安装 APP ,也会有一个提示窗,告知我们 “打不开该网页,因为网址无效”
  • 传统 Scheme 跳转无法得知唤端是否成功,Universal Link 唤端失败可以直接打开此链接对应的页面
  • Scheme 在微信、微博、QQ浏览器、手百中都已经被禁止使用,使用 Universal Link 可以避开它们的屏蔽( 截止到 18年8月21日,微信和QQ浏览器已经禁止了 Universal Link,其他主流APP未发现有禁止 )

如何让 APP 支持 Universal Link

有大量的文章会详细的告诉我们如何配置,你也可以去看官方文档,我这里简单的写一个12345。

  1. 拥有一个支持 https 的域名
  2. 在 开发者中心 ,Identifiers 下 AppIDs 找到自己的 App ID,编辑打开 Associated Domains 服务。
  3. 打开工程配置中的 Associated Domains ,在其中的 Domains 中填入你想支持的域名,必须以 applinks: 为前缀
  4. 配置 apple-app-site-association 文件,文件名必须为 apple-app-site-association不带任何后缀
  5. 上传该文件到你的 HTTPS 服务器的 根目录 或者 .well-known 目录下

Universal Link 配置中的坑

这里放一下我们在配置过程中遇到的坑,当然首先你在配置过程中必须得严格按照上面的要求去做,尤其是加粗的地方。

  • 域名问题

Universal Link 支持的域名最多只能支持到二级域名,如果你用到了三级域名,Universal Link 唤端是不会生效的。

  • 跨域问题

IOS 9.2 以后,必须要触发跨域才能支持 Universal Link 唤端。

IOS 那边有这样一个判断,如果你要打开的 Universal Link 和 当前页面是同一域名,ios 尊重用户最可能的意图,直接打开链接所对应的页面。如果不在同一域名下,则在你的 APP 中打开链接,也就是执行具体的唤端操作。

  • Universal Link 是空页面

Universal Link 本质上是个空页面,如果未安装 APP,Universal Link 被当做普通的页面链接,自然会跳到 404 页面,所以我们需要将它绑定到我们的中转页或者下载页。

唤端方式

  • Android中,不同浏览器对唤起APP有严重的兼容性问题,主要处理方案有以下几种:
    1、window.location.href
    2、通过创建 iframe 并为其 src 赋值(主要)
    3、通过 intent
    4、通过制造不可见 a 链接,并触发点击

  • ios中,不同浏览器对唤起APP有严重的兼容性问题,主要处理方案有以下几种:
    1、系统版本在 8 以下时,可以监听页面的 pagehide / visibilitychange 事件。
    2、 window.location.href (主要)
    系统版本大于 8 以后可以 URL scheme 进行跳转。 IOS9 可以使用 universal Link

这里我们结合了两种来处理。

判断唤端是否成功

在浏览器实际上是没有能力判断手机里是否安装了某个App的,所以只能够采取一种投机取巧的方式。APP 如果被唤起的话,页面就会进入后台运行。setInterval 在 ios 中不会停止运行,在 android 中停止运行。

  • ios 通过 document.hidden 和 document.webkitHidden 属性来判断 APP 在 ios 中是否被正常唤起,2000ms 内,页面转入后台运行,document.hidden 会返回 true,代表唤端成功,反之则代表失败。
  • Android 我们的判断条件比预期时间多设置了 500ms,所以如果安卓中 setInterval 内的函数执行 100 次以内所费时间超过 2500ms,则说明 APP 唤起成功,反之则代表失败。
    在JavaScript中判断页面是否进入后台来判断打开成功。Html5提供了下列事件和属性可以利用:
    pagehide : 页面隐藏时触发
    visibilitychange : 页面隐藏没有在当前显示时触发(切换tab也会触发该事件)
    document.hidden : 当页面隐藏时,该值为true,显示时为false
const initialTime = new Date();
let counter = 0;
let waitTime = 0;
const checkOpen = setInterval(() => {
   count++;
   waitTime = new Date() - initialTime;
   if (waitTime > 2500) {
      clearInterval(checkOpen);
      cb();
   }
   if (counter < 100) return;
   const hide = document.hidden || document.webkitHidden;
   if (!hide) {
      cb(); // 唤端失败的回调函数
   }
}, 20);

如果唤端失败(APP 未安装),我们总是要做一些处理的,可以是跳转下载页,可以是 ios 下跳转 App Store… 但是Js 并不能提供给我们获取 APP 唤起状态的能力,Android Intent 以及 Universal Link 倒是不用担心,它们俩的自身机制允许它们唤端失败后直接导航至相应的页面,但是 URL Scheme 并不具备这样的能力,所以我们只能通过一些很 hack 的方式来实现 APP 唤起检测功能。

代码

const browser = {
    getBrowser: function() {
        var u = navigator.userAgent,
            app = navigator.appVersion;
        return {
            trident: u.indexOf('Trident') > -1, //IE内核
            presto: u.indexOf('Presto') > -1, //opera内核
            webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
            gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
            opera: u.indexOf('Opera') > -1,
            chrome: u.indexOf('Chrome') > -1,
            firefox: u.indexOf('Firefox') > -1,
            safari: u.indexOf('Safari') > -1, //注意chrome浏览器此项也为true,非chrome且此项为true则可确定为Safari
            ie: u.indexOf('compatible') > -1 && u.indexOf('MSIE') > -1 && u.indexOf('Gecko') == -1,
            mobile: u.search(/AppleWebKit.*Mobile/) > -1, //是否为移动终端
            ios: u.search(/\(i[^;]+;( U;)? CPU.+Mac OS X/) > -1, //ios终端
            android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android终端
            winPhone: u.search(/Windows Phone/) > -1, //windows phone终端
            iPhone: u.indexOf('iPhone') > -1, //是否为iPhon
            iPad: u.indexOf('iPad') > -1, //是否iPad
            webApp: u.indexOf('Safari') == -1, //是否Safari web应该程序,没有头部与底部
            weibo: u.search(/WeiBo/i) > -1, //是否微博
            weixin: u.search(/MicroMessenger/i) > -1, //是否微信
            qq: u.search(/\sQQ/i) > -1, //是否QQ
            mQQ: u.search(/MQQBrowser/) > -1, //是否QQ手机浏览器
            uc: u.search(/UCBrowser/) > -1 //是否UC浏览器
        };
    }(),
  getIOSVersion:  function getIOSVersion() {
            const verion = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
            return parseInt(verion[1], 10);
}()
}
/**
 * [evokeApp 唤起APP]
 * @param  {[Object]} config  [跳转的URL]
 * config.ios.url {[String]} [iOS跳转的URL]
 * config.ios.data {[String]} [iOS URL参数]
 * config.ios.callback {[function]} //android的回掉如果没有下载app就执行操作
 * config.android.url {[String]} [android跳转的URL]
 * config.android.data {[String]} [android URL参数]
 * config.android.callback {[function]} //android的回掉如果没有下载app就执行操作
 */
const evokeApp = (config) =>{
//对微信,微博,qq做处理弹窗
if (browser.versions.weibo || browser.versions.weixin || browser.versions.qq) {
        return;
    }
//
let browserVersions = browser.versions;
let evokeAppURL = '';
let cb;
    if (browserVersions.ios) {
        evokeAppURL = config.ios.data ? 'authority://' + config.ios.url + '?' + config.ios.dataArr.join('&') : 'authority://' + config.ios.url;
cb = config.ios.callback;
    } else if (browserVersions.android) {
      evokeAppURL = config.android.data ? 'authority://' + config.android.url + '?' + config.android.dataArr.join('&') : 'authority://' + config.android.url;
cb = config.android.callback;
    };
const initialTime = new Date();
let counter = 0;
let waitTime = 0;
const checkOpen = setInterval(() => {
   count++;
   waitTime = new Date() - initialTime;
   if (waitTime > 2500) {
      clearInterval(checkOpen);
      cb && cb();
   }
   if (counter < 100) return;
   const hide = document.hidden || document.webkitHidden;
   if (!hide) {
      cb && cb(); // 唤端失败的回调函数
   }
}, 20);
}

注意

1、h5在微信中无法唤醒App,需要“用浏览器打开”
微信对所有的分享连接做了scheme屏蔽,也就是说分享连接中所有对于scheme的调用都被微信封掉了。

  1. 在询问是否打开APP的时候,如果选择了“取消”,则再唤起APP的时候会不起作用。目前并没有什么解决方案,在chrome Android,UC Android上会复现问题。需再次刷新页面才行。

  2. 在ios手机中,用location.href唤起app,本地如果没装app,会弹窗提示safari浏览器打不开该网页,网址无效,后面在用location.href来下载安装包的话也会显示同样的错误,即使你的下载链接是有效的。解决办法:IOS9及以上使用 Universal Links

感谢作者
参考博客

你可能感兴趣的:(H5唤起App)