时间:2018-10-18
URL Scheme: zhihu://
Universal Link: https://oia.zhihu.com/****
安卓端intent协议
前记
最近接到一个需求,需要在投放到外部app的页面中支持唤醒app的功能,未安装app就跳到APP Store或者安卓端调用下载app应用。
这道这个需求的时候,安利了好久。网络上对于前端控制唤醒的app的方式也有很详尽的解释。本文就简单介绍一下URL Scheme唤醒方式和universal Link方式及其优缺点。重点描述我在开发过程中的踩坑点。
好了,说了这么多,开始切入正题吧。
在这个万物互联的时代
虽然不能让app之间相互通信,但是可以让app之间相互唤起,并且通过传递参数的形式简介单向通信,前提是你已经安装了该应用。要是没有安装的话就跳转到下载
唤醒方式
1. URL Scheme
- 每一个ios中的APP都在安装的时候可以自定义URL Scheme,开头类似
zhihu://
,至于怎么配置,就请各位百度一下吧,本文只讨论前端如何使用URL Scheme。 - 配置的注意点:
定义的时候不要和其他app冲突而且也不要和原生app冲突,因为在app安装的时候,系统就注册了你的URL Scheme要是和原生app冲突的话,原生的优先级比其他的高,会调起原生的而不是你的。安卓同理,不过是协议不同而已,这个需要我们ios和安卓的小伙伴们提供。 - 使用URL Scheme中遇到的坑,有些前任踩过也分享过了。
- Scheme无法判断是否安装了APP
这种弊端的出现是因为浏览器是没有能力判断是否安装了App的,当Scheme没有唤醒的时候,用户会继续留存在该网页不会做任何的操作。
所以,看到这,我们伟大的程序员哥哥就想出了一个方法
这时,// 唤醒失败样例函数 function onWakeupFail() { if (ua.isIOS()) { location.href = 'https://itunes.apple.com/us/app/idxxxxxxx?mt=8' } else { location.href='https://xxx.xxx.xxx/xxx/xxx.apk';//直接apk下载link } } function openByScheme(wakeupUrl, onBeforeWakeup, onWakeupFail) { var ifm = document.createElement('iframe'); ifm.setAttribute('src', wakeupUrl); ifm.setAttribute('style', 'display:none'); document.body.appendChild(ifm); onBeforeWakeup && onBeforeWakeup(); var currentTime = Date.now(); setTimeout(function() { var nowTime = Date.now(); if (nowTime - currentTime < 1050) { onWakeupFail && onWakeupFail(); } }, 1000); }
- 发起Scheme跳转
- 如果按照了App,会成功打开。如果未安装App,会打开失败,没任何效果
- 延迟1000ms执行的意义
- 如果没有安装,Scheme打开失败,等待1000ms之后,自动去下载
- 如果安装了App,App会打开,当前页面会被暂停,后面的延迟代码就会被阻断,不会执行。但是,这里有个问题就是,当再次返回到这个页面时,那段被阻断的延迟代码就会执行了,安卓会跳出下载apk包的提示,IOS会再度跳到Appstore。是不是体验很差,唤醒成功之后,再次进来竟然让我去下载?不过这个是目前最佳的解决办法了,忍一下让用户多个点击取消的操作,应该估计可能还能勉强接受。不过嘛,这个也是有解决办法的,例如根据页面的显示和隐藏,当监听到离开该页面,即跳去其他APP的时候,就清除掉setTimeout函数,这样返回来的时候就不会再次执行了:
/** * 监听页面的显隐来判断是否唤起成功 * 当唤起成功的时候,会离开该页面,此时去掉deeplink唤起的setTimeout * 防止唤起之后,又返回浏览器继续执行setTimeout,引导用户下载 */ function attachDocumentHide() { // eslint-disable-next-line let hiddenProperty = 'hidden' in document ? 'hidden' : ('webkitHidden' in document ? 'webkitHidden' : ('mozHidden' in document ? 'mozHidden' : null)); let visibilityChangeEvent = hiddenProperty.replace( /hidden/i, 'visibilitychange' ); let onVisibilityChange = function() { if (document[hiddenProperty]) { console.log('页面隐藏了'); deepLinkTimeout && clearTimeout(deepLinkTimeout); } }; document.addEventListener(visibilityChangeEvent, onVisibilityChange); }
- Scheme被很多的App禁止了,例如微信和百度浏览器,QQ浏览器。因为他们出于留存用户的考量,不希望用户看到分享的内容的时候就跳出App到其他应用了,所以就拦截了所有的Scheme,此时就无法通过Scheme唤醒App。
解决办法也很简单,就是嗅探一下浏览器类型,如果浏览器上方有个'...'图标的话,指示用户跳到系统/外部浏览器打开 - 使用了URL Scheme系统会唤起一个弹窗“是否打开***”。这样跳转不太流畅,所以建议在ios9以上的系统中使用 universalLink的方式唤醒,直接唤醒到App里面具体的页面
- Scheme无法判断是否安装了APP
2. Universal Link
这里先说明一下为什么苹果在WWDC2015推出Universal Link
。因为Universal Link
将一个正常的url访问方式赋予了唤醒的功能,前提是你在App应用中配置apple-app-association。同时解决了上文提到的Scheme的前两个弊端。所以,建议广大开发们迎接新技术,在IOS9以上使用Universal Link
,而且在ios9以上系统,已经不支持URL Scheme
的方式了。安卓的话就使用URL Scheme吧,这项技术是IOS特有的。
- 踩坑记(前辈们)
- 跨域
Universal Link,必须要求跨域,如果不跨域,就不行,就失效,就不工作。(iOS 9.2之后的改动,苹果就这么规定这么设计的)
例如:
假如当前网页的域名是 A
当前网页发起跳转的域名是 B
必须要求 B 和 A 是不同域名,才会触发Universal Link
如果B 和 A 是相同域名,只会继续在当前WebView里面进行跳转,哪怕你的Universal Link一切正常,根本不会打开App
所以,一般使用Universal Link唤醒App的公司都有一个域来专门做universal link唤醒域。
- 跨域
+ 当链接跳转的页面在WAP不存在和APP存在时
例如:WAP和APP功能差异非常大,除了公共的功能外,其他的功能WAP是WAP的,APP是APP的,形态和场景都有明显差异。他只需要跳转到APP,他没有合法的```WAP Url```可以让浏览器在没有安装App的情况下继续跳转。我们选择的Universal Link的域名其实是一个没有实际页面的域名,也就是说```https://xxx.xxx.xxx/view/*```这个url,如果没安装APP因此触发WebView继续跳转原地址,会直接404。
所以,可以通过重定向来设置,在url的hash参数中加入refer=redirectUrl,后端获取到这个参数就重定向到那个页面。
我的踩坑记录
毕竟每家厂商考虑的东西不一样,有些能支持universalLink的就使用universalLink,否则就使用deeplink
目前ios端中:
-
UC、微博、头条、Safari都支持universalLink或deeplink的唤醒方式 - QQ浏览器竟然Universal Link无效,而URL Scheme有效。额,这就有点尴尬了。(测试机ios12 QQ浏览器版本8.8.2.3990)。
- 微信端,微信的话必须得调用他自己的wx-sdk工具才能唤醒外部引用,安卓和微信端都是。
安卓端的话:
由于同一使用URL Scheme的情况,所以不用考虑QQ浏览器的情况了,但是遇到个问题是某些浏览器URL Scheme唤醒不成功的时候弹出下载apk包的提示,但是,但是,此时用户已经安装了App了。这特么怎么办?只能说某些国产浏览器真的厉害了
代码走一波
1. 非微信端
/*
* APP唤醒模块
*/
var testAgent = function(agentRegEx) {
return function() {
return agentRegEx.test((window.navigator && navigator.userAgent) || '')
}
}
var ua = {
isSafari: testAgent(/webkit\W(?!.*chrome).*safari\W/i),
isIOS: testAgent(/(ipad|iphone|ipod)/i),
isWechat: detect(/micromessenger/i),
isUC: testAgent(/uc browser|ucbrowser|ucweb/i)
}
var wakeupApp = {
/**
* 唤醒APP,无法知道是否唤醒成功
* @param {String} {wakeupUrl} 唤醒参数 url唤起的链接
* @param {Function} onBeforeWakeup 唤醒前执行
* @param {Function} onWakeupFail deeplink唤醒失败后执行
*/
wakeup: function(wakeupUrl, onBeforeWakeup, onWakeupFail) {
var iPhoneVersion = navigator.userAgent.match(
/OS ([\d]+)_\d[_\d]* like Mac OS X/i
);
var wakeupUrl = wakeupUrl || '';
if (
ua.isIOS() &&
(ua.isSafari() || ua.isUC()) &&
iPhoneVersion &&
iPhoneVersion[1] >= 9
) {
this._openByUniversalLink(
wakeupUrl,
onBeforeWakeup,
onWakeupFail
);
} else {
this._openByIframe(wakeupUrl, onBeforeWakeup, onWakeupFail);
}
},
/**
* universal_link唤起
* @param wakeupUrl
* @param onBeforeWakeup
* @param onWakeupFail
*/
_openByUniversalLink: function(wakeupUrl, onBeforeWakeup, onWakeupFail) {
// 此处不用调用onWakeupFail函数了,因为Universal Link跳转后端会做一个重定向,不会访问到404的页面
onBeforeWakeup && onBeforeWakeup();
wakeupUrl = wakeupUrl.replace(
'zhihu://',
'//oia.zhihu.com/'
);
location.href = wakeupUrl;
},
/**
* iframe唤起
* @param wakeupUrl
* @param onBeforeWakeup
* @param onWakeupFail
* @private
*/
_openByIframe: function(wakeupUrl, onBeforeWakeup, onWakeupFail) {
var ifm = document.createElement('iframe');
ifm.setAttribute('src', wakeupUrl);
ifm.setAttribute('style', 'display:none');
document.body.appendChild(ifm);
onBeforeWakeup && onBeforeWakeup();
var currentTime = Date.now();
setTimeout(function() {
var nowTime = Date.now();
if (nowTime - currentTime < 1050) {
onWakeupFail && onWakeupFail();
}
}, 1000);
}
};
2. 微信
微信的话,可能为了用户体验或者提高自身存在感,自己搞了套唤醒的js。要是想在微信唤醒外部app的话得使用wx-sdk。
其次,跟微信打交道的话,我想离不开WeixinJSBridge这个微信浏览器里挂载到window里的对象,以及wx-sdk的sdk工具。两者功能基本一致,只是使用上有点区别。而wx-sdk主要是在外部App中使用的,WeixinJSBridge是针对微信中的h5页面的。
关于如何使用wx-sdk.js 请各位小伙伴查看官方文档:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
关于WeixinJSBridge,目前还没找到好的文档
- 引入js
- 注入配置权限
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,需要使用的JS接口列表
});
注意这里的appid需要你去官网注册,而signature需要后台根据签名算法动态生成的
- 使用
WeixinJSBridge.invoke
的launchApplication
需要注意的是微信版本大于6.5.16才能支持WeixinJSBridge
function invokeLaunchApp(opts) {
const invoke = () => {
const conf = {
appID: '填写申请的appid',
schemeUrl: opts.schemeUrl
// // 自定义 scheme URL 中的 path 部分,for iOS
// parameter: opts.parameter,
// // 格式可以自定义,第三方 APP 自主处理,可以是 path 或 json,for Android
// extInfo: opts.extInfo
};
/* eslint-disable */
// WeixinJSBridge 是微信环境下(真实环境或微信开发者工具下)window 下的对象
WeixinJSBridge &&
WeixinJSBridge.invoke &&
WeixinJSBridge.invoke('launchApplication', conf, opts.onLaunch);
/* eslint-enable */
};
setTimeout(invoke, 0);
}
function wakeup(opts) {
if (window.WeixinJSBridge) {
invokeLaunchApp(opts);
} else {
document.addEventListener('WeixinJSBridgeReady', () => {
this.invokeLaunchApp(opts);
});
}
}
// 调用
wechatWakeupApp.wakeup({
schemeUrl: url,
onLaunch: handleLaunch
});
const handleLaunch = res => {
switch (res && res.err_msg) {
case 'launchApplication:ok':
break;
case 'launchApplication:fail':
// '打开失败,请检查是否已安装APP'
break;
case 'launchApplication:fail_check fail':
// '调用 app 权限校验失败'
break;
default:
// 显示res.err_msg
break;
}
};
- 完工
2018-11-29 补充:
在一次版本迭代的过程中,使用京东商城作为我们的模板对象,研究京东是如何唤醒的,过程中发现IOS12以后再Safari唤醒的时候,当系统感知到universallink的时候,会自动调起系统的选择框,而IOS12一下不会存在该选择框:
image.png
如图所示,发现个坑,当点击取消的时候,IOS12下不会继续请求universal link这个链接,即可以留着当前页了。但是IOS12的时候就会继续访问这个universal link链接,要是这个链接没有匹配的页面,就会有报错的风险。所以,针对这个,一些使用302重定向方式唤醒APP的技术,需要考虑适配IOS12这种情况。
而京东商城触发该唤醒逻辑是使用模拟点击的方式:
代码来源京东线上:
setTimeout(function() {
var e = document.createElement("a");
// 此处的a,最好是URL Scheme的连接,如果是Universal Link的话,点击取消的时候会(IOS12下)会继续访问universal Link的连接。
// 例如: a = 'jindongshop://goHome'。只是个虚拟例子,不能成功访问的。
e.setAttribute("href", a);
e.style.display = "none";
document.body.appendChild(e);
var t = document.createEvent("HTMLEvents");
t.initEvent("click", !1, !1);
e.dispatchEvent(t)
}, 0)
而如果没有安装京东APP的话,则会有以下提示框,之后,就会跳转到下载页面。很不友好耶。。
微信图片_20181129145950.jpg
兼容性总结:
Android系统:Chrome for Android无法通过iframe方式来调用scheme,而通过a链接的方式可以成功调用,而针对Chrome内核的浏览器如360浏览器,对于iframe和a链接的方式都能支持,所以对Chrome内核的浏览器采用a链接的方式来调用scheme;对于其他浏览器,如UC,QQ浏览器则采用iframe方式调用scheme。
iOS系统:Safari浏览器不支持 iframe可直接做页面跳转;对于UC、Chrome、QQ只能通过a链接方式调用scheme。
2019-07-09
安卓UC浏览器使用scheme方式换不起APP
原因:安卓会在scheme前自动加上http:// ?
最后感谢一下网站作者:(写的很详细也很好,果断mark)
1、Universal Link 前端部署踩坑记:Universal Link 前端部署采坑记
2、包含intend唤起的解释:如何唤起APP
网友评论