2017年4月12日 星期三

從瀏覽器或者 Webview 中喚醒 APP

文章來源:http://blog.html5funny.com/2015/06/19/open-app-from-mobile-web-browser-or-webview/


移動互聯時代,很多互聯網服務都會同時具備網站以及移動客戶端,很多人認為APP的能幫助建立更穩固的用戶關係,於是經常會接到各種從瀏覽器、webview中喚醒APP的需求,顯然,這對於前端開發人員來說,是一件很糾結的事。

喚醒APP

目前常見的主動喚醒APP方式有幾種:

Url scheme

Url scheme是iOS,Android平台都支持,只需要原生APP開發時註冊scheme, 那麼用戶點擊到此類鏈接時,會自動跳到APP。比如
1
2
3
4
<!-- 打開考拉APP首頁 -->
<a href="kaola://www.kaola.com">打開APP</a>
<!-- 呼叫號碼 -->
<a href="tel://13788889999">打開撥號</a>
如果配置scheme的路徑,並在app中識別,則可以直接打開APP特定頁面,如下:
1
2
<!-- 打開考拉APP商品詳情 -->
<a href="kaola://www.kaola.com/product/8342.html">打開APP商品詳情</a>
上述的鏈接,需要考慮手機是否支持此Scheme:
支持:彈出相應程序;
不支持:錯誤處理情況因平台而異,部分app會直接跳錯誤頁(比如Android Chrome/41,iOS中老版的Lofter); 也有的停留在原頁面,但彈出提示「無法打開網頁」(比如iOS7);iOS8以及最新的Android Chrome/43 目前都是直接停留在當前頁,不會跳出錯誤提示。
總體來看, iOS的支持程度比Android好,iOS在實際使用中,除非明確禁止的(比如微信),很少碰到不支持的情況;Android平台則各個app廠商差異很大,比如Chrome從25及以後就不再支持通過js觸發(非用戶點擊),設置iframe src地址等來觸發scheme跳轉

Android intent

這是Android平台獨有的,使用方式如下
1
2
3
4
5
6
7
8
9
intent:
   HOST/URI-path // Optional host 
   #Intent; 
      package=[string]; 
      action=[string]; 
      category=[string]; 
      component=[string]; 
      scheme=[string]; 
   end;
這裡的HOST/URI-path, 與普通http URL 的host/path書寫方式相同, package是Android app的包名,其它參數如action、category、component不是很理解, 具體見文檔 , 比如打開考拉 app的商品詳情,代碼如下
1
2
<!-- 打開考拉APP -->
<a href="intent://www.kaola.com/product/8342.html#Intent;scheme=kaola;package=com.kaola;end">打開APP</a>
如果手機能匹配到相應的APP,則會直接打開;如沒有安裝,則會跳到手機默認的應用商店,比如Google原生系統Nexus 5,將會直接跳到Google Play, 對於國內各廠商定製過的系統,則跳轉到各自的默認應用商店,或者彈出商店供選擇。
intent 比scheme相對完善的一點是,提供一個打開失敗去向URL的選項,可以通過指定參數 S.browser_fallback_url 來指定去向URL。
比如如下的打開APP動作,如果打開失敗,則跳轉到app下載頁,這對於國內的特殊網絡環境,還是挺有用的。
1
2
<!-- 打開考拉APP -->
<a href="intent://www.kaola.com/product/8342.html#Intent;scheme=kaola;package=com.kaola;S.browser_fallback_url=http%3A%2F%2Fapp.kaola.com;end">打開APP</a>

HTTP URL訂閱

Android Chrome平台獨有,app中訂閱自有內容相關的URL,在Chrome中瀏覽相關網頁時,會自動彈出提示,讓用戶選擇用瀏覽器還是APP打開,通用性有限。

iOS內置APP廣告條

在頁面Head中增加meta, 添加智能 App 廣告條 (iOS 6+ Safari), 如下
1
<meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"
可以自動判斷是否已安裝應用, 可惜只能用於iOS+Safari, 在第三方應用中就不行了。
效果如下:

Android Chrome內置app安裝提示

這個是Mobile Chrome 43 beta新加入的特性,在用戶瀏覽某一個網站多次後,如果Chrome發現該站點有原生APP,則會提示用戶下載原生APP,此項特性開發者無法干預,完全是Google的推薦行為,在項目中用不上,具體見新聞報導

實際應用中存在的問題

