最近总接到落地页的需求,落地页的职责主要是引流,有以下几种类型
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。
- 拥有一个支持 https 的域名
- 在 开发者中心 ,Identifiers 下 AppIDs 找到自己的 App ID,编辑打开 Associated Domains 服务。
- 打开工程配置中的 Associated Domains ,在其中的 Domains 中填入你想支持的域名,必须以
applinks:
为前缀 - 配置
apple-app-site-association
文件,文件名必须为apple-app-site-association
,不带任何后缀 - 上传该文件到你的 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的调用都被微信封掉了。
在询问是否打开APP的时候,如果选择了“取消”,则再唤起APP的时候会不起作用。目前并没有什么解决方案,在chrome Android,UC Android上会复现问题。需再次刷新页面才行。
在ios手机中,用location.href唤起app,本地如果没装app,会弹窗提示
safari浏览器打不开该网页,网址无效
,后面在用location.href来下载安装包的话也会显示同样的错误,即使你的下载链接是有效的。解决办法:IOS9及以上使用 Universal Links。
感谢作者
参考博客