iOS 中的 Deferred Deep Linking(延遲深度連接)

http://www.cocoachina.com/ios/20160105/14871.htmlhtml

 

Deep Linking

其實 deep linking 並非一個新名詞,在 web 開發領域,區別於指向首頁的連接(http://tech.glowing.com/),deep linking 是指向具體內容頁的連接(http://tech.glowing.com/cn/advices-to-junior-developers/)。在移動開發領域,deep linking 則是指 mobile app 在 handle 特定 URI 的時候能夠直接跳轉到對應的內容頁或觸發特定邏輯,而不只僅是啓動 app。好比 dianping://shopinfo?id=1859284,若是你的手機上裝了大衆點評的話點擊這個連接能夠直接跳轉到商鋪頁面。這樣作的好處主要有:ios

  • 在 web 和 app 的切換過程當中保留上下文git

  • App 間帶上下文切換(用於實現 app 間參數的傳遞,如受權協議,分享 API 等)github

  • Web 頁能夠被搜索引擎索引,能夠經過 SEO 增長訪問量從而提升 app 下載量和開啓率web

目前處理 deep linking,主要有兩種方式:服務器

Custom URL Scheme

在 universal links 出現以前的很長一段時間裏,iOS 上主要經過 custom URL scheme 來實現 deep linking,以及 app 間的通訊。cookie

在 info plist 裏設置了自定義 URL後,handle URL 的入口是 app delegate 方法 application:openURL:sourceApplication:annotation:(iOS 9 開始被 deprecate)或 application:openURL:options:(iOS 9 引入,但若是沒有實現這個方法,在 iOS 9 上仍是會向前兼容 call 老方法,因此通常仍是實現老方法)。session

1
2
3
4
5
6
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
         sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
     BOOL handled = NO;
     // code to handle the URL
     return  handled;
}

一個比較完整的 NSURL 能夠包含如下部分:scheme://user:password@host:port/path?query#fragment。但對於 deep linking 來講大部分時候只須要 scheme://host/path?query。有時候會省去 path 部分,把 host 直接做爲 command,如上文提到的點評的 link;也有些 app 會省去 query 部分,用 path 傳參,更接近 RESTful API 的風格。這取決於具體業務邏輯複雜程度以及 handler 的實現方式。有一點須要注意的是,規範的 URL 是 percentage encoded 的,因此取出來的參數須要用 stringByReplacingPercentEscapesUsingEncoding: 或 stringByRemovingPercentEncoding(iOS 7+)方法 decode。反之,拼 URL 的時候應該使用 stringByAddingPercentEscapesUsingEncoding: 或 stringByAddingPercentEncodingWithAllowedCharacters:(iOS 7+)方法 encode。app

在 iOS 7+ 上處理 query 的時候也能夠配合使用 NSURLComponents 類。dom

具體 handle URL 的時候,對於須要處理的業務邏輯較少的 app 來講,能夠簡單地經過字符串比較來區分業務邏輯。對於業務邏輯相對複雜,特別是在跨團隊共同維護 URL handler 的時候,則須要引入 router 來分發請求。關於 router 已經有不少文章涉及,GitHub 上也有不少開源代碼可供參考或使用,好比:

具體選型或本身實現 router 的時候主要考慮一些問題好比:用 code 註冊仍是配置文件;是否須要去中心化;如何傳參;以 view controller 仍是 block (closure) 爲單位來註冊 handler;是否須要像淘寶同樣作 web 版的 failover 等等……這裏再也不展開。

相關文檔:Using URL Schemes to Communicate with Apps

Apple 在 iOS 9 上引入了 universal links,相較 custom URL scheme,universal links 有如下好處:

  • Custom URL scheme 由於是自定義的協議,因此在沒有安裝 app 的狀況下是沒法直接打開的,而 universal links 自己是一個 HTTP/HTTPS 連接,因此有更好的兼容性。

  • 不一樣的 app 是能夠定義相同的 custom URL scheme 的,因此會存在搶佔或衝突的問題,而 universal links 是從 server 查詢由哪一個 app 打開的,因此不存在上述問題。

  • Universal links 支持從其餘 app 的 MKWebView 或 UIWebView 中跳轉到目標 app。

  • Universal links 自己能夠被搜索引擎索引。

Universal links 的具體實現能夠參考官方文檔:Support Universal Links。簡單來講你須要:

  • 添加一個 apple-app-site-association 文件到你的網站來描述 URL 和 app 的關聯。

  • 添加 com.apple.developer.associated-domains entitlement 來指定要從哪些域名查詢 universal links support。

  • 在 app delegate 的 application:continueUserActivity:restorationHandler: 方法中 handle userActivity.webpageURL

處理 URL 自己的方法跟前面處理 custom URL 相似,再也不贅述。

Deferred Deep Linking

顧名思義,deferred deep linking 是指用戶打開一個 web page 的時候並無安裝對應的 app,但願用戶在安裝 app 之後能夠 deep link 到對應內容。這裏有三個須要解決的問題:

  1. 判斷是否已經安裝了 app,若是已經安裝了直接 deep link 到 app,不然跳轉 App Store。

  2. 用戶匹配(user matching),如何把一個 install 對應到某一次 web page view 或者某一次 click。

  3. Deep linking

