无论 iOS 还是 Android 都不约而同地支持 URI Scheme(扫盲帖)来作为页面与客户端的通讯协议。这里的 URI Scheme 前缀不是一般的 http://,而是由客户端开发者定义的,一般在写程序的时候就会设置的了。然后剩下的部分就像普通的 URL 地址一样,需要大家来约定 URI Scheme 具体如何,例如参数是什么等等,好比网易新闻客户端的是以 newsapp:// 为前缀:



[html]  view plain  copy


 

1. <a href="newsapp://"> 打开网易新闻客户端</a>



如此便可以从网页打开客户端了。这只是最简单的 Scheme 部分,除此之外,应该还有更多的参数,一般要约定好,让网页开发者和客户端开发者两者之间可以相互调用。

这种情况,当然是假设用户已经安装了客户端,进而才会自动打开。那么,问题就来了,没有安装怎么办?以及最关键的一点——如何判断客户端是否已经安装呢?如果没有安装的话,我们可以提示提示用户去下载(跳到相应的下载页面)这个比较简单;但是,如何判断客户端是否已经安装呢?遗憾的是,浏览器并没有一个方法可以告诉你哪个客户端安装了,哪个没有安装!因此,如果按照上述 a 链接直接跳转而用户又没有安装客户端的话,浏览者看到的是一张空白的页面,——用户体验很差哦~于是我们一般采用“障眼法”,也就是把“隐藏”的 iframe 派上场,用 iframe.src 指向 URI Scheme 地址就可以了,既可以尝试打开客户端,又可以停留在当前页面上。

不过,我们也可以不停留也页面上——因为我们总是鼓励使用客户端的,所以这时候可跳转到下载客户端的页面。这是在没有安装的情况下的做法。如果客户端已经安装了,那就可以不跳转。

具体逻辑如下:



[html]  view plain  copy



1. <iframe class="openApp hide" src="about:blank"></iframe>  
2. <script>  
3.     function openClient(el, downloadUrl){  
4. iframe = arguments.callee.iframe;  
5.           
6.         if(!iframe){  
7. iframe = document.querySelector('.openApp')  
8. arguments.callee.iframe = iframe;  
9.         }  
10. startTime = +new Date();  
11. iframe.src = el.getAttribute('data-scheme');  
12.           
13.         // 如果已知安装的了,则无须跳转下载页  
14.         setTimeout(function() { // 如果不能打开客户端,跳到下载页面  
15. < 500) // 通过判断触发的时间与执行settimeout的时间差值是否小于设置的定时时间加上一个浮动值(一般设为100)。  
16. window.location.href = downloadUrl;  
17.         }, 400);  
18.     }  
19. openClient = openClient.delegate(null, 'http://g.3gtv.net');  
20. </script>


