時(shí)間:2018-10-18
URL Scheme: zhihu://
Universal Link: https://oia.zhihu.com/****
安卓端intent協(xié)議
前記
最近接到一個(gè)需求,需要在投放到外部app的頁(yè)面中支持喚醒a(bǔ)pp的功能扳躬,未安裝app就跳到APP Store或者安卓端調(diào)用下載app應(yīng)用甚亭。
這道這個(gè)需求的時(shí)候亏狰,安利了好久。網(wǎng)絡(luò)上對(duì)于前端控制喚醒的app的方式也有很詳盡的解釋促脉。本文就簡(jiǎn)單介紹一下URL Scheme喚醒方式和universal Link方式及其優(yōu)缺點(diǎn)。重點(diǎn)描述我在開(kāi)發(fā)過(guò)程中的踩坑點(diǎn)亡呵。
好了硫戈,說(shuō)了這么多丁逝,開(kāi)始切入正題吧。
在這個(gè)萬(wàn)物互聯(lián)的時(shí)代
雖然不能讓app之間相互通信嫩码,但是可以讓app之間相互喚起罪既,并且通過(guò)傳遞參數(shù)的形式簡(jiǎn)介單向通信琢感,前提是你已經(jīng)安裝了該應(yīng)用。要是沒(méi)有安裝的話就跳轉(zhuǎn)到下載
喚醒方式
1. URL Scheme
- 每一個(gè)ios中的APP都在安裝的時(shí)候可以自定義URL Scheme烘挫,開(kāi)頭類似
zhihu://
柬甥,至于怎么配置,就請(qǐng)各位百度一下吧卤橄,本文只討論前端如何使用URL Scheme撤防。 - 配置的注意點(diǎn):
定義的時(shí)候不要和其他app沖突而且也不要和原生app沖突寄月,因?yàn)樵赼pp安裝的時(shí)候,系統(tǒng)就注冊(cè)了你的URL Scheme要是和原生app沖突的話厂抖,原生的優(yōu)先級(jí)比其他的高克懊,會(huì)調(diào)起原生的而不是你的。安卓同理墙懂,不過(guò)是協(xié)議不同而已损搬,這個(gè)需要我們ios和安卓的小伙伴們提供。 - 使用URL Scheme中遇到的坑嵌灰,有些前任踩過(guò)也分享過(guò)了颅悉。
- Scheme無(wú)法判斷是否安裝了APP
這種弊端的出現(xiàn)是因?yàn)闉g覽器是沒(méi)有能力判斷是否安裝了App的剩瓶,當(dāng)Scheme沒(méi)有喚醒的時(shí)候,用戶會(huì)繼續(xù)留存在該網(wǎng)頁(yè)不會(huì)做任何的操作吠架。
所以搂鲫,看到這魂仍,我們偉大的程序員哥哥就想出了一個(gè)方法
這時(shí)拣挪,// 喚醒失敗樣例函數(shù) function onWakeupFail() { if (ua.isIOS()) { location. } else { location.;//直接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); }
- 發(fā)起Scheme跳轉(zhuǎn)
- 如果按照了App菠劝,會(huì)成功打開(kāi)。如果未安裝App笼平,會(huì)打開(kāi)失敗舔痪,沒(méi)任何效果
- 延遲1000ms執(zhí)行的意義
- 如果沒(méi)有安裝锄码,Scheme打開(kāi)失敗晌涕,等待1000ms之后余黎,自動(dòng)去下載
- 如果安裝了App载萌,App會(huì)打開(kāi)驯耻,當(dāng)前頁(yè)面會(huì)被暫停,后面的延遲代碼就會(huì)被阻斷炒考,不會(huì)執(zhí)行可缚。但是,這里有個(gè)問(wèn)題就是斋枢,當(dāng)再次返回到這個(gè)頁(yè)面時(shí)帘靡,那段被阻斷的延遲代碼就會(huì)執(zhí)行了,安卓會(huì)跳出下載apk包的提示瓤帚,IOS會(huì)再度跳到Appstore。是不是體驗(yàn)很差戈次,喚醒成功之后轩勘,再次進(jìn)來(lái)竟然讓我去下載?不過(guò)這個(gè)是目前最佳的解決辦法了怯邪,忍一下讓用戶多個(gè)點(diǎn)擊取消的操作绊寻,應(yīng)該估計(jì)可能還能勉強(qiáng)接受。不過(guò)嘛悬秉,這個(gè)也是有解決辦法的澄步,例如根據(jù)頁(yè)面的顯示和隱藏,當(dāng)監(jiān)聽(tīng)到離開(kāi)該頁(yè)面和泌,即跳去其他APP的時(shí)候村缸,就清除掉setTimeout函數(shù),這樣返回來(lái)的時(shí)候就不會(huì)再次執(zhí)行了:
/** * 監(jiān)聽(tīng)頁(yè)面的顯隱來(lái)判斷是否喚起成功 * 當(dāng)喚起成功的時(shí)候武氓,會(huì)離開(kāi)該頁(yè)面梯皿,此時(shí)去掉deeplink喚起的setTimeout * 防止喚起之后,又返回瀏覽器繼續(xù)執(zhí)行setTimeout县恕,引導(dǎo)用戶下載 */ 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('頁(yè)面隱藏了'); deepLinkTimeout && clearTimeout(deepLinkTimeout); } }; document.addEventListener(visibilityChangeEvent, onVisibilityChange); }
- Scheme被很多的App禁止了东羹,例如微信和百度瀏覽器,QQ瀏覽器弱睦。因?yàn)樗麄兂鲇诹舸嬗脩舻目剂堪傩眨幌M脩艨吹椒窒淼膬?nèi)容的時(shí)候就跳出App到其他應(yīng)用了,所以就攔截了所有的Scheme况木,此時(shí)就無(wú)法通過(guò)Scheme喚醒App垒拢。
解決辦法也很簡(jiǎn)單旬迹,就是嗅探一下瀏覽器類型,如果瀏覽器上方有個(gè)'...'圖標(biāo)的話求类,指示用戶跳到系統(tǒng)/外部瀏覽器打開(kāi) - 使用了URL Scheme系統(tǒng)會(huì)喚起一個(gè)彈窗“是否打開(kāi)***”奔垦。這樣跳轉(zhuǎn)不太流暢,所以建議在ios9以上的系統(tǒng)中使用 universalLink的方式喚醒尸疆,直接喚醒到App里面具體的頁(yè)面
- Scheme無(wú)法判斷是否安裝了APP
2. Universal Link
這里先說(shuō)明一下為什么蘋果在WWDC2015推出Universal Link
椿猎。因?yàn)?code>Universal Link將一個(gè)正常的url訪問(wèn)方式賦予了喚醒的功能,前提是你在App應(yīng)用中配置apple-app-association寿弱。同時(shí)解決了上文提到的Scheme的前兩個(gè)弊端犯眠。所以,建議廣大開(kāi)發(fā)們迎接新技術(shù)症革,在IOS9以上使用Universal Link
,而且在ios9以上系統(tǒng)筐咧,已經(jīng)不支持URL Scheme
的方式了。安卓的話就使用URL Scheme吧噪矛,這項(xiàng)技術(shù)是IOS特有的量蕊。
- 踩坑記(前輩們)
- 跨域
Universal Link,必須要求跨域艇挨,如果不跨域残炮,就不行,就失效缩滨,就不工作势就。(iOS 9.2之后的改動(dòng),蘋果就這么規(guī)定這么設(shè)計(jì)的)
例如:
假如當(dāng)前網(wǎng)頁(yè)的域名是 A
當(dāng)前網(wǎng)頁(yè)發(fā)起跳轉(zhuǎn)的域名是 B
必須要求 B 和 A 是不同域名楷怒,才會(huì)觸發(fā)Universal Link
如果B 和 A 是相同域名蛋勺,只會(huì)繼續(xù)在當(dāng)前WebView里面進(jìn)行跳轉(zhuǎn),哪怕你的Universal Link一切正常鸠删,根本不會(huì)打開(kāi)App
所以,一般使用Universal Link喚醒App的公司都有一個(gè)域來(lái)專門做universal link喚醒域贼陶。
- 跨域
+ 當(dāng)鏈接跳轉(zhuǎn)的頁(yè)面在WAP不存在和APP存在時(shí)
例如:WAP和APP功能差異非常大刃泡,除了公共的功能外,其他的功能WAP是WAP的碉怔,APP是APP的烘贴,形態(tài)和場(chǎng)景都有明顯差異。他只需要跳轉(zhuǎn)到APP撮胧,他沒(méi)有合法的```WAP Url```可以讓瀏覽器在沒(méi)有安裝App的情況下繼續(xù)跳轉(zhuǎn)桨踪。我們選擇的Universal Link的域名其實(shí)是一個(gè)沒(méi)有實(shí)際頁(yè)面的域名,也就是說(shuō)```https://xxx.xxx.xxx/view/*```這個(gè)url芹啥,如果沒(méi)安裝APP因此觸發(fā)WebView繼續(xù)跳轉(zhuǎn)原地址锻离,會(huì)直接404铺峭。
所以,可以通過(guò)重定向來(lái)設(shè)置汽纠,在url的hash參數(shù)中加入refer=redirectUrl卫键,后端獲取到這個(gè)參數(shù)就重定向到那個(gè)頁(yè)面。
我的踩坑記錄
畢竟每家廠商考慮的東西不一樣虱朵,有些能支持universalLink的就使用universalLink莉炉,否則就使用deeplink
目前ios端中:
-
UC、微博碴犬、頭條絮宁、Safari都支持universalLink或deeplink的喚醒方式 - QQ瀏覽器竟然Universal Link無(wú)效,而URL Scheme有效服协。額绍昂,這就有點(diǎn)尷尬了。(測(cè)試機(jī)ios12 QQ瀏覽器版本8.8.2.3990)蚯涮。
- 微信端治专,微信的話必須得調(diào)用他自己的wx-sdk工具才能喚醒外部引用,安卓和微信端都是遭顶。
安卓端的話:
由于同一使用URL Scheme的情況张峰,所以不用考慮QQ瀏覽器的情況了,但是遇到個(gè)問(wèn)題是某些瀏覽器URL Scheme喚醒不成功的時(shí)候彈出下載apk包的提示棒旗,但是喘批,但是,此時(shí)用戶已經(jīng)安裝了App了铣揉。這特么怎么辦饶深?只能說(shuō)某些國(guó)產(chǎn)瀏覽器真的厲害了
代碼走一波
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,無(wú)法知道是否喚醒成功
* @param {String} {wakeupUrl} 喚醒參數(shù) url喚起的鏈接
* @param {Function} onBeforeWakeup 喚醒前執(zhí)行
* @param {Function} onWakeupFail deeplink喚醒失敗后執(zhí)行
*/
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) {
// 此處不用調(diào)用onWakeupFail函數(shù)了逛拱,因?yàn)閁niversal Link跳轉(zhuǎn)后端會(huì)做一個(gè)重定向敌厘,不會(huì)訪問(wèn)到404的頁(yè)面
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. 微信
微信的話,可能為了用戶體驗(yàn)或者提高自身存在感朽合,自己搞了套喚醒的js俱两。要是想在微信喚醒外部app的話得使用wx-sdk。
其次曹步,跟微信打交道的話宪彩,我想離不開(kāi)WeixinJSBridge這個(gè)微信瀏覽器里掛載到window里的對(duì)象,以及wx-sdk的sdk工具讲婚。兩者功能基本一致尿孔,只是使用上有點(diǎn)區(qū)別。而wx-sdk主要是在外部App中使用的,WeixinJSBridge是針對(duì)微信中的h5頁(yè)面的活合。
關(guān)于如何使用wx-sdk.js 請(qǐng)各位小伙伴查看官方文檔:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
關(guān)于WeixinJSBridge雏婶,目前還沒(méi)找到好的文檔
- 引入js
- 注入配置權(quán)限
wx.config({
debug: true, // 開(kāi)啟調(diào)試模式,調(diào)用的所有api的返回值會(huì)在客戶端alert出來(lái),若要查看傳入的參數(shù)芜辕,可以在pc端打開(kāi)尚骄,參數(shù)信息會(huì)通過(guò)log打出,僅在pc端時(shí)才會(huì)打印侵续。
appId: '', // 必填倔丈,公眾號(hào)的唯一標(biāo)識(shí)
timestamp: , // 必填,生成簽名的時(shí)間戳
nonceStr: '', // 必填状蜗,生成簽名的隨機(jī)串
signature: '',// 必填需五,簽名
jsApiList: [] // 必填,需要使用的JS接口列表
});
注意這里的appid需要你去官網(wǎng)注冊(cè)轧坎,而signature需要后臺(tái)根據(jù)簽名算法動(dòng)態(tài)生成的
- 使用
WeixinJSBridge.invoke
的launchApplication
需要注意的是微信版本大于6.5.16才能支持WeixinJSBridge
function invokeLaunchApp(opts) {
const invoke = () => {
const conf = {
appID: '填寫申請(qǐng)的appid',
schemeUrl: opts.schemeUrl
// // 自定義 scheme URL 中的 path 部分宏邮,for iOS
// parameter: opts.parameter,
// // 格式可以自定義,第三方 APP 自主處理缸血,可以是 path 或 json蜜氨,for Android
// extInfo: opts.extInfo
};
/* eslint-disable */
// WeixinJSBridge 是微信環(huán)境下(真實(shí)環(huán)境或微信開(kāi)發(fā)者工具下)window 下的對(duì)象
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);
});
}
}
// 調(diào)用
wechatWakeupApp.wakeup({
schemeUrl: url,
onLaunch: handleLaunch
});
const handleLaunch = res => {
switch (res && res.err_msg) {
case 'launchApplication:ok':
break;
case 'launchApplication:fail':
// '打開(kāi)失敗,請(qǐng)檢查是否已安裝APP'
break;
case 'launchApplication:fail_check fail':
// '調(diào)用 app 權(quán)限校驗(yàn)失敗'
break;
default:
// 顯示res.err_msg
break;
}
};
- 完工
2018-11-29 補(bǔ)充:
在一次版本迭代的過(guò)程中捎泻,使用京東商城作為我們的模板對(duì)象飒炎,研究京東是如何喚醒的,過(guò)程中發(fā)現(xiàn)IOS12以后再Safari喚醒的時(shí)候笆豁,當(dāng)系統(tǒng)感知到universallink的時(shí)候郎汪,會(huì)自動(dòng)調(diào)起系統(tǒng)的選擇框,而IOS12一下不會(huì)存在該選擇框:
如圖所示闯狱,發(fā)現(xiàn)個(gè)坑煞赢,當(dāng)點(diǎn)擊取消的時(shí)候,IOS12下不會(huì)繼續(xù)請(qǐng)求universal link這個(gè)鏈接哄孤,即可以留著當(dāng)前頁(yè)了照筑。但是IOS12的時(shí)候就會(huì)繼續(xù)訪問(wèn)這個(gè)universal link鏈接,要是這個(gè)鏈接沒(méi)有匹配的頁(yè)面瘦陈,就會(huì)有報(bào)錯(cuò)的風(fēng)險(xiǎn)朦肘。所以,針對(duì)這個(gè)双饥,一些使用302重定向方式喚醒APP的技術(shù),需要考慮適配IOS12這種情況弟断。
而京東商城觸發(fā)該喚醒邏輯是使用模擬點(diǎn)擊的方式:
代碼來(lái)源京東線上:
setTimeout(function() {
var e = document.createElement("a");
// 此處的a咏花,最好是URL Scheme的連接,如果是Universal Link的話,點(diǎn)擊取消的時(shí)候會(huì)(IOS12下)會(huì)繼續(xù)訪問(wèn)universal Link的連接昏翰。
// 例如: a = 'jindongshop://goHome'苍匆。只是個(gè)虛擬例子,不能成功訪問(wèn)的棚菊。
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)
而如果沒(méi)有安裝京東APP的話浸踩,則會(huì)有以下提示框,之后统求,就會(huì)跳轉(zhuǎn)到下載頁(yè)面检碗。很不友好耶。码邻。
兼容性總結(jié):
Android系統(tǒng):Chrome for Android無(wú)法通過(guò)iframe方式來(lái)調(diào)用scheme折剃,而通過(guò)a鏈接的方式可以成功調(diào)用,而針對(duì)Chrome內(nèi)核的瀏覽器如360瀏覽器像屋,對(duì)于iframe和a鏈接的方式都能支持怕犁,所以對(duì)Chrome內(nèi)核的瀏覽器采用a鏈接的方式來(lái)調(diào)用scheme;對(duì)于其他瀏覽器己莺,如UC奏甫,QQ瀏覽器則采用iframe方式調(diào)用scheme。
iOS系統(tǒng):Safari瀏覽器不支持 iframe可直接做頁(yè)面跳轉(zhuǎn)凌受;對(duì)于UC阵子、Chrome、QQ只能通過(guò)a鏈接方式調(diào)用scheme胁艰。
2019-07-09
安卓UC瀏覽器使用scheme方式換不起APP
原因:安卓會(huì)在scheme前自動(dòng)加上http:// ?
最后感謝一下網(wǎng)站作者:(寫的很詳細(xì)也很好款筑,果斷mark)
1、Universal Link 前端部署踩坑記:Universal Link 前端部署采坑記
2腾么、包含intend喚起的解釋:如何喚起APP