一、引言
有这么一个需求,点击h5页面上的一个按钮,需要判断本机有没有安装某app,若已经安装过,则直接调起此app,若没有安装,则跳转该app的下载页。要求安卓手机跳转此app的下载包地址。ios跳转此app的APP Store。
二、唤起方法
URL Schemes
[scheme:][//authority][path][?query][#fragment] 比如:app://weixin?test=1
行为(应用的某个功能)
|
scheme://[path][?query]
| |
应用标识 功能需要的参数
URL Schemes:好比给手机APP分配一个特殊格式的 URL,用来访问这个APP或者这个APP中的某个功能(来实现通信)。APP得有一个标识,好让我们可以定位到它,它就是 URL 的 Scheme 部分。
*注意:应用是否支持
URL Schemes
要看App开发者有没有写那部分的代码了
Intent
安卓的原生谷歌浏览器从
chrome25
版本之后就不能通过URL Schemes
唤醒安卓应用。要使用谷歌官方提供的intent:
预发, 如果唤醒失败,则会跳转到谷歌的应用市场。语法与URL Schemes
及其相似,相当于谷歌定制版的URL Schemes
,也没用过,就不多说。
IOS Universal Link
-
简介
Universal Link
是在iOS9引入的新功能,通过传统的HTTP链接就可以唤醒app,如果用户没有安装APP,则会跳转到该链接对应的页面,而且在唤醒app的时候没有弹框提示哦。可以说是解决了URL Schemes
的大部分问题。 -
原理及流程
-
App开发人员去配置中心配置
Associated Domain
配置一个支持https
的域名,比如app-support.test.com
- 然后app-support.test.com/apple-app-site-association
或者app-support.test.com/apple-app-site-association/.well-known/apple-app-site-association
要返回app的teamId,bundleId,paths
信息.router.get('/apple-app-site-association, (req, res) => { const data = { applinks: { apps: [], details: [ { appID: 'teamId.bundleId', paths: ['*'] } ] } }; res.set('Content-Type', 'text/html'); res.send(JSON.stringify(data)); });
然后APP安装后首次打开,如果
Associated Domain
配置了的话,就会去请求app-support.test.com/apple-app-site-association
。系统会根据返回的teamId,bundleId,paths
知道当打开app-support.test.com
下的哪些路径的时候唤醒对应的app,比如paths=*
的话,就是打开app-support.test.com
下的任意路径都会唤醒appapp那边会收到对应的路径,然后要根据
path
写逻辑跳转到对应的功能
-
-
如何验证配置成功
在备忘录中输入配置好的链接,直接点开这个链接(
https://app-support.test.com
),配置好的话会直接跳到app, 或者长按,弹出菜单中会提示在xxx中打开
- 在safari中。
三、常见唤醒媒介
- iframe
ifr = document.createElement("iframe");
ifr.setAttribute("src", "wrjk://com.eko123"); /***打开app的协议,有an同事提供***/
ifr.style.display = "none";
document.body.appendChild(ifr);
iframe方案的唤起原理是: 程序切换到后台时,计时器会被推迟(计时器不准的又一种情况)。如果app被唤醒那么网页必然就进入了后台,如果用户从app切回来,那么时间一般会超过2s;若app没有被唤起,那么网页不会进入后台,setTimeout基本准时触发,那么时间不会超过2s。
在未安装 app 的情况下,不会去跳转错误页面。但在各个系统及应用中兼容性比较多。例如:ios9+ 禁止掉了iframe方式。
-
a 标签
a = document.createElement("a"); a.setAttribute("href", "wrjk://com.eko123"); /***打开app的协议,有an同事提供***/ a.style.display = "none"; document.body.appendChild(a);
a标签如果目标scheme错误,即应用不存在也不会报错
-
window.location跳转
window.location.href = "wrjk://com.eko123";
兼容性:URL Scheme 在 ios 9+ 上诸如 safari、UC、QQ浏览器中, iframe 均无法成功唤起 APP,只能通过 window.location 才能成功唤端。(本人没测试过,摘自别人的帖子)
三种唤醒媒介对比
某篇博文中对三种唤起方式进行了测试
。X表示唤起失败,√表示唤起成功
。红色标记表示进入页面直接唤起,绿色表示人工事件操作后唤起
。ios测试机:iphone 6p;android测试机:小米1s
-
iframe唤起app测试结果
-
a标签唤起app测试结果
-
iframe和window.location.href唤起对比
-
iframe、window.location.href和a标签唤起三者对比
四、总结
- 对于ios来说,location.href跳转更合适,因为这种方式可以在Safari中成功唤起app。Safari作为iphone默认浏览器其重要性就不用多说了。
- 对于Android来说,在进入页面直接唤起的情况下,iframe和location.href是一样的,但是如果是事件驱动的唤起,iframe唤起的表现比location.href要更好一点。
- 通过测试可以发现,进入页面直接唤起和事件驱动的唤起,对于很多浏览器,两者的表现是不同的,简单来说,直接唤起的失败更多。
- 以上测试可能随时间已经有出入和变化,仅供参考。
五、使用中常见问题及解决方案
-
问题:可能会被app禁掉,比如微信,qq等
解决:通常会检测打开的app环境,如果是微信,qq等环境,提示用户浏览器内打开。
-
问题:ios9+ 禁止掉了
iframe
方式。解决:通常会检测ios的版本,ios9+不使用
iframe
方式,或直接用window.location.href
. -
问题:h5无法感知是否唤醒成功.
解决:一段时间之后自动跳转下载页,或者是依赖setTimeout在浏览器进入后台后进程切换导致的时间延迟判断。
-
问题:大部分浏览器需要用户手动触发链接,js自动触发无效。
解决:浏览器机制,可以设置弹框不在显示。
实际应用
之前已经介绍了要实现的需求,让我们一步一步来吧。我应用的是URL Schemes唤起方法,其实最完美的方案是IOS Universal Link,因为这个方法需要app那边配置很多东西,配置好后给前端提供一个地址调用。首先要获取访问设备的信息,代码如下:
let globalData = {
isMobile: false,//是否为移动端
openIn: '',//页面打开的app
openInType: '',//移动端设备的类型 ios an
isHW: false,//是否为华为浏览器
}
var browser = {
versions: 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, //火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或uc浏览器
iPhone: u.indexOf('iPhone') > -1, //是否为iPhone或者QQHD浏览器
iPad: u.indexOf('iPad') > -1, //是否iPad
webApp: u.indexOf('safari') == -1, //是否web应该程序,没有头部与底部
//DF:u.indexOf('DF'),//是否是mmc app端
};
}(),
language: (navigator.browserLanguage || navigator.language).toLowerCase(),
}
if (browser.versions.mobile) {//判断是否是移动设备打开。browser代码在下面
globalData.isMobile = true;
var ua = navigator.userAgent.toLowerCase();//获取判断用的对象
console.log(ua + '=============ua对象');
if (ua.match(/MicroMessenger/i) == "micromessenger") {
//在微信中打开
globalData.openIn = 'weixin';
}
if (ua.match(/WeiBo/i) == "weibo") {
//在新浪微博客户端打开
globalData.openIn = 'weibo';
}
if (ua.match(/ qq\//i) == " qq/") {
//在QQ空间打开
globalData.openIn = 'qq';
}
if (ua.match(/huawei/i) == 'huawei') {
globalData.isHW = true;
}
if (browser.versions.ios) {
//是否在IOS浏览器打开
globalData.openInType = 'ios';
}
if (browser.versions.android) {
//是否在安卓浏览器打开
globalData.openInType = 'android';
}
} else {
//否则就是PC浏览器打开
globalData.isMobile = false;
}
我定义了一个全局对象globalData,用来存储设备信息,有个属性isHW,是因为华为浏览器和其他安卓设备又有些不同,故区分出来单独处理。
此上是设备判断的逻辑,能够得到是pc端打开还是移动端,移动端打开app是什么app(例如:微信,微博,qq等),移动端设备的类型是 ios 还是 an,以及是否是华为手机。
下面就来写调起的程序了。
//唤起app或跳转下载页
function applyApp(){
if (globalData.isMobile && globalData.openInType == "android") {
//判断是否是会屏蔽下载链接的app打开
if (
globalData.openIn == "weixin" ||
globalData.openIn == "weibo" ||
globalData.openIn == "qq"
) {
//Toast("请将链接复制到浏览器中打开");
$('.mask').show();
return;
} else {
if (globalData.isHW) {
window.location.href = "wrjk://com.eko123.manmachine/startapp";
} else {
ifr = document.createElement("iframe");
ifr.setAttribute("src", "wrjk://com.ekao123.manmachine/startapp"); /***打开app的协议,有an同事提供***/
ifr.style.display = "none";
document.body.appendChild(ifr);
}
timer = setTimeout(function () {
location.href = "http://jrfile.huatu.com/app_version/hlpy/android/wrjk.apk";
}, 5000);
}
}
if (globalData.isMobile && globalData.openInType == "ios") {
if (
globalData.openIn == "weixin" ||
globalData.openIn == "weibo" ||
globalData.openIn == "qq"
) {
//Toast("请将链接复制到浏览器中打开");
$('.mask').show()
return;
} else {
var loadDateTime = new Date();
window.location = "com.ht.yiqikao://";//schema链接或者universal link
window.setTimeout(function() { //如果没有安装app,便会执行setTimeout跳转下载页
var timeOutDateTime = new Date();
if (timeOutDateTime - loadDateTime < 2000) {
window.location = "https://itunes.apple.com/cn/app/id1422958471?l=zh&ls=1&mt=8"; //ios下载地址
} else {
window.close();
}
}, 2000);
}
}
}
至此,唤起app的需求就完成了,仅供大家参考,若有更好的方法,欢迎留言交流。
本文部分内容参考自:https://www.jianshu.com/p/500f4be528e3