稍有点不足的就是,iOS 上面,如果没安装客户端,那么这段自定义的 URL Scheme(如 newsapp://) 就被视为"无效的链接“,弹对话框出来!(貌似 腾讯视频可以解决该问题哦,点击这里看页面——无奈的就是代码经过混淆的,不易察觉) 更新代码后,没有发现这个问题了。如果还是会这样的话,可以尝试用 script tag 发起请求,看能不能规避这个弹出框。


最后我们遇到一个下载页面的问题,由于微信内部浏览器限制的原因,不允许下载 apk 文件或者访问 app store,除非腾讯旗下的“应用宝”才可以。我们对腾讯这一封闭的做法十分不齿,却又无可奈何。于是只能默默地“归化”应用宝。好了,用就用呗——本来跳到下载页面是没有问题的。但令人觉得蛋疼的是,应用宝页面居然自己又会调起客户端(如果安装过的话)!这样势必就是“二次打开”客户端!要规避这个问题,一、不直接跳掉应用宝下载页面;二、改 UI,提供两个按钮,一是打开客户端的,另外一个是下载。

后记,还是兼容性问题:

  • 发现三星 note3 系统浏览器竟然不能 iframe 调起!
  • QQ 浏览器杯具了,竟然对 URI Scheme 一点都不支持!如下图所示,对此,优酷的做法是干脆把 打开客户端 的按钮隐藏掉。

android 指定区域 android.location_android 指定区域

android 指定区域 android.location_客户端_02





[html]  view plain  copy


 


    1. <%@tag pageEncoding="UTF-8" import="com.ajaxjs.bigfoot.*" %>  
    2. <%@ attribute name="download_url" required="true" description="应用的下载页面,for Android used"%>   
    3. <%@ attribute name="app_store_id" required="true" description="苹果商店的之 id,当前为 iphone 版,有 ipad 版吗?"%>   
    4. <%@ attribute name="URI_Scheme_iOS" required="true" description="iOS 的 URI_Scheme"%>   
    5. <%@ attribute name="URI_Scheme_Android" required="true" description="安卓的 URI_Scheme"%>  
    6. <!-- 按钮 单击事件 -->  
    7. <!-- <a class="openBtn">打开客户端</a> -->  
    8.   
    9. <iframe id="ifr" style="display: none;border:0;" src="javascript:void(0)"></iframe>  
    10. <script>  
    11.     ;(function(window, doc){  
    12.           
    13.         /**  
    14.          * 读取 search 和 hash 的参数  
    15.          */  
    16.         function localParam(search, hash){  
    17. search  = search    || window.location.search;  
    18. hash    = hash      || window.location.hash;  
    19.               
    20. fn = function(str, reg){  
    21.                 if(str){  
    22. data = {};  
    23.                     str.replace(reg,function( $0, $1, $2, $3 ){  
    24.                         data[ $1 ] = $3;  
    25.                     });  
    26.                     return data;  
    27.                 }  
    28.             }  
    29.               
    30.             return {  
    31.                 search: fn(search,  new RegExp( "([^?=&]+)(=([^&]*))?", "g" ))||{},  
    32.                 hash:   fn(hash,    new RegExp( "([^#=&]+)(=([^&]*))?", "g" ))||{}  
    33.             };  
    34.         }  
    35.           
    36.         // 应用注册的URI Scheme   
    37.         // 当发现没有安装应用的时候,跳转到 WAP 的下载页面  
    38.           
    39.         window.scrollTo(0,1);  
    40. params = localParam(),  
    41. isIDevice = (/iphone|ipod/gi).test(navigator.platform),  
    42. isIDeviceIpad = (/ipad/gi).test(navigator.platform),  
    43. isAndroid = (/Android/gi).test(navigator.userAgent),  
    44. isWeixin = (/MicroMessenger/ig).test(navigator.userAgent),  
    45. isappinstalled = params.search['isappinstalled'],  
    46. appinstall = params.search['appinstall'],  
    47. wxLink = 'weixinfallback.html',  
    48. iDownload = 'itms-apps://itunes.apple.com/cn/app/<%=app_store_id%>?mt=8',  
    49. openAppLink = params.hash['url'] || params.search['url'],  
    50. iframe = document.getElementById('ifr');  
    51.               
    52.         if( (isIDevice || isIDeviceIpad) && !isAndroid){  
    53. //          openAppLink = openAppLink || 'smcyuetv://';  
    54. openAppLink = openAppLink || '<%=URI_Scheme_iOS%>';  
    55.         }else if(isAndroid){  
    56. //          openAppLink = openAppLink || 'yuetv://';  
    57. openAppLink = openAppLink || '<%=URI_Scheme_Android%>';  
    58.         }  
    59.           
    60.         if(isappinstalled!==undefined){  
    61. isappinstalled='+isappinstalled+'&openurl='+openAppLink;  
    62.         }else if(appinstall!==undefined){  
    63. appinstall='+appinstall+'&openurl='+openAppLink;  
    64.         }else{  
    65. openurl='+openAppLink;  
    66.         }  
    67.           
    68.         if(isIDeviceIpad){  
    69. iDownload = 'itms-apps://itunes.apple.com/cn/app/id425349261?mt=8';  
    70.         }     
    71.         function download(){  
    72.             // if(isAndroid){  
    73. window.location = 'http://3g.163.com/m/android/software/2vbrks.html';  
    74.             // }else{  
    75. window.location ='<%=download_url%>';  
    76.             // }  
    77.         }  
    78.         /*  
    79.          * iOS点击打开:  
    80.         1.如果是微信就去引导图页面  
    81.         2.如果不是微信就走安装就打开不安装就去app store  
    82.         3.如果微信用户按引导图从浏览器打开就能走通第2条  
    83.           
    84.         android点击打开:  
    85.         1.如果是微信就在打开的时候同时跳转到有图的引导页  
    86.         2.如果不是微信就同时跳转到公公共下载页  
    87.         3.如果微信用户按引导图从浏览器打开就能走通第2条  
    88.          */  
    89.         function open(){  
    90.             if(isWeixin){  
    91. window.location = wxLink;  
    92.             }else if((isIDevice || isIDeviceIpad) && !isAndroid){  
    93. window.location = openAppLink;  
    94.                 setTimeout(function(){  
    95. window.location = iDownload;  
    96.                 }, 50);  
    97.             }else{  
    98. iframe.src = openAppLink;  
    99.                 download();  
    100.             }  
    101.         }  
    102.           
    103.         document.querySelector(".openBtn").addEventListener("click", open, false);  
    104.           
    105.         // 自动打开  
    106. autoopen = params.search['autoopen'] || params.hash['autoopen'];  
    107. autoopen == 1 && open();  
    108.     })(window,document);  
    109. <
     其实,只要是在

    页面,就应该可以通过 URI Scheme 调用客户端。那么,在 WebApp / Hyper App 也能如是作法吧!


    另外,我这里只是着重讨论的网页的部分。调用客户端还是比较简单的,如果需要调用客户端里面的某个一个模块呢?客户端可不想网页那样,天生就是有 URL 定位,传个地址或参数就可以了。这里涉及的客户端原生程序的部分比较多,就是说,如果直接跳到某个模块里面去,——这涉及到客户端模块是否已经合理解耦了,以及所依赖的上下文参数等等诸多问题,——需要和客户端开发者好好商量才行。



    无论 iOS 还是 Android 都不约而同地支持 URI Scheme(扫盲帖)来作为页面与客户端的通讯协议。这里的 URI Scheme 前缀不是一般的 http://,而是由客户端开发者定义的,一般在写程序的时候就会设置的了。然后剩下的部分就像普通的 URL 地址一样,需要大家来约定 URI Scheme 具体如何,例如参数是什么等等,好比网易新闻客户端的是以 newsapp:// 为前缀:



    [html]  view plain  copy


     


    1. <a href="newsapp://"> 打开网易新闻客户端</a>



    如此便可以从网页打开客户端了。这只是最简单的 Scheme 部分,除此之外,应该还有更多的参数,一般要约定好,让网页开发者和客户端开发者两者之间可以相互调用。

    这种情况,当然是假设用户已经安装了客户端,进而才会自动打开。那么,问题就来了,没有安装怎么办?以及最关键的一点——如何判断客户端是否已经安装呢?如果没有安装的话,我们可以提示提示用户去下载(跳到相应的下载页面)这个比较简单;但是,如何判断客户端是否已经安装呢?遗憾的是,浏览器并没有一个方法可以告诉你哪个客户端安装了,哪个没有安装!因此,如果按照上述 a 链接直接跳转而用户又没有安装客户端的话,浏览者看到的是一张空白的页面,——用户体验很差哦~于是我们一般采用“障眼法”,也就是把“隐藏”的 iframe 派上场,用 iframe.src 指向 URI Scheme 地址就可以了,既可以尝试打开客户端,又可以停留在当前页面上。

    不过,我们也可以不停留也页面上——因为我们总是鼓励使用客户端的,所以这时候可跳转到下载客户端的页面。这是在没有安装的情况下的做法。如果客户端已经安装了,那就可以不跳转。

    具体逻辑如下:



    [html]  view plain  copy



    1. <iframe class="openApp hide" src="about:blank"></iframe>  
    2. <script>  
    3.     function openClient(el, downloadUrl){  
    4. iframe = arguments.callee.iframe;  
    5.           
    6.         if(!iframe){  
    7. iframe = document.querySelector('.openApp')  
    8. arguments.callee.iframe = iframe;  
    9.         }  
    10. startTime = +new Date();  
    11. iframe.src = el.getAttribute('data-scheme');  
    12.           
    13.         // 如果已知安装的了,则无须跳转下载页  
    14.         setTimeout(function() { // 如果不能打开客户端,跳到下载页面  
    15. < 500) // 通过判断触发的时间与执行settimeout的时间差值是否小于设置的定时时间加上一个浮动值(一般设为100)。  
    16. window.location.href = downloadUrl;  
    17.         }, 400);  
    18.     }  
    19. openClient = openClient.delegate(null, 'http://g.3gtv.net');  
    20. </script>


    稍有点不足的就是,iOS 上面,如果没安装客户端,那么这段自定义的 URL Scheme(如 newsapp://) 就被视为"无效的链接“,弹对话框出来!(貌似 腾讯视频可以解决该问题哦,点击这里看页面——无奈的就是代码经过混淆的,不易察觉) 更新代码后,没有发现这个问题了。如果还是会这样的话,可以尝试用 script tag 发起请求,看能不能规避这个弹出框。


    最后我们遇到一个下载页面的问题,由于微信内部浏览器限制的原因,不允许下载 apk 文件或者访问 app store,除非腾讯旗下的“应用宝”才可以。我们对腾讯这一封闭的做法十分不齿,却又无可奈何。于是只能默默地“归化”应用宝。好了,用就用呗——本来跳到下载页面是没有问题的。但令人觉得蛋疼的是,应用宝页面居然自己又会调起客户端(如果安装过的话)!这样势必就是“二次打开”客户端!要规避这个问题,一、不直接跳掉应用宝下载页面;二、改 UI,提供两个按钮,一是打开客户端的,另外一个是下载。

    后记,还是兼容性问题:

    • 发现三星 note3 系统浏览器竟然不能 iframe 调起!
    • QQ 浏览器杯具了,竟然对 URI Scheme 一点都不支持!如下图所示,对此,优酷的做法是干脆把 打开客户端 的按钮隐藏掉。

    android 指定区域 android.location_android 指定区域

    android 指定区域 android.location_客户端_02





    [html]  view plain  copy


     


    1. <%@tag pageEncoding="UTF-8" import="com.ajaxjs.bigfoot.*" %>  
    2. <%@ attribute name="download_url" required="true" description="应用的下载页面,for Android used"%>   
    3. <%@ attribute name="app_store_id" required="true" description="苹果商店的之 id,当前为 iphone 版,有 ipad 版吗?"%>   
    4. <%@ attribute name="URI_Scheme_iOS" required="true" description="iOS 的 URI_Scheme"%>   
    5. <%@ attribute name="URI_Scheme_Android" required="true" description="安卓的 URI_Scheme"%>  
    6. <!-- 按钮 单击事件 -->  
    7. <!-- <a class="openBtn">打开客户端</a> -->  
    8.   
    9. <iframe id="ifr" style="display: none;border:0;" src="javascript:void(0)"></iframe>  
    10. <script>  
    11.     ;(function(window, doc){  
    12.           
    13.         /**  
    14.          * 读取 search 和 hash 的参数  
    15.          */  
    16.         function localParam(search, hash){  
    17. search  = search    || window.location.search;  
    18. hash    = hash      || window.location.hash;  
    19.               
    20. fn = function(str, reg){  
    21.                 if(str){  
    22. data = {};  
    23.                     str.replace(reg,function( $0, $1, $2, $3 ){  
    24.                         data[ $1 ] = $3;  
    25.                     });  
    26.                     return data;  
    27.                 }  
    28.             }  
    29.               
    30.             return {  
    31.                 search: fn(search,  new RegExp( "([^?=&]+)(=([^&]*))?", "g" ))||{},  
    32.                 hash:   fn(hash,    new RegExp( "([^#=&]+)(=([^&]*))?", "g" ))||{}  
    33.             };  
    34.         }  
    35.           
    36.         // 应用注册的URI Scheme   
    37.         // 当发现没有安装应用的时候,跳转到 WAP 的下载页面  
    38.           
    39.         window.scrollTo(0,1);  
    40. params = localParam(),  
    41. isIDevice = (/iphone|ipod/gi).test(navigator.platform),  
    42. isIDeviceIpad = (/ipad/gi).test(navigator.platform),  
    43. isAndroid = (/Android/gi).test(navigator.userAgent),  
    44. isWeixin = (/MicroMessenger/ig).test(navigator.userAgent),  
    45. isappinstalled = params.search['isappinstalled'],  
    46. appinstall = params.search['appinstall'],  
    47. wxLink = 'weixinfallback.html',  
    48. iDownload = 'itms-apps://itunes.apple.com/cn/app/<%=app_store_id%>?mt=8',  
    49. openAppLink = params.hash['url'] || params.search['url'],  
    50. iframe = document.getElementById('ifr');  
    51.               
    52.         if( (isIDevice || isIDeviceIpad) && !isAndroid){  
    53. //          openAppLink = openAppLink || 'smcyuetv://';  
    54. openAppLink = openAppLink || '<%=URI_Scheme_iOS%>';  
    55.         }else if(isAndroid){  
    56. //          openAppLink = openAppLink || 'yuetv://';  
    57. openAppLink = openAppLink || '<%=URI_Scheme_Android%>';  
    58.         }  
    59.           
    60.         if(isappinstalled!==undefined){  
    61. isappinstalled='+isappinstalled+'&openurl='+openAppLink;  
    62.         }else if(appinstall!==undefined){  
    63. appinstall='+appinstall+'&openurl='+openAppLink;  
    64.         }else{  
    65. openurl='+openAppLink;  
    66.         }  
    67.           
    68.         if(isIDeviceIpad){  
    69. iDownload = 'itms-apps://itunes.apple.com/cn/app/id425349261?mt=8';  
    70.         }     
    71.         function download(){  
    72.             // if(isAndroid){  
    73. window.location = 'http://3g.163.com/m/android/software/2vbrks.html';  
    74.             // }else{  
    75. window.location ='<%=download_url%>';  
    76.             // }  
    77.         }  
    78.         /*  
    79.          * iOS点击打开:  
    80.         1.如果是微信就去引导图页面  
    81.         2.如果不是微信就走安装就打开不安装就去app store  
    82.         3.如果微信用户按引导图从浏览器打开就能走通第2条  
    83.           
    84.         android点击打开:  
    85.         1.如果是微信就在打开的时候同时跳转到有图的引导页  
    86.         2.如果不是微信就同时跳转到公公共下载页  
    87.         3.如果微信用户按引导图从浏览器打开就能走通第2条  
    88.          */  
    89.         function open(){  
    90.             if(isWeixin){  
    91. window.location = wxLink;  
    92.             }else if((isIDevice || isIDeviceIpad) && !isAndroid){  
    93. window.location = openAppLink;  
    94.                 setTimeout(function(){  
    95. window.location = iDownload;  
    96.                 }, 50);  
    97.             }else{  
    98. iframe.src = openAppLink;  
    99.                 download();  
    100.             }  
    101.         }  
    102.           
    103.         document.querySelector(".openBtn").addEventListener("click", open, false);  
    104.           
    105.         // 自动打开  
    106. autoopen = params.search['autoopen'] || params.hash['autoopen'];  
    107. autoopen == 1 && open();  
    108.     })(window,document);  
    109. </script>

    其实,只要是在页面,就应该可以通过 URI Scheme 调用客户端。那么,在 WebApp / Hyper App 也能如是作法吧!


    另外,我这里只是着重讨论的网页的部分。调用客户端还是比较简单的,如果需要调用客户端里面的某个一个模块呢?客户端可不想网页那样,天生就是有 URL 定位,传个地址或参数就可以了。这里涉及的客户端原生程序的部分比较多,就是说,如果直接跳到某个模块里面去,——这涉及到客户端模块是否已经合理解耦了,以及所依赖的上下文参数等等诸多问题,——需要和客户端开发者好好商量才行。