在iOS中,需要調(diào)起一個(gè)app可以使用schema協(xié)議,這是iOS原生支持的,并且因?yàn)閕OS系統(tǒng)中都不能使用自己的瀏覽器內(nèi)核,所以所有的瀏覽器都支持,這跟android生態(tài)不一樣,android是可以自己搞內(nèi)核的,但是iOS不行。
在iOS中提供了兩種在瀏覽器中打開(kāi)APP的方法:Smart App Banner和schema協(xié)議。
Smart App Banner
即通過(guò)一個(gè)meta 標(biāo)簽,在標(biāo)簽上帶上app的信息,和打開(kāi)后的行為,例如:app-id之類的,代碼形如:
具體可以看下開(kāi)發(fā)文檔:https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html
今天Smart APP Banner不是我們的主角,我們說(shuō)的是schema
使用schema URL來(lái)打開(kāi)iOS APP
schema類似自定義url協(xié)議,我們可以通過(guò)自定義的協(xié)議來(lái)打開(kāi)自己的應(yīng)用,形如:
myapplink://
# 例如 facebook的
fb://
# itunes的
itms-apps://
# 還有短信也是類似的
sms://
如果要打開(kāi)一個(gè)app,最簡(jiǎn)單的方式是通過(guò)一個(gè)鏈接,如我們?cè)趆tml中這樣寫(xiě):
<a href="myapplink://">打開(kāi)我的app</a>
當(dāng)用戶點(diǎn)擊鏈接的時(shí)候就可以打開(kāi)對(duì)應(yīng)的app。
綁定click事件
但是實(shí)際中我們更多的情況是綁定事件,比如做個(gè)彈層啥的,不能一味的用a標(biāo)簽啊,所以可以通過(guò)兩種方式來(lái)解決:location.href和iframe。
iframe的方式是開(kāi)發(fā)中常用的,但是他也有一些問(wèn)題:
1.我們沒(méi)很好的方式來(lái)判斷是否打開(kāi)了app
2.會(huì)引起history變化
3.因?yàn)橐餳istory變化,所以一些webview會(huì)有問(wèn)題,比如:我查查,打開(kāi)一個(gè)頁(yè)面,如果有iframe,選擇在safari中打開(kāi),實(shí)際打開(kāi)的是iframe的頁(yè)面
4.如果頁(yè)面暴漏給了android系統(tǒng),那么也會(huì)出現(xiàn)頁(yè)面打不開(kāi),之類的問(wèn)題
5.如果沒(méi)有app,調(diào)起不成功,ios的safari會(huì)自己彈出一個(gè)對(duì)話框:打不開(kāi)網(wǎng)址之類的提示
所以現(xiàn)在的問(wèn)題是:如何知道iframe已經(jīng)打開(kāi)了某個(gè)app,即解決iframe打開(kāi)app回調(diào)。
使用iframe在iOS系統(tǒng)中打開(kāi)app
聰明的你可能想到了,iframe的onload事件啊,可是遺憾的說(shuō),無(wú)效!所以我們找到了定時(shí)器(setTimeout),通過(guò)一個(gè)定時(shí)器,如果在一段時(shí)間內(nèi)(比如500ms),當(dāng)點(diǎn)擊了按鈕(記錄time1),頁(yè)面沒(méi)有切走(調(diào)起app之后,頁(yè)面進(jìn)程會(huì)被中斷),進(jìn)程中斷,那么計(jì)時(shí)器也會(huì)中斷,這時(shí)候應(yīng)該不會(huì)觸發(fā)timer,如果調(diào)起失敗,那么timer會(huì)就觸發(fā),我們判斷下在一定時(shí)間內(nèi)如果頁(yè)面沒(méi)有被切走,就認(rèn)為調(diào)起失敗。
另外通過(guò)timer觸發(fā)時(shí)候的timer2,做差,判斷是否太離譜了(切走了之后的時(shí)間應(yīng)該比timer實(shí)際定時(shí)的500ms要長(zhǎng)):
function openIos(url, callback) {
if (!url) {
return;
}
var node = document.createElement('iframe');
node.style.display = 'none';
var body = document.body;
var timer;
var clear = function(evt, isTimeout) {
(typeof callback==='function') && callback(isTimeout);
if (!node) {
return;
}
node.onload = null;
body.removeChild(node);
node = null;
};
var hide = function(e){
clearTimeout(timer);
clear(e, false);
};
node.onload = clear;
node.src = url;
body.appendChild(node);
var now = +new Date();
//如果事件失敗,則1秒設(shè)置為空
timer = setTimeout(function(){
var newTime = +new Date();
if(now-newTime>600){
//因?yàn)榍凶吡耍谇谢貋?lái)需要消耗時(shí)間
//所以timer即使執(zhí)行了,但是兩者的時(shí)間差應(yīng)該跟500ms有較大的出入
//但是實(shí)際并不是這樣的!
clear(null, false);
}else{
clear(null, true);
}
}, 500);
}
看上去方法很靠譜,但是現(xiàn)實(shí)總是那么的殘酷!
不同的瀏覽器app(包括webview),都有自己在后臺(tái)的常駐時(shí)間,即:假如一個(gè)瀏覽器他在被切走之后,后臺(tái)常駐10s,那么我們?cè)O(shè)置定時(shí)器5s過(guò)期就是徒勞的,而且5s的定時(shí)器,用戶要空等5s!交互也不讓你這樣干啊!
最后我們想到了pageshow和pagehide事件,即如果瀏覽器被切走到了要打開(kāi)的app,應(yīng)該會(huì)觸發(fā)瀏覽器的pagehide事件,而從app重新返回到瀏覽器,就會(huì)觸發(fā)pageshow方法。
但是經(jīng)過(guò)代碼測(cè)試發(fā)現(xiàn),在uc、chrome中,不會(huì)觸發(fā)pagehide和pageshow的方法,而在safari中可以的。
結(jié)論:
1.使用iframe調(diào)用schema URL
2.使用定時(shí)器判斷在一段時(shí)間內(nèi)是否調(diào)起成功
3.使用pageshow和pagehide來(lái)輔助定時(shí)器做更詳細(xì)的判斷
4.定時(shí)器中如果有alert可能不會(huì)被彈出,這一點(diǎn)很吃驚!后面的dom竟然5.執(zhí)行了,但是alert沒(méi)彈出,可能跟alert的實(shí)現(xiàn)有關(guān)系吧
6.在實(shí)驗(yàn)中我使用了兩個(gè)定時(shí)器,是因?yàn)榍谢貫g覽器之后,有時(shí)候timeout觸發(fā)要在pagehide和pageshow之前
7.計(jì)算timer實(shí)際執(zhí)行時(shí)間差,也是不靠譜的
最后附上研究的代碼,算是比較靠譜的方法了,雖然還是有一定的失敗(第三方瀏覽器pagehide和pageshow不觸發(fā)):
<p><button id="btn">點(diǎn)我點(diǎn)我啊!alert,不會(huì)彈出</button></p>
<p><button id="btn2">點(diǎn)我點(diǎn)我啊!alert2,雖然有alert和info,info執(zhí)行,但是alert不彈出</button></p>
<p><button id="btninfo">點(diǎn)我點(diǎn)我啊!info可以</button></p>
$(function(){
var $info = $('#info');
function info(msg){
var p = $('<p>'+msg+'</p>');
$info.append(p);
}
$('#btn').on('click', function(){
openIos('baiduboxapp://', function(t){
if(t){
alert('timeout or no baidu APP');
}else{
alert('invoke success');
}
});
});
$('#btn2').on('click', function(){
openIos('baiduboxapp://', function(t){
if(t){
info('timeout or no baidu APP2');
alert('timeout or no baidu APP2');
}else{
info('invoke success2');
alert('invoke success2');
}
});
});
$('#btninfo').on('click', function(){
openIos('baiduboxapp://', function(t){
if(t){
info('timeout or no baidu APP');
}else{
info('invoke success');
}
});
});
});
function openIos(url, callback) {
if (!url) {
return;
}
var node = document.createElement('iframe');
node.style.display = 'none';
var body = document.body;
var timer;
var clear = function(evt, isTimeout) {
(typeof callback==='function') && callback(isTimeout);
window.removeEventListener('pagehide', hide, true);
window.removeEventListener('pageshow', hide, true);
if (!node) {
return;
}
node.onload = null;
body.removeChild(node);
node = null;
};
var hide = function(e){
clearTimeout(timer);
clear(e, false);
};
window.addEventListener('pagehide', hide, true);
window.addEventListener('pageshow', hide, true);
node.onload = clear;
node.src = url;
body.appendChild(node);
var now = +new Date();
//如果事件失敗,則1秒設(shè)置為空
timer = setTimeout(function(){
timer = setTimeout(function(){
var newTime = +new Date();
if(now-newTime>1300){
clear(null, false);
}else{
clear(null, true);
}
}, 1200);
}, 60);
}