網頁呼叫 iOS/Android 客戶端
無論 iOS 還是 Android 都不約而同地支援 URI Scheme(掃盲帖)來作為頁面與客戶端的通訊協議。這裡的 URI Scheme 字首不是一般的 http://,而是由客戶端開發者定義的,一般在寫程式的時候就會設定的了。然後剩下的部分就像普通的 URL 地址一樣,需要大家來約定 URI Scheme 具體如何,例如引數是什麼等等,好比網易新聞客戶端的是以 newsapp:// 為字首:
<a href="newsapp://"> 開啟網易新聞客戶端</a>
如此便可以從網頁開啟客戶端了。這只是最簡單的 Scheme 部分,除此之外,應該還有更多的引數,一般要約定好,讓網頁開發者和客戶端開發者兩者之間可以相互呼叫。
這種情況,當然是假設使用者已經安裝了客戶端,進而才會自動開啟。那麼,問題就來了,沒有安裝怎麼辦?以及最關鍵的一點——如何判斷客戶端是否已經安裝呢?如果沒有安裝的話,我們可以提示提示使用者去下載(跳到相應的下載頁面)這個比較簡單;但是,如何判斷客戶端是否已經安裝呢?遺憾的是,瀏覽器並沒有一個方法可以告訴你哪個客戶端安裝了,哪個沒有安裝!因此,如果按照上述 a 連結直接跳轉而使用者又沒有安裝客戶端的話,瀏覽者看到的是一張空白的頁面,——使用者體驗很差哦~於是我們一般採用“障眼法”,也就是把“隱藏”的 iframe 派上場,用 iframe.src 指向 URI Scheme 地址就可以了,既可以嘗試開啟客戶端,又可以停留在當前頁面上。
不過,我們也可以不停留也頁面上——因為我們總是鼓勵使用客戶端的,所以這時候可跳轉到下載客戶端的頁面。這是在沒有安裝的情況下的做法。如果客戶端已經安裝了,那就可以不跳轉。
具體邏輯如下:
<iframe class="openApp hide" src="about:blank"></iframe> <script> function openClient(el, downloadUrl){ var iframe = arguments.callee.iframe; if(!iframe){ iframe = document.querySelector('.openApp') arguments.callee.iframe = iframe; } var startTime = +new Date(); iframe.src = el.getAttribute('data-scheme'); // 如果已知安裝的了,則無須跳轉下載頁 setTimeout(function() { // 如果不能開啟客戶端,跳到下載頁面 if (Date.now() - startTime < 500) // 通過判斷觸發的時間與執行settimeout的時間差值是否小於設定的定時時間加上一個浮動值(一般設為100)。 window.location.href = downloadUrl; }, 400); } openClient = openClient.delegate(null, 'http://g.3gtv.net'); </script>
最後我們遇到一個下載頁面的問題,由於微信內部瀏覽器限制的原因,不允許下載 apk 檔案或者訪問 app store,除非騰訊旗下的“應用寶”才可以。我們對騰訊這一封閉的做法十分不齒,卻又無可奈何。於是只能默默地“歸化”應用寶。好了,用就用唄——本來跳到下載頁面是沒有問題的。但令人覺得蛋疼的是,應用寶頁面居然自己又會調起客戶端(如果安裝過的話)!這樣勢必就是“二次開啟”客戶端!要規避這個問題,一、不直接跳掉應用寶下載頁面;二、改 UI,提供兩個按鈕,一是開啟客戶端的,另外一個是下載。
後記,還是相容性問題:
- 發現三星 note3 系統瀏覽器竟然不能 iframe 調起!
- QQ 瀏覽器杯具了,竟然對 URI Scheme 一點都不支援!如下圖所示,對此,優酷的做法是乾脆把 開啟客戶端 的按鈕隱藏掉。
<%@tag pageEncoding="UTF-8" import="com.ajaxjs.bigfoot.*" %>
<%@ attribute name="download_url" required="true" description="應用的下載頁面,for Android used"%>
<%@ attribute name="app_store_id" required="true" description="蘋果商店的之 id,當前為 iphone 版,有 ipad 版嗎?"%>
<%@ attribute name="URI_Scheme_iOS" required="true" description="iOS 的 URI_Scheme"%>
<%@ attribute name="URI_Scheme_Android" required="true" description="安卓的 URI_Scheme"%>
<!-- 按鈕 單擊事件 -->
<!-- <a class="openBtn">開啟客戶端</a> -->
<iframe id="ifr" style="display: none;border:0;" src="javascript:void(0)"></iframe>
<script>
;(function(window, doc){
/**
* 讀取 search 和 hash 的引數
*/
function localParam(search, hash){
search = search || window.location.search;
hash = hash || window.location.hash;
var fn = function(str, reg){
if(str){
var data = {};
str.replace(reg,function( $0, $1, $2, $3 ){
data[ $1 ] = $3;
});
return data;
}
}
return {
search: fn(search, new RegExp( "([^?=&]+)(=([^&]*))?", "g" ))||{},
hash: fn(hash, new RegExp( "([^#=&]+)(=([^&]*))?", "g" ))||{}
};
}
// 應用註冊的URI Scheme
// 當發現沒有安裝應用的時候,跳轉到 WAP 的下載頁面
window.scrollTo(0,1);
var params = localParam(),
isIDevice = (/iphone|ipod/gi).test(navigator.platform),
isIDeviceIpad = (/ipad/gi).test(navigator.platform),
isAndroid = (/Android/gi).test(navigator.userAgent),
isWeixin = (/MicroMessenger/ig).test(navigator.userAgent),
isappinstalled = params.search['isappinstalled'],
appinstall = params.search['appinstall'],
wxLink = 'weixinfallback.html',
iDownload = 'itms-apps://itunes.apple.com/cn/app/<%=app_store_id%>?mt=8',
openAppLink = params.hash['url'] || params.search['url'],
iframe = document.getElementById('ifr');
if( (isIDevice || isIDeviceIpad) && !isAndroid){
// openAppLink = openAppLink || 'smcyuetv://';
openAppLink = openAppLink || '<%=URI_Scheme_iOS%>';
}else if(isAndroid){
// openAppLink = openAppLink || 'yuetv://';
openAppLink = openAppLink || '<%=URI_Scheme_Android%>';
}
if(isappinstalled!==undefined){
wxLink += '?isappinstalled='+isappinstalled+'&openurl='+openAppLink;
}else if(appinstall!==undefined){
wxLink += '?appinstall='+appinstall+'&openurl='+openAppLink;
}else{
wxLink += '?openurl='+openAppLink;
}
if(isIDeviceIpad){
//iDownload = 'itms-apps://itunes.apple.com/cn/app/id425349261?mt=8';
}
function download(){
// if(isAndroid){
// window.location = 'http://3g.163.com/m/android/software/2vbrks.html';
// }else{
window.location ='<%=download_url%>';
// }
}
/*
* iOS點選開啟:
1.如果是微信就去引導圖頁面
2.如果不是微信就走安裝就開啟不安裝就去app store
3.如果微信使用者按引導圖從瀏覽器開啟就能走通第2條
android點選開啟:
1.如果是微信就在開啟的時候同時跳轉到有圖的引導頁
2.如果不是微信就同時跳轉到公公共下載頁
3.如果微信使用者按引導圖從瀏覽器開啟就能走通第2條
*/
function open(){
if(isWeixin){
window.location = wxLink;
}else if((isIDevice || isIDeviceIpad) && !isAndroid){
window.location = openAppLink;
setTimeout(function(){
window.location = iDownload;
}, 50);
}else{
iframe.src = openAppLink;
download();
}
}
document.querySelector(".openBtn").addEventListener("click", open, false);
// 自動開啟
var autoopen = params.search['autoopen'] || params.hash['autoopen'];
autoopen == 1 && open();
})(window,document);
</script>
其實,只要是在頁面,就應該可以通過 URI Scheme 呼叫客戶端。那麼,在 WebApp / Hyper App 也能如是作法吧!另外,我這裡只是著重討論的網頁的部分。呼叫客戶端還是比較簡單的,如果需要呼叫客戶端裡面的某個一個模組呢?客戶端可不想網頁那樣,天生就是有 URL 定位,傳個地址或引數就可以了。這裡涉及的客戶端原生程式的部分比較多,就是說,如果直接跳到某個模組裡面去,——這涉及到客戶端模組是否已經合理解耦了,以及所依賴的上下文引數等等諸多問題,——需要和客戶端開發者好好商量才行。
參考: