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的開發人員來做了。

iOS 9 學習系列:打通 iOS 9 的通用鏈接(Universal Links)

文章來源:http://www.cocoachina.com/ios/20150911/13321.html


在WWDC 2015 上, Apple 為 iOS 9 宣佈了一個所謂 通用鏈接 的深層鏈接特性, 視頻地址為?[無縫鏈接到您的 App]。雖然它不是一個必須實現的功能, 但還是需要引起一些注意。
在網上有太多讓人迷惑和錯誤的信息, 這次 WWDC 本身也沒有去描述細節。幸運的是, 在 HOKO 我們在我們的智能鏈接上添加了這一特性, 所以我們可以無縫的引導用戶到 APP 上。
什麼是通用鏈接?
很顯然 Apple 正在大力推動 APP 開發者在深層鏈接上有更好的體驗。所有的消息都圍繞著深層鏈接技術。與此同時, Apple 推出通用鏈接:一種能夠方便的通過傳統 HTTP 鏈接來啟動 APP, 使用相同的網址打開網站和 APP。
通過唯一的網址, 就可以鏈接一個特定的視圖到你的 APP 裡面, 不需要特別的 schema 。試想一下 Twitter 使用了通用鏈接, 那麼你每在 twitter。com 點擊一個鏈接, 你的 iOS 設備都會在 Twitter 裡面自動打開這個頁面, 而不是當你沒有安裝時轉到普通的網頁。這個用戶體驗是順暢的, 最重要的是用戶不會失去上下文(跳到 APP 後 Safari 上不再留下空標籤)。
準備使用通用鏈接
實現通用鏈接不難, 但首先必須遵守一些先決條件。如下:
  • 有一個註冊的域名
  • 通過 SSL 訪問域名
  • 支持上傳一個 JSON 文件到你的域名
  • 至少 iOS 9 beta 2 版本 [下載],這很重要, 因為如果是之前的測試版本你需要做額外的操作。
  • 至少 Xcode 7 beta 2 [下載]
如果你都有了, 那就來按照下面3個步驟來做吧。
1.添加域名到 Capabilities
首先, 你必須在 Xcode 的 capabilities 裡 添加你的 APP 域名, 必須用 applinks: 前置它:還添加一些你可能擁有的子域和擴展(www.domain.com, news.domain.com 等等)。
apple_capabilities.png
用 applinks: 前綴添加所有域名, 同時不要忘了包含所有可能需要的子域名*
這將使你的 APP 從你的域名請求一個特殊的 JSON 文件 apple-app-site-association。當你第一次啟動 APP,它會從?https://domain.com/apple-app-site-association?下載這個文件。跳到下個步驟來瞭解如何構建這個文件。
2.上傳 apple-app-site-association 文件
該文件必須存在且為了安全原因可使用?SSL?通過?GET?請求訪問到。你可以打開一個文本編輯器然後寫一個這樣的簡單?JSON?格式:
1
2
3
4
5
6
7
8
9
10
{
??"applinks":?{
????"apps":?[],
????"details":?{
??????"TBEJCS6FFP.com.domain.App":?{
????????"paths":[?"*"?]
??????}
????}
??}
}
根據 paths 鍵設定一個允許的路徑列表(你希望APP 作出反應的路徑), 或只是一個星號如果你想打開 APP 而不管路徑是什麼。
你可能想知道?TBEJCS6FFP.com.domain.App?從何而來, 基本上, 它是加入了你的團隊標識的?bundle id。你可以從你的 [蘋果開發賬號頁面]獲取你的團隊標識:
45.png
這個頁面有你的團隊標識, 你可以拷貝粘貼到 apple-app-site-association 文件
Bundle id 可以在項目的 target -- General 中找到:
1.png
檢查 General 標籤並拷貝粘貼 bundle id 到 apple-app-site-association 文件
最後, 上傳這個文件到你的域名根目錄。如果你打開?https://domain.com/apple-app-site-association?可以看到你的文件, 那麼你就可以繼續下一步了。
3.在 APP 裡處理通用鏈接
為了在 APP 裡支持通用鏈接, 你需要在 AppDelegate 裡實現 [application(_:continueUserActivity:restorationHandler:)]。 儘管這種方法可以用於許多不同的目的(比如 [Handoff]和 [搜索 API]), 我們將只關注如何處理接收到的通用鏈接。
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
import?UIKit
 