問題 1

之前在使用 custom URL 的時候通常用相似這樣的一段 JS 處理:

1
2
3
4
window.location =  'lexie://' ;  
setTimeout( function () {  
}, 250);

這是由於在 iOS 9.2 之前,Safari 裏是否用 app 打開 custom URL 的提示是 blocking JS 的,因此若是用戶贊成用 app 打開連接之後就不會跳轉 App Store,反之,用戶選擇取消或者並無安裝 app 的時候,會跳轉 App Store。iOS 9.2 Apple 作了一個更新就是這個提示再也不 block JS,因此不管如何都會跳轉 App Store。所以如今會推薦使用 universal links 來實現這樣的邏輯,對於須要強制安裝 app 後才能瀏覽的內容,能夠提供一個直接跳轉 App Store 的中轉頁面,若是裝了 app,iOS 會自動跳轉到 app 內處理。

問題 2

這曾經是個老大難的問題,受系統所限,在 iOS 上很難追蹤到一個安裝的來源,可是這樣的需求又不少,主要的場景有:

  • 追蹤廣告效果

  • 追蹤用戶推薦/邀請連接

  • 在 app 內保持網頁瀏覽的上下文,如登陸信息,購物車等

對於這個問題,在 iOS 9 之前常見的作法是猜,沒錯,就是用猜的。在訪問特定頁面或點擊特定連接的時候記錄用戶特徵,如 IP,系統版本,手機型號,語言等等。而後在打開 app 的時候發送這些特徵到服務器,查詢一段時間內(如 1 小時內)有能夠匹配的用戶點擊過的連接,而後處理這個連接。這樣作的缺點很明顯,由於是經過特徵模糊匹配的,因此很容易匹配不到或匹配到錯誤的上下文。可是其實大部分第三方服務會從不一樣來源收集更多信息,因此這個準確率其實比想象中高不少,尤爲是在打開了 IDFA 的狀況下。

這個問題卻在 iOS 9 引入 SFSafariViewController 之後獲得了很好的解決,由於 SFSafariViewController和 Safari 的 cookies 是互通的!因此理論上能夠作到 100% 的 match。解決方案也很簡單,本地生成一個 UUID 並經過一個隱藏的 SFSafariViewController 傳回給 server,server 就能夠把這個 UUID 跟以前的 session 對應起來,而後經過通常的 API call 查詢更多跟這個 session 有關的信息。具體的 code 能夠參考 Branch SDK 的實現

問題 3

上個章節已經提到,再也不贅述,只是處理 URL 的入口換成了某個 API 請求的 callback 裏。

Branch SDK

有不少第三方提供了 deep linking 和 deferred deep linking 的服務,好比 AppsFlyer 和 Branch。目前在 Glow 的 app 裏這兩個 SDK 都有用到。

其中 AppsFlyer 的優點在於他們跟不少公司有合做關係,好比 Facebook,因此用於追蹤 Facebook 廣告效果表現較好。另外 AppsFlyer 支持不少第三方服務的 server callback,能夠方便集成不少第三方服務。缺點是 AppsFlyer 按 non-organic install 量收費。並且 AppsFlyer 的 SDK 和 API doc 寫的不是很好,在 track 安裝之後的後續 deep link 的時候感受有不少 bug。

Branch 的優點在於免費,SDK 和 API doc 都寫的比較好,並且有一些特殊的功能好比用戶邀請及獎勵之類的,適合作一些運營活動。另外 Branch 能夠實現一個 link 根據平臺自動跳轉不一樣 Store,甚至能夠在 desktop 上經過短信發送能夠追蹤的連接。缺點是 Branch 運營時間不久,服務穩定性有待驗證,dashboard 的功能也還比較輕量。

總的來講 AppsFlyer 更適合 track 廣告效果,Branch 更適合實現 feature。必須一提的是,由於這兩個服務都是主要面向海外市場的,因此曾經都遇到過國內短暫抽風的現象,因此國內的 app 若是要用的話風險自擔 :) 若是國內有相似的服務的話也歡迎留言補充。

Branch 的集成比較簡單,參見官方文檔。一個須要注意的是,本身實現的時候在 handle URL 或者 user activity 的時候能夠直接處理 URL,可是用 Branch 的時候,第一級的 URL 是 Branch 的 URL,因此要經過 [[Branch getInstance] handleDeepLink:url] 和/或 [[Branch getInstance] continueUserActivity:userActivity] 交由 Branch 處理,而後在 init Branch 時傳入的 block (closure) 中處理各種參數

1
2
3
4
5
6
7
8
[branch initSessionWithLaunchOptions:launchOptions andRegisterDeepLinkHandler:^(NSDictionary *params, NSError *error) {
     if  (!error) {
         // params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
         // params will be empty if no data found
         // ... insert custom logic here ...
         NSLog(@ "params: %@" , params.description);
     }
}];
相關文章
相關標籤/搜索