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;
<a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;S.browser_fallback_url=http%3A%2F%2Fzxing.org;end"> Take a QR code </a>

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

感谢作者
参考博客

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • H5唤起APP进行分享 最近很久没有写blog和note,倒是过家家的开发日志简单草草写了一点。这次记录下这个学习...
    二歪求知iSk2y阅读 27,826评论 3 25
  • 最近遇到一个需求:点击一个按钮,如果本机装有则唤起app,没有的话则跳下载页。 刚一接到需求,觉得很熟悉,实际上这...
    tobAlier阅读 14,118评论 3 2
  • 一般 H5 常见的引导页,为已安装 app 的用户唤起 app,未安装 app 的用户引导下载 app 一、简介 ...
    Johnson杰阅读 9,230评论 1 6
  • 最近有个需求,公司H5的页面在浏览器中打开的时候需要唤起自己的app,如果移动端没有安装本app就跳转到下载页面。...
    昨日d书生阅读 8,542评论 2 1
  • 移动互联网时代,“用户增长”成为每个公司关注的重点话题。为了将更多用户引导到客户端内,产品经理会习惯性地在网页的各...
    宇晓阅读 21,661评论 4 20