iOSer作為移動開發(fā)者中的一員,不得不說深度鏈接在當(dāng)下這個“流量”時代已經(jīng)成為我們的必修課了,那么什么是深度鏈接呢?簡單的說就是,可以通過一個簡單的“鏈接”,打開App并直接進(jìn)入該App中的內(nèi)容頁。前提是該手機(jī)上已安裝該App,且該App需要支持深度鏈接。例如:在Safari里看到的澎湃新聞App的某一篇新聞 “中國又一新的世界遺產(chǎn)...” 點(diǎn)擊下面滾動Banner上的“打開App”按鈕便可直接進(jìn)入澎湃新聞App(已安裝)中對應(yīng)的新聞頁面:

可見在移動端采用深度鏈接技術(shù),極大的省去了用戶打開App、再搜索內(nèi)容或者點(diǎn)擊某處進(jìn)入指定頁面等繁瑣的操作,直接點(diǎn)擊網(wǎng)頁上的打開按鈕即可一鍵到達(dá)App內(nèi)的指定頁面。
一、iOS上深度鏈接的由來
在介紹深度鏈接是怎么來的之前,有一個基礎(chǔ)概念需要和大家同步一下:
SandBox(沙盒)
SandBox(沙盒)是蘋果官方規(guī)定的iOS系統(tǒng)強(qiáng)制應(yīng)用程序只能夠讀取應(yīng)用程序內(nèi)部數(shù)據(jù),不可以訪問其他應(yīng)用信息數(shù)據(jù)的一種機(jī)制。
- 在iOS系統(tǒng)的設(shè)備中每一個App都有自己的儲存空間;
- App只能訪問自己沙盒目錄下的內(nèi)容,不能訪問其它存儲空間的內(nèi)容;
- 應(yīng)用程序的數(shù)據(jù)請求需要經(jīng)過權(quán)限檢測,檢測不通過則不執(zhí)行;
為什么使用沙盒?
SandBox(沙盒)是安全體系中的一種機(jī)制,從而蘋果公司在設(shè)計(jì)iOS系統(tǒng)時,考慮到應(yīng)用之間的信息安全,對應(yīng)用程序的訪問權(quán)限設(shè)置了限制。
SandBox(沙盒)的弊端
使用沙盒機(jī)制后App之間不能相互訪問進(jìn)行通信,從而使得App成為一個個的信息孤島。(弱小可憐又無助)
如何解決SandBox(沙盒)問題?
不能說蘋果的初衷怎么樣,但是帶來的問題是顯而易見的,那么其實(shí)蘋果早在2010年 iOS 4 的時候就已經(jīng)意識到App信息孤立的問題了,所以推出了 URL Scheme 技術(shù),此技術(shù)使得iOS系統(tǒng)可以通過特定的URL方式傳遞參數(shù)給另外一個App。例如:iOSer://userid=123456&name=sands 。
不得不承認(rèn),這個技術(shù)確實(shí)解決了當(dāng)初比較棘手的問題,但是在日新月異的今天,URL Scheme的詬病也日漸顯著,比如想要實(shí)現(xiàn)兩個App之間的跳轉(zhuǎn)則需要兼并開發(fā),再比如URL Scheme能夠打開App的前提是已經(jīng)安裝了App,如果沒有安裝則一定會報(bào)錯,相信下面的錯誤大家一定都見的不少,更重要的是現(xiàn)在越來越多的瀏覽器已經(jīng)不再支持URL Scheme了,這必然讓我們不得不另辟蹊徑。
相信蘋果也是深刻的意識到了URL Scheme已經(jīng)不再是長久之計(jì)了,所以蘋果在2015年 iOS 9 中隆重推出了 Universal Links(通用鏈接) 。
二、深度鏈接解決的問題
Universal Links(通用連接) 一種能夠通過點(diǎn)擊傳統(tǒng) HTTPS鏈接 來 啟動App 或者 打開對應(yīng)網(wǎng)站 的技術(shù)。
通過唯一的網(wǎng)址, 不需要特別的URI Scheme就可以鏈接一個特定App里面的視圖 。比如:一個App分享內(nèi)容到微信,用戶在微信內(nèi)置瀏覽器中看到H5頁面內(nèi)容,然后用戶點(diǎn)擊觸發(fā)Universal Links鏈接后,即可直接打開App內(nèi)相同的頁面內(nèi)容。(PS: 由于微信 6.5 版本之后做了 屏蔽操作 ,導(dǎo)致無法直接打開App了,但這并不影響系統(tǒng)引導(dǎo)。)
NOTE
Universal links let users open your app when they tap links to your website within WKWebView and UIWebView views and Safari pages, in addition to links that result in a call to openURL:, such as those that occur in Mail, Messages, and other apps.
When a user is browsing your website in Safari and they tap a universal link to a URL in the same domain as the current webpage, iOS respects the user’s most likely intent and opens the link in Safari. If the user taps a universal link to a URL in a different domain, iOS opens the link in your app.
For users who are running versions of iOS earlier than 9.0, tapping a universal link to your website opens the link in Safari.
首先,這是一種標(biāo)準(zhǔn)的HTTPS鏈接,為什么要強(qiáng)調(diào)這一點(diǎn)呢?因?yàn)樗鉀Q了一個很核心的問題,即使你的設(shè)備上沒有安裝App,那么點(diǎn)擊該鏈接也不會出現(xiàn)上面“Safari無法打開該網(wǎng)頁,因?yàn)榫W(wǎng)址無效”的報(bào)錯,它可以當(dāng)作普通網(wǎng)頁正常被訪問,為用戶體驗(yàn)層面的提升提供了更多的可能。
其次,通過 Universal Links 跳轉(zhuǎn)到自己App從而進(jìn)行通訊的方式不需要兩個App之間的兼并開發(fā),別人的App里不需要為打開自己的App做任何配置,只需要自己開發(fā)配置好自己的App和網(wǎng)頁即可,不管自己的網(wǎng)頁是在哪一個App里被打開,網(wǎng)頁和App之間實(shí)現(xiàn)的 Universal Links 都一直有效。
最后,使用這種方式還有幾個細(xì)節(jié)優(yōu)勢點(diǎn),比如省去了URL Scheme跳轉(zhuǎn)App前的系統(tǒng)確認(rèn)提示框,相比之下更直接,另外,App未安裝時點(diǎn)擊之后直接訪問網(wǎng)頁,一方面解決了URL Scheme在瀏覽器層面不可知的成功或者失敗,同時還能夠呈現(xiàn)給用戶網(wǎng)頁內(nèi)容,引導(dǎo)用戶下載或是進(jìn)行網(wǎng)頁操作,不管從哪一層面來說,這都是一種完勝URL Scheme的方式。
三、如何實(shí)現(xiàn)iOS上的深度鏈接
添加 Universal Links 的支持很簡單。你只需要三步即可實(shí)現(xiàn):
- 創(chuàng)建一個
apple-app-site-association文件,文件內(nèi)容是關(guān)于你的App能夠處理的URLs的JSON數(shù)據(jù) - 上傳上面創(chuàng)建好的這個
apple-app-site-association文件到你的支持HTTPS的Web服務(wù)器。你可以將這個文件放到你服務(wù)器的根目錄下或者.well-known的子文件夾里 - 在你的App里配置、處理 Universal Links
創(chuàng)建和上傳 Association 文件
為了在你的網(wǎng)站和App之間創(chuàng)建一個安全的連接,你建立了它們之間的信任關(guān)系。這個信任關(guān)系的建立分兩步:
- 一個是你添加到你的網(wǎng)站的
apple-app-site-association文件 - 另一個是你添加到你的App的
com.apple.developer.associated-domains權(quán)限(這一部分在下面的 [準(zhǔn)備處理 Universal Links 的App] 中介紹)
在您的apple-app-site-association文件中,您可以指定網(wǎng)站中應(yīng)作為通用鏈接處理的路徑以及不應(yīng)作為通用鏈接處理的路徑。保持路徑列表相當(dāng)短,并通過正則通配符的方式來匹配更多的路徑集。如下是一個 apple-app-site-association 文件的示例,該文件標(biāo)識了應(yīng)作為通用鏈接處理的三個路徑。
{
"applinks": {
"apps": [],
"details": [
{
"appID": "9JA89QQLNQ.com.mob.moblink",
"paths": [ "/moblink/news/", "/videos/moblink/2019/*"]
},
{
"appID": "ABCD1234.com.mob.moblink",
"paths": [ "*" ]
}
]
}
}
注意
不要 在 apple-app-site-association 文件名后面添加 .json 后綴 。
- apps:這個key必須存在,并且對應(yīng)value必須是一個空數(shù)組
- details:這個key對應(yīng)的value是一個包含一個或多個字典的數(shù)組,其中每一個字典都對應(yīng)一個網(wǎng)站支持的app,另外,數(shù)組中字典的順序決定了系統(tǒng)查詢匹配的順序,所以你可以指定app只處理你網(wǎng)站的部分路徑
- appID:這個key對應(yīng)的value是Team ID后面拼接上Bundle ID,這個很重要,千萬不能錯
- paths:這個key對應(yīng)的value是指定你的網(wǎng)站中哪些路徑是需要(或者不需要)app處理的字符串?dāng)?shù)組。
不需要匹配的path則在對應(yīng)路徑前面加 NOT 即可,例如:"paths": [ "/moblink/news/", "NOT /videos/moblink/2010/*", "/videos/moblink/201?/*"] 由于系統(tǒng)會順序查找路徑規(guī)則,一旦發(fā)現(xiàn)規(guī)則相匹配時則停止查找,所以你應(yīng)該將優(yōu)先級高的寫在優(yōu)先級低的后面。如果將前面的示例改為: "paths": [ "/moblink/news/", "/videos/moblink/201?/*", "NOT /videos/moblink/2010/*"] 則數(shù)組中最后一條規(guī)則就沒有用了,因?yàn)樗欢〞磺耙粭l規(guī)則攔截掉。
在 apple-app-site-association 文件中有很多種方式來指定網(wǎng)站路徑。你可以:
- 用
*來指定你的整個網(wǎng)頁都支持 - 包含一個特殊的URL,例如
/moblink/news來指定部分鏈接支持 - 在特殊URL后面加
*,例如/videos/moblink/2017/*來指定一組鏈接支持
注意
paths數(shù)組中的路徑字符串是大小寫敏感的,一定要注意區(qū)分大小寫。
創(chuàng)建 apple-app-site-association 文件后,將其上傳到HTTPS Web服務(wù)器的根目錄或 .well-known 子目錄。該文件需要通過HTTPS直接訪問-無需重定向-位于 https://<domain>/apple-app-site-association 或 https://<domain>/.well-known/apple-app-site-association 。接下來,你需要在你的App中處理 Universal Links。
準(zhǔn)備處理 Universal Links 的App
Universal Links使用了兩項(xiàng)技術(shù):第一個是在Web瀏覽器和App之間啟用Handoff的相同機(jī)制,第二個是共享Web憑據(jù)。關(guān)于這兩項(xiàng)技術(shù)可以參考官方 Web Browser–to–Native App Handoff 和 Shared Web Credentials Reference 。當(dāng)用戶點(diǎn)擊一個 Universal Link 時,iOS系統(tǒng)啟動你的App,并且?guī)Я艘粋€你可以查詢到你的App是通過什么方式被啟動的 NSUserActivity 對象參數(shù)到你的App里。
要在你的App里支持 Universal Links,只需要做如下兩步:
- 添加一個指定的你的App支持的域名權(quán)限
- 更新你的
AppDelegate當(dāng)接收到NSUserActivity對象時作出適當(dāng)?shù)捻憫?yīng)
在你App的 com.apple.developer.associated-domains 權(quán)限里,包含了所有你的App想要當(dāng)作universal link處理的域名列表。在Xcode的項(xiàng)目主頁中,選擇 Capabilities tab頁,然后打開 Associated Domains ,以 applinks: 開頭添加上你的App支持的域名,例如:applinks:z.t4m.cn 、 applinks:*.ulml.mob.com
注意:這個域名權(quán)限列表最多添加20到30個。

配置好Associated Domains之后,就要在 AppDelegate.m 中實(shí)現(xiàn) HandOff 的 UIApplicationDelegate 方法以便你的App能夠接收到universal link并進(jìn)行適當(dāng)?shù)奶幚怼?/p>
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
NSLog(@"title: %@", userActivity.title);
// NSUserActivityTypeBrowsingWeb
NSLog(@"activityType: %@", userActivity.activityType);
// The webpage URL property always contains an HTTP or HTTPS URL, and you can use NSURLComponents APIs to manipulate the components of the URL.
NSLog(@"webpageURL: %@", userActivity.webpageURL);
// 根據(jù)webpageURL的路徑、參數(shù)等作出適當(dāng)?shù)奶幚? // <your code here ...>
return YES;
}
到此,iOS上深度鏈接的實(shí)現(xiàn)就完成了。整體來看,實(shí)現(xiàn)iOS上的深度鏈接涉及到前后端的操作和聯(lián)調(diào)會特別多,尤其是HTTPS證書和錯誤處理訪問到網(wǎng)站那一塊,看似簡單,但真正一步一步做起來的時候,大大小小的問題還是不少的,如果對于前后端不是特別熟悉的話可能還是需要一些時間的。這里我建議大家可以選擇第三方深度鏈接服務(wù),比如MobLink、魔窗、LinkedMe等等,相比之下,MobLink全球領(lǐng)先的移動端場景還原解決方案 以免費(fèi)、服務(wù)好和功能而出名,較為推薦。
四、WWDC2019: What's New in Universal Links
時隔多年,在剛結(jié)束沒有多久的 WWDC 2019 中,又再一次提到了Universal Links,雖然 整個session 不長,也就不到20分鐘的時間,但足以體驗(yàn)蘋果一直在做這件事兒。
首先,劃一下重點(diǎn):Universal Links 之前只支持在 iOS 和 tvOS 平臺上,現(xiàn)在全面登陸到 macOS 平臺上了,無論你是用 UIKit 還是 AppKit 開發(fā)的 Mac App 都可以使用Universal Links。
這一次 WWDC2019 Universal Links 主要的變化有兩點(diǎn):
- apple-app-site-association 文件支持精細(xì)化地址匹配寫法
- 全面支持macOS系統(tǒng)
OverView 總覽
Universal Link 的基本運(yùn)作機(jī)制
- 通過在 XCode 的 App 配置中配置了相關(guān)信息以及安全域名指定
- 通過在 Https only 的安全域名上部署一個配置 apple-app-site-association 文件
- apple-app-site-association 文件中配置上豐富的 website 與 app 的鏈接信息
- 在 website 與 app 之間建立起了安全有效的握手機(jī)制
- 實(shí)現(xiàn) website 的 url 與 app 的直接聯(lián)動
- 相對于自定義的 schemes 來說 Universal Link 能帶給你更安全更流暢的體驗(yàn)
補(bǔ)充:schemes 上最令人詬病的就是 app 劫持,很多 app 會冒充注冊知名大廠的 app scheme 從而攔截調(diào)常規(guī)的大廠 scheme 喚起,這一點(diǎn)在 Universal Link 就能很好的避免。
apple-app-site-association 文件精細(xì)化匹配

上圖是 MobLink 這款提供深度鏈接功能的SDK的apple-app-site-association文件老的寫法示例。

這是新的寫法,支持了更多更強(qiáng)的配置能力。
- apps 這個字段只有在 iOS 上有用,tvOS/macOS 這個字段可以忽略
- details 字段結(jié)構(gòu)大幅度變化
- 以前是字典 appID 為 Key,現(xiàn)在是數(shù)組,并且支持 appIDs 這樣的 key,可以一套配置適配多個 appID,大幅度減少工作量
- 新增 components 字段,可以進(jìn)一步約束 Universal Link 的生效條件
- 可以通過 / 來配置支持的 path 格式條件
- 可以通過 # 來配置支持的錨點(diǎn)條件
- 可以通過 ? 來配置支持的字段條件
- exclude 是排除字段,符合這個條件的 Universal Link 不生效
Universal Links 網(wǎng)絡(luò)服務(wù)的配置建議
在部署網(wǎng)絡(luò)服務(wù),配置 apple-app-site-association 文件的時候,蘋果建議還要遵循一下幾點(diǎn) Tips
必須支持 https:
必須是正規(guī)證書,不能使用自定義證書。URL 使用 ASCII 編碼
因?yàn)樵?URL 中會使用很多符號諸如 # ?% ,因此要使用 ASCII 編碼。減少 apple-app-site-association 文件的大小
減少大小有助于在擁堵的網(wǎng)絡(luò)條件下,更順暢的生效 Universal Link。如果你的應(yīng)用面向不通的國家,針對不同的國家有不同的配置文件,可以通過國家標(biāo)記符來分別配置,從而讓用戶只下載最精簡的 JSON 文件。apple-app-site-association 文件的下載和更新策略:
當(dāng) App 安裝到設(shè)備的時候,會從配置域名上下載這個JSON文件到本地,從而根據(jù)配置生效相關(guān)的功能。并且設(shè)備上下載的這個JSON文件會不定期的更新(親測過,App 發(fā)布版本更新的時候也會準(zhǔn)確觸發(fā)JSON文件的更新)。使用 Smart Banner
網(wǎng)頁中的 Smart Banner 與 Universal Links 一起配合使用,可以有效的引導(dǎo)未安裝 app 的用戶前往下載,已安裝 app 的用戶就可以順暢的進(jìn)行跳轉(zhuǎn)
Universal Links 支持 macOS
在 XCode 中配置 Universal Links

開發(fā) Universal Links的代碼,支持macOS:
// Configuring Your App
func application(_ application: Application, continue userActivity: NSUserActivity,
restorationHandler: @escaping ([ UserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}
for queryItem in components.queryItems ?? [] {
…
return true
}
在支持 macOS 的情況下,appDelegate 的 Universal Link 的處理也是這個 delegate,區(qū)別只是 UIApplication 變?yōu)?NSApplication。
由于這個 delegate 會由很多其他的快捷方式觸發(fā),不僅僅由 Universal Link 觸發(fā),所以通過 NSUserActivityTypeBrowsingWeb 來判斷來源,剩下的就是標(biāo)準(zhǔn)的 url 處理了。
macOS 下的 Universal Links 工作機(jī)制與 iOS 下存在一定的差異
- 默認(rèn)打開網(wǎng)頁,會提示用戶是否需要打開app
- 遠(yuǎn)程登錄下無法生效
- Appstore 簽名 macApp 可以在下載 App 后立刻生效
- 開發(fā)者簽名的 macApp 必須在用戶運(yùn)行過一次后生效
在 mac app 中打開一個 Universal Link
// UIApplication
UIApplication.shared.open(url, options: [ .universalLinksOnly: true ]) {
…
}
// NSWorkspace
let configuration = NSWorkspace.OpenConfiguration()
configuration.requiresUniversalLinks = true
NSWorkspace.shared.open(url, configuration: configuration) {
…
}