extension?AppDelegate?{
????func?application(application:?UIApplication,?continueUserActivity?userActivity:?NSUserActivity,?restorationHandler:?([AnyObject]?)?->?Void)?->?Bool?{
????????if?userActivity.activityType?==?NSUserActivityTypeBrowsingWeb?{
????????????let?webpageURL?=?userActivity.webpageURL!?//?Always?exists
????????????if?!handleUniversalLink(URL:?webpageURL)?{
????????????????UIApplication.sharedApplication().openURL(webpageURL)
????????????}
????????}
????????return?true
????}
????
????private?func?handleUniversalLink(URL?url:?NSURL)?->?Bool?{
????????if?let?components?=?NSURLComponents(URL:?url,?resolvingAgainstBaseURL:?true),?let?host?=?components.host,?let?pathComponents?=?components.path?.pathComponents?{
????????????switch?host?{
????????????case?"domain.com":
????????????????if?pathComponents.count?>=?4?{
????????????????????switch?(pathComponents[0],?pathComponents[1],?pathComponents[2],?pathComponents[3])?{
????????????????????case?("/",?"path",?"to",?let?something):
????????????????????????if?validateSomething(something)?{
????????????????????????????presentSomethingViewController(something)
????????????????????????????return?true
????????????????????????}
????????????????????default:
????????????????????????return?false
????????????????????}
????????????????}
????????????default:
????????????????return?false
????????????}
????????????
????????}
????????return?false
????}
}
如果提供的 userActivity 是 NSUserActivityTypeBrowsingWeb 類型, 那麼意味著它已經由通用鏈接 API 代理。這樣的話, 它保證用戶打開的 URL 將有一個非空的 webpageURL 屬性。依據前面的例子, 這將是?http://domain.com/path/to/thezoo?的體現。
為了確保你的 APP 可以翻譯 URL 成實際的內容, 你需要做下面幾步:
  • 使用 [NSURLComponents]簡單解析 webpageURL 到 host(如domain。com), 路徑組成同理(如 ["/"]、"path"、"to"以及"thezoo")。
  • 確保能識別 host。
  • 嘗試將 pathComponents 匹配到 APP 的已知內容裡。
  • 驗證該內容實際上可以被呈現。
  • 呈現內容給用戶。