移動平台提供這麼多喚醒APP的方法,但是功能還不夠完善,以下情況JS無法檢測並做處理:
  1. APP如果喚醒失敗,很多時候都會跳到錯誤頁,影響用戶體驗,而我們的需求很可能是需要跳到下載或者其它頁
  2. APP成功喚醒,頁面無法直接得知,系統沒有提供此類回調。

實際需求、解決方案

  1. 要在打開APP失敗時,不能使當前頁面跳到錯誤頁,且打開失敗時,有失敗函數回調。
  2. 如果成功打開APP,有成功函數回調。
    針對第一點,可以將打開動作放到iframe中,就算跳轉失敗仍能停留在當前頁面;那剩下的問題就是如何檢測APP是否成功打開;
    網上常見解決方案如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //創建一個隱藏的iframe
    var ifr = document.createElement('iframe');
    ifr.src = 'com.baidu.tieba://';
    ifr.style.display = 'none';
    document.body.appendChild(ifr);
    //記錄喚醒時間
    var openTime = +new Date();
    window.setTimeout(function(){
        document.body.removeChild(ifr);
        //如果setTimeout 回調超過2500ms,則彈出下載
        if( (+new Date()) - openTime > 2500 ){
            window.location = 'http://exam.com/xxxx.apk';
        }
    },2000)
此腳本利用了程序切換到後台時,計時器回調會被推遲的原理,如果APP被喚醒,那麼此網頁必然進入後台,如果用戶從APP再切換回來,時間一般也會超過2.5s;如果app沒有喚醒,則setTimeout 基本上會準時回調,時間不會超過2s。但是實際上,這個僅僅在iOS平台有效,Android由於是多任務的,應用放到後台,setTimeout 基本上還是會準時觸發,所以這個邏輯還不夠完善。
那Android 瀏覽器有沒有方法檢測應用是否進入了後台呢? 頁面可見性API(Page Visibility API), 可以通過檢測 document.hiddendocument.[webkit|moz|ms]Hidden 來檢查頁面是否可見,或者訂閱頁面的visibilitychange事件; 如果僅僅應用在新版Android及Chrome上,這個是很美好的,對於老版本(<4.4)及Android Webview, 則不可用。
暮然回首,那人卻在燈火闌珊處,setInterval,對,就setInterval, 如果設置比較小的運行間隔(<30ms),在瀏覽器或者webview中,應用切換到後台,setInterval會被很明顯的延遲執行,比如設置一個運行間隔20ms,總計運行100次的定時器,如果頁面一直處於前台,則100次跑完,總耗時與 100x20=2000ms不會有太大差異,但頁面在後台運行時,此時間會明顯超過2000ms。可以利用這一點來實現是否成功打開APP檢測及回調。
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function openApp(openUrl, appUrl, action, callback) {
    //檢查app是否打開
    function checkOpen(cb){
        var _clickTime = +(new Date());
        function check(elsTime) {
            if ( elsTime > 3000 || document.hidden || document.webkitHidden) {
                cb(1);
            } else {
                cb(0);
            }
        }
        //啟動間隔20ms運行的定時器,並檢測累計消耗時間是否超過3000ms,超過則結束
        var _count = 0, intHandle;
        intHandle = setInterval(function(){
            _count++;        
            var elsTime = +(new Date()) - _clickTime;
            if (_count>=100 || elsTime > 3000 ) {
                clearInterval(intHandle);
                check(elsTime);
            }
        }, 20);
    }
    
    //在iframe 中打開APP
    var ifr = document.createElement('iframe');
    ifr.src = openUrl;
    ifr.style.display = 'none';
    if (callback) {
        checkOpen(function(opened){
            callback && callback(opened);
        });
    }
    
    document.body.appendChild(ifr);      
    setTimeout(function() {
        document.body.removeChild(ifr);
    }, 2000);  
}
iframe方式打開APP的問題:
  • Android Chrome/25+ 無法打開APP,所以最好是APP配合監聽http URL 來實現。
  • iOS、Android平台,近期發現在沒有安裝對應app時嘗試喚醒,有少數APP會連當前頁面(iframe的父頁面)也變成錯誤頁的情況。
其它問題:
微信無法打開或者下載,打開APP這個基本無解,下載則只能讓應用進駐應用寶市場,然後檢測到在微信中運行時,跳轉到應用寶頁面下載。

2015-11-01 補充

隨著iOS9, Android M的推出, 原先的Scheme URL喚醒app的方式成功率以及很低,並且蘋果還加入了確認機制,使得通過javascript來自動喚醒APP的方式基本不可用。
這兩大平台都推出了自己的WEB與APP連接的方式: Universal Links, App Link, 這部分需要App的開發人員來做了。

沒有留言:

張貼留言