h5唤醒app
在App的业务场景中,会出现这样的需求:点击网页广告,如果用户手机安装了该App,那它就会直接打开用户安装的App;如果没安装该App,则会跳转到App下载页面。
这里主要有两种实现方式:
- url scheme
- universal links (apple)
url scheme
使用特殊格式([scheme]://[host][:port]/[path]?[query])的URL打开app应用。
实现方式
iframe
每20ms执行一次,执行100次在页面中实际耗费与2000ms不会相差多少。判断条件比预期时间多设置了200ms,所以如果中setInterval内的函数执行100次以内所费时间超过2200ms,则说明APP唤起成功, 打开APP,反之则代表失败,跳转到下载页面。
var $iframe = document.createElement('iframe');
var openTime = +new Date();
var limitNum = 100;
$iframe.src = 'url scheme';
$iframe.style.display = 'none';
document.body.appendChild($iframe); // 用户安装app, 会打开app
var timer = setInterval(function () {
if (limitNum > 0) {
limitNum--;
} else {
if ((new Date()) - openTime < 2200) {//加了200ms基准误差
document.body.removeChild($iframe);
window.location.href = 'your download page'; // 进入下载页面
}
clearInterval(timer);
}
}, 20);
a链接方式
<a href='<scheme域名>://<path>?<params>=<value>'>打开APP</a>
location方式跳转
window.location.href = '<scheme域名>://<path>?<params>=<value>'
在Android / Ios中Url Scheme的实现方式
Android
chrome for Android无法通过iframe方式来调用scheme(安卓会在scheme前自动加上http://),通过a链接的方式可以成功调用。针对chrome内核的浏览器如360浏览器,对于iframe和a链接的方式都能支持,所以对chrome内核的浏览器采用a链接的方式来调用scheme。
对于其他浏览器,如UC,QQ浏览器则采用iframe方式调用scheme。
iOS
ios 9.0之前,可以通过iframe方式调用scheme。
ios 9.0之后,iframe方案变得不可用,在打开URL scheme时,会弹出对话框,询问是否用xx应用来打开。若未安装APP则会显示“safari打不开该网页,因为网址无效”。可以通过window.location.href的方式调用scheme。
其它
微信白名单机制会导致分享到微信的页面无法正常打开原生App。
注意
url scheme为应用程序提供了潜在的攻击载体, 因此需要确保所有的url参数格式正确,将操作限制为不冒数据风险的操作。例如, 不允许直接删除内容,访问与用户有关的敏感信息。
官方建议使用universal links。
universal links (apple)
universal links是标准的http或https链接, 可以同时用来唤醒app或网页呈现。
当用户未安装app时,系统会把它当做普通的http或https链接,在浏览器中打开这个链接显示网页内容。
当用户安装app时,系统会通过一个存储在我们服务器的文件(apple-app-site-association.json)来判断网站是否允许app打开这个链接。
universal link 生效条件
- 当在同一域名下打开universal links,系统会认为用户想继续在浏览器中浏览网页;当域名不同时, 系统会进行打开app的操作。
- universal links支持的域名最多只能支持到二级域名,如果你用到了三级域名,universal links 唤端不会生效。
- iPhone 至少 iOS 9.2 以上。至少 Xcode 7 以上。
apple-app-site-association.json
提供支持universal links 的列表。从macOS 11 和IOS 14开始, apple-app-site-association.json 的请求不再通过服务器请求,而是通过CDN分发完成。如果服务器不能在公网下访问,需要通过另外的方式绕过CDN直接连接私有域。参考链接
如下格式:
{
"applinks": {
"details": [
{
"appIDs": [ "ABCDE12345.com.example.app", "ABCDE12345.com.example.app2" ], // 一套配置适配多个 appID
"components": [
{
"#": "no_universal_links", // # 配置支持的锚点条件
"exclude": true,
"comment": "Matches any URL whose fragment equals no_universal_links and instructs the system not to open it as a universal link"
},
{
"/": "/buy/*", // / 来配置支持的 path 格式条件
"comment": "Matches any URL whose path starts with /buy/"
},
{
"/": "/help/website/*",
"exclude": true, // exclude 是排除字段,符合这个条件的 Universal Link 不生效
"comment": "Matches any URL whose path starts with /help/website/ and instructs the system not to open it as a universal link"
},
{
"/": "/help/*",
"?": { "articleNumber": "????" }, // ? 来配置支持的字段条件
"comment": "Matches any URL whose path starts with /help/ and which has a query item with name 'articleNumber' and a value of exactly 4 characters"
}
]
}
]
},
"webcredentials": {
"apps": [ "ABCDE12345.com.example.app" ]
},
"appclips": {
"apps": ["ABCED12345.com.example.MyApp.Clip"]
}
}
最终解决方案
var ue = {};
// 客户端检测
ue.browserInfo = {
userAgent: navigator.userAgent.toLowerCase(),
isAndroid: Boolean(navigator.userAgent.match(/android/ig)),
isIos: Boolean(navigator.userAgent.match(/iphone|ipod|ipad/ig)),
}
// 唤醒APP
ue.openApp = function (urls) {
var scheme = urls.scheme;
var universalLink = urls.universalLink;
var downloadUrl = urls.downloadUrl;
if (ue.browserInfo.isIos) {
// ios 9以上universal link
if (ue.getIosVersion() >= 9) {
window.location.href = universalLink;
} else {
// ios 9以下url scheme --iframe
ue.iframeScheme(scheme, downloadUrl);
}
} else if (ue.browserInfo.isAndroid) {
// 安卓
window.location.href = scheme;
} else {
// 其它设备
ue.iframeScheme(scheme, downloadUrl);
}
};
// 获取ios地址
ue.getIosVersion = function () {
var iosVersion = ue.browserInfo.userAgent.match(/os\s*(\d+)/);
return iosVersion ? (iosVersion[1] || 0) : 0;
}
// iframe 方式唤醒app
ue.iframeScheme = function (scheme, downloadUrl) {
var $iframe = document.createElement('iframe');
var openTime = +new Date();
var limitNum = 100;
$iframe.src = scheme;
$iframe.style.display = 'none';
document.body.appendChild($iframe); // 用户安装app, 会唤醒app
var timer = setInterval(function () {
if (limitNum > 0) {
limitNum--;
} else {
if ((new Date()) - openTime < 2200) {//加了200ms基准误差
document.body.removeChild($iframe);
window.location.href = downloadUrl;
}
clearInterval(timer);
}
}, 20);
}
ue.init = function (urls) {
ue.openApp(urls);
};