如果上述步驟有任何一個失敗, Apple 建議你的 APP 應該在 Safari 上打開 webpageURL 以顯示錯誤。
總結如圖:
15.png
上圖是通用鏈接的工作流程的概要
通用鏈接的缺點
通用鏈接對於開發人員來說是一個好主意, 但也有一些缺點可能會導致不受青睞。
通用鏈接只適用於 iOS 9 +
配置 APP 支持通用鏈接意味著只有運行 iOS 9 的用戶才能享用這一技術優勢。 而之前版本的用戶將不能在點擊網頁鏈接時打開 APP。 相反的, 他們將返回到瀏覽器和網頁, 就像之前正常的網頁鏈接一樣。
然後, HOKO為 iOS 5 及更高版本的用戶提供了移動深層鏈接。 因此, 你的移動深層鏈接將可以運行在幾乎所有的 iOS 設備上, 不管它們是不是 iOS 9。
通用鏈接總是會返回到先前創建的網頁
如果你想退回主頁或與 APP 完全不不相關的網站?實現這一目標需要一些額外的工作, 要配置一個網頁可以指引用戶到你的目標頁面。 此外, 如果你沒有一個網站的話, 這將會是一個不可能的解決方案。
你可以使用 HOKO 智能鏈接和他們自適應的返回來輕鬆的解決這個問題。 對於創建的每個智能鏈接, 你可以在每個平台上選擇如果沒有安裝 APP 會發生什麼。 另外你可以設置返回到你的網站, iTunes 商店頁面或其它外部網站。
使用通用鏈接, 開發者必須部署一個網站來關聯 APP
這對於小型開發者來說可能是個壞消息, 他們可能買不起或維護一個網站, 但是仍然想通過網頁鏈接進入他們的 APP。?
HOKO 可以解決這個問題因為它來充當開發者的網站, 每個 APP 託管在不同的子域。 因此, 開發者只需創建智能鏈接, 並發布其網址, 然後每次都會無縫的打開相應的 APP。
APP 與網站之間的聯繫是由創建和託管在開發者網站上的配置文件搞定的
使用 HOKO, 你可以跳過這些繁的配置, 因為我們使它可以開箱即用。 此外, 我們的服務器運行在有行業頂級標準的安全性和性能上, 為每個設備提供安全和快速的方式。
如果你想瞭解更多關於 HOKO 的信息, 可以給我們發郵件或直接註冊
(或直接檢查我們關於Google's App Links的詳細指南)

關於 AppLinks 兩三事

文章來源:https://blog.patw.me/archives/1080/something-about-app-links/


前言

在行動裝置時代,使用者更常停留在 App 中瀏覽資訊(像是 Facebook),而相對地在 Web 的停留時間就降低了。過去我們在 Web 上透過超連結搭起不同網站之間的橋,而在 App 與 App 之間又如何做到這件事呢?
當然,我們可以透過 URL Scheme 開啟應用程式,但我們並無法得知世上每個 App 的 URL Scheme,甚至是應用程式某個頁面 Deep Link 的 Scheme,而且萬一哪天增修了那又該如何?
我最近一部分的工作就在研究如何加強 App 於 Mobile Web 上的連結體驗,其實各大廠早提出了自己的構想及標準,例如 Google 提出的 App Indexing,以及 Facebook 提出的 App Links。
今天就 App Links 的部份做個簡單筆記與實作參考。App Indexing 等下次有空再寫文介紹囉。

App Links

為了要解決 App 跟 App 之間相互跨越連結的問題,Facebook 在 2014 年 4 月的 f8 開發者大會上提出了 App Links 的標準。
按照這個標準實作的 App,就可以透過 AppLinks 標準設定的參考,達到 App 之間的跳轉應用。

使用情境

上面的介紹看完可能還是霧煞煞,什麼是 App 之間的跳轉呢?
假設我今天在 Facebook 上看到一則分享連結,這是 小P 購物(我亂取的 XD)的網站、A 商品的頁面,而 小P 購物也有自己的 Android/iOS App(剛好我也有裝),要是我點擊那篇分享連結就自動開啟 小P 購物 App,並且是 A 商品的頁面。
這樣就是從 Facebook 的 App 跳轉到 小P 購物的 App,如此的體驗更為無縫且順暢(如果 小P 購物 App 本身設計的很糟那是另一回事了 :P)
過去我們在動態時報上看到分享連結,點擊就僅僅能到網站、或是 Google Play/App Store 的下載頁面,若加上 App Links 的設定,那麼更能將使用者帶入 App 的深層連結中,提昇轉換率(像是促成購物等行為)。

實作

由於我不是 App 工程師,這邊就不介紹 App 端的實作了,詳情請參考 Facebook 官方文件的介紹
而就 Web 端而言,App Links 的設定其實是放在網頁上的(如果本來就沒網站,只有個 App,那也可以透過 Facebook 提供的 Hosting API 達成),開發者只要把相關的設定(例如 Android、iOS 的 url scheme,以及 package name/App Store ID 之類的設定)加到目標網頁的 <head></head> 區塊中就可以了,不過是幾個 <meta /> 標籤即可完成。
直接來看實例跟實測擷圖(有圖有真相!)。
例如 小P 購物的商品頁上加入了下面這段 HTML:
<!-- Android: 商品 123 的 URL Scheme -->
<meta property="al:android:url" content="partnerapp://product/123">
<!-- Android Package Name,這可以讓使用者在未安裝的時候連結至安裝連結 -->
<meta property="al:android:package" content="partner.ecapp">
<!-- Android: App 名稱,這會出現在使用者未安裝 App,叫你去下載某某 App 的那個文案 -->
<meta property="al:android:app_name" content="Partner Studio">

<!-- iOS: 商品 123 的 URL Scheme -->
<meta property="al:ios:url" content="partnerapp://product/123" />
<!-- iOS: App Store ID,這可以讓使用者在未安裝的時候連結至安裝連結 -->
<meta property="al:ios:app_store_id" content="123456789" />
<!-- iOS: App 名稱,這會出現在使用者未安裝 App,叫你去下載某某 App 的那個文案 -->
<meta property="al:ios:app_name" content="Partner Studio" />

<!-- 要在使用者未安裝 App 的時候自動 fallback 帶使用者到網頁版嗎?預設是 true,若設為 false,則 Facebook 會問使用者是否要下載該 App,或是直接去網頁 -->
<meta property="al:web:should_fallback" content="false" />
塞好之後,可以去 Facebook 的 Debugger 送出目標網址測試看看,若沒有設錯的話應該可以正常看到 App Links 的區塊,就像這樣:
Debugger
最後來看一下使用者操作的擷圖:
Android FB 動態時報
Android 點擊 FB 貼文(未安裝的情況)
iOS FB 動態時報
iOS 點擊 FB 貼文(未安裝的情況)
已安裝 App 的就不擷囉,因為直接是跳轉到 App 裡面了。
好!那麼這就大功告成了!

結語 + 牢騷

雖然行動時代 App 很重要,但使用者的體驗還是第一,個人實在受不了那種從 Facebook 動態時報上點連結過去,還沒看到想看的內容,就跳個插頁式廣告(interstitial ads)叫使用者「下載 App 以瀏覽內容」的作法,這樣跟內容農場必須點讚才讓人看內容有什麼差別!!XD 讓人根本不想點那些連結了,連 Google 的研究報告 也這樣說啊。
我有時候只是想在通勤時看一下內容啊… 為何要逼迫我下載 App 呢?而且這樣網站存在的意義是什麼…只是跳板嗎?好吧,也許 App 下載量是該公司的 KPI 吧…(攤手)