1. 程式人生 > >網頁呼叫 iOS/Android 客戶端

網頁呼叫 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>
稍有點不足的就是,iOS 上面,如果沒安裝客戶端,那麼這段自定義的 URL Scheme(如 newsapp://) 就被視為"無效的連結“,彈對話框出來!(貌似 騰訊視訊可以解決該問題哦,點選這裡看頁面——無奈的就是程式碼經過混淆的,不易察覺) 更新程式碼後,沒有發現這個問題了。如果還是會這樣的話,可以嘗試用 script tag 發起請求,看能不能規避這個彈出框。

最後我們遇到一個下載頁面的問題,由於微信內部瀏覽器限制的原因,不允許下載 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 定位,傳個地址或引數就可以了。這裡涉及的客戶端原生程式的部分比較多,就是說,如果直接跳到某個模組裡面去,——這涉及到客戶端模組是否已經合理解耦了,以及所依賴的上下文引數等等諸多問題,——需要和客戶端開發者好好商量才行。

參考: