iOSer 必知必会的深度链接技术——WWDC2019更新
iOSer作为移动开发者中的一员,不得不说深度链接在当下这个“流量”时代已经成为我们的必修课了,那么什么是深度链接呢?简单的说就是,可以通过一个简单的“链接”,打开App并直接进入该App中的内容页。前提是该手机上已安装该App,且该App需要支持深度链接。例如:在Safari里看到的澎湃新闻App的某一篇新闻 “中国又一新的世界遗产...” 点击下面滚动Banner上的“打开App”按钮便可直接进入澎湃新闻App(已安装)中对应的新闻页面:
可见在移动端采用深度链接技术,极大的省去了用户打开App、再搜索内容或者点击某处进入指定页面等繁琐的操作,直接点击网页上的打开按钮即可一键到达App内的指定页面。
一、iOS上深度链接的由来
在介绍深度链接是怎么来的之前,有一个基础概念需要和大家同步一下:
SandBox(沙盒)
SandBox(沙盒)是苹果官方规定的iOS系统强制应用程序只能够读取应用程序内部数据,不可以访问其他应用信息数据的一种机制。
- 在iOS系统的设备中每一个App都有自己的储存空间;
- App只能访问自己沙盒目录下的内容,不能访问其它存储空间的内容;
- 应用程序的数据请求需要经过权限检测,检测不通过则不执行;
为什么使用沙盒?
SandBox(沙盒)是安全体系中的一种机制,从而苹果公司在设计iOS系统时,考虑到应用之间的信息安全,对应用程序的访问权限设置了限制。
SandBox(沙盒)的弊端
使用沙盒机制后App之间不能相互访问进行通信,从而使得App成为一个个的信息孤岛。(弱小可怜又无助)
如何解决SandBox(沙盒)问题?
不能说苹果的初衷怎么样,但是带来的问题是显而易见的,那么其实苹果早在2010年 iOS 4 的时候就已经意识到App信息孤立的问题了,所以推出了 URL Scheme 技术,此技术使得iOS系统可以通过特定的URL方式传递参数给另外一个App。例如:iOSer://userid=123456&name=sands 。
不得不承认,这个技术确实解决了当初比较棘手的问题,但是在日新月异的今天,URL Scheme的诟病也日渐显著,比如想要实现两个App之间的跳转则需要兼并开发,再比如URL Scheme能够打开App的前提是已经安装了App,如果没有安装则一定会报错,相信下面的错误大家一定都见的不少,更重要的是现在越来越多的浏览器已经不再支持URL Scheme了,这必然让我们不得不另辟蹊径。
相信苹果也是深刻的意识到了URL Scheme已经不再是长久之计了,所以苹果在2015年 iOS 9 中隆重推出了 Universal Links(通用链接) 。
二、深度链接解决的问题
Universal Links(通用连接) 一种能够通过点击传统 HTTPS链接 来 启动App 或者 打开对应网站 的技术。
通过唯一的网址, 不需要特别的URI Scheme就可以链接一个特定App里面的视图 。比如:一个App分享内容到微信,用户在微信内置浏览器中看到H5页面内容,然后用户点击触发Universal Links链接后,即可直接打开App内相同的页面内容。(PS: 由于微信 6.5 版本之后做了 屏蔽操作 ,导致无法直接打开App了,但这并不影响系统引导。)
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.
首先,这是一种标准的HTTPS链接,为什么要强调这一点呢?因为它解决了一个很核心的问题,即使你的设备上没有安装App,那么点击该链接也不会出现上面“Safari无法打开该网页,因为网址无效”的报错,它可以当作普通网页正常被访问,为用户体验层面的提升提供了更多的可能。
其次,通过 Universal Links 跳转到自己App从而进行通讯的方式不需要两个App之间的兼并开发,别人的App里不需要为打开自己的App做任何配置,只需要自己开发配置好自己的App和网页即可,不管自己的网页是在哪一个App里被打开,网页和App之间实现的 Universal Links 都一直有效。
最后,使用这种方式还有几个细节优势点,比如省去了URL Scheme跳转App前的系统确认提示框,相比之下更直接,另外,App未安装时点击之后直接访问网页,一方面解决了URL Scheme在浏览器层面不可知的成功或者失败,同时还能够呈现给用户网页内容,引导用户下载或是进行网页操作,不管从哪一层面来说,这都是一种完胜URL Scheme的方式。
三、如何实现iOS上的深度链接
添加 Universal Links 的支持很简单。你只需要三步即可实现:
- 创建一个 apple-app-site-association 文件,文件内容是关于你的App能够处理的URLs的JSON数据
- 上传上面创建好的这个 apple-app-site-association 文件到你的支持HTTPS的Web服务器。你可以将这个文件放到你服务器的根目录下或者 .well-known 的子文件夹里
- 在你的App里配置、处理 Universal Links
创建和上传 Association 文件
为了在你的网站和App之间创建一个安全的连接,你建立了它们之间的信任关系。这个信任关系的建立分两步:
- 一个是你添加到你的网站的 apple-app-site-association 文件
- 另一个是你添加到你的App的 com.apple.developer.associated-domains 权限(这一部分在下面的 准备处理 Universal Links 的App 中介绍)
在您的apple-app-site-association文件中,您可以指定网站中应作为通用链接处理的路径以及不应作为通用链接处理的路径。保持路径列表相当短,并通过正则通配符的方式来匹配更多的路径集。如下是一个 apple-app-site-association 文件的示例,该文件标识了应作为通用链接处理的三个路径。
{ "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必须存在,并且对应value必须是一个空数组
- details:这个key对应的value是一个包含一个或多个字典的数组,其中每一个字典都对应一个网站支持的app,另外,数组中字典的顺序决定了系统查询匹配的顺序,所以你可以指定app只处理你网站的部分路径
- appID:这个key对应的value是Team ID后面拼接上Bundle ID,这个很重要,千万不能错
- paths:这个key对应的value是指定你的网站中哪些路径是需要(或者不需要)app处理的字符串数组。
不需要匹配的path则在对应路径前面加 NOT 即可,例如:"paths": [ "/moblink/news/", "NOT /videos/moblink/2010/*", "/videos/moblink/201?/*"]由于系统会顺序查找路径规则,一旦发现规则相匹配时则停止查找,所以你应该将优先级高的写在优先级低的后面。如果将前面的示例改为: "paths": [ "/moblink/news/", "/videos/moblink/201?/*", "NOT /videos/moblink/2010/*"]则数组中最后一条规则就没有用了,因为它一定会被前一条规则拦截掉。
在 apple-app-site-association 文件中有很多种方式来指定网站路径。你可以:
- 用 * 来指定你的整个网页都支持
- 包含一个特殊的URL,例如 /moblink/news 来指定部分链接支持
- 在特殊URL后面加 * ,例如 /videos/moblink/2017/* 来指定一组链接支持
注意
paths数组中的路径字符串是大小写敏感的,一定要注意区分大小写。
创建 apple-app-site-association 文件后,将其上传到HTTPS Web服务器的根目录或 .well-known 子目录。该文件需要通过HTTPS直接访问-无需重定向-位于 https://<domain>/apple-app-site-association 或 https://<domain>/.well-known/apple-app-site-association 。接下来,你需要在你的App中处理 Universal Links。
准备处理 Universal Links 的App
Universal Links使用了两项技术:第一个是在Web浏览器和App之间启用Handoff的相同机制,第二个是共享Web凭据。关于这两项技术可以参考官方 Web Browser–to–Native App Handoff 和 Shared Web Credentials Reference 。当用户点击一个 Universal Link 时,iOS系统启动你的App,并且带了一个你可以查询到你的App是通过什么方式被启动的 NSUserActivity 对象参数到你的App里。
要在你的App里支持 Universal Links,只需要做如下两步:
- 添加一个指定的你的App支持的域名权限
- 更新你的 AppDelegate 当接收到 NSUserActivity 对象时作出适当的响应
在你App的 com.apple.developer.associated-domains 权限里,包含了所有你的App想要当作universal link处理的域名列表。在Xcode的项目主页中,选择 Capabilities tab页,然后打开 Associated Domains ,以 applinks: 开头添加上你的App支持的域名,例如:applinks:z.t4m.cn 、 applinks:*.ulml.mob.com
注意:这个域名权限列表最多添加20到30个。
配置好Associated Domains之后,就要在 AppDelegate.m 中实现 HandOff 的UIApplicationDelegate 方法以便你的App能够接收到universal link并进行适当的处理。
- (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);
// 根据webpageURL的路径、参数等作出适当的处理
// <your code here ...>
return YES;
}
到此,iOS上深度链接的实现就完成了。整体来看,实现iOS上的深度链接涉及到前后端的操作和联调会特别多,尤其是HTTPS证书和错误处理访问到网站那一块,看似简单,但真正一步一步做起来的时候,大大小小的问题还是不少的,如果对于前后端不是特别熟悉的话可能还是需要一些时间的。这里我建议大家可以选择第三方深度链接服务,比如MobLink、魔窗、LinkedMe等等,相比之下,MobLink全球领先的移动端场景还原解决方案 以免费、服务好和功能而出名,较为推荐。
四、WWDC2019: What's New in Universal Links
时隔多年,在刚结束没有多久的 WWDC 2019 中,又再一次提到了Universal Links,虽然 整个session不长,也就不到20分钟的时间,但足以体验苹果一直在做这件事儿。
首先,划一下重点:Universal Links 之前只支持在 iOS 和 tvOS 平台上,现在全面登陆到 macOS 平台上了,无论你是用 UIKit 还是 AppKit 开发的 Mac App 都可以使用Universal Links。
这一次 WWDC2019 Universal Links 主要的变化有两点:
- apple-app-site-association 文件支持精细化地址匹配写法
- 全面支持macOS系统
OverView 总览
Universal Link 的基本运作机制
- 通过在 XCode 的 App 配置中配置了相关信息以及安全域名指定
- 通过在 Https only 的安全域名上部署一个配置 apple-app-site-association 文件
- apple-app-site-association 文件中配置上丰富的 website 与 app 的链接信息
- 在 website 与 app 之间建立起了安全有效的握手机制
- 实现 website 的 url 与 app 的直接联动
- 相对于自定义的 schemes 来说 Universal Link 能带给你更安全更流畅的体验
补充:schemes 上最令人诟病的就是 app 劫持,很多 app 会冒充注册知名大厂的 app scheme 从而拦截调常规的大厂 scheme 唤起,这一点在 Universal Link 就能很好的避免。
apple-app-site-association 文件精细化匹配
上图是 MobLink 这款提供深度链接功能的SDK的apple-app-site-association文件老的写法示例。
这是新的写法,支持了更多更强的配置能力。
- apps 这个字段只有在 iOS 上有用,tvOS/macOS 这个字段可以忽略
- details 字段结构大幅度变化
- 以前是字典 appID 为 Key,现在是数组,并且支持 appIDs 这样的 key,可以一套配置适配多个 appID,大幅度减少工作量
- 新增 components 字段,可以进一步约束 Universal Link 的生效条件
- 可以通过 / 来配置支持的 path 格式条件
- 可以通过 # 来配置支持的锚点条件
- 可以通过 ? 来配置支持的字段条件
- exclude 是排除字段,符合这个条件的 Universal Link 不生效
Universal Links 网络服务的配置建议
在部署网络服务,配置 apple-app-site-association 文件的时候,苹果建议还要遵循一下几点 Tips
- 必须支持 https:
必须是正规证书,不能使用自定义证书。
- URL 使用 ASCII 编码
因为在 URL 中会使用很多符号诸如 # ?% ,因此要使用 ASCII 编码。
- 减少 apple-app-site-association 文件的大小
减少大小有助于在拥堵的网络条件下,更顺畅的生效 Universal Link。如果你的应用面向不通的国家,针对不同的国家有不同的配置文件,可以通过国家标记符来分别配置,从而让用户只下载最精简的 JSON 文件。
- apple-app-site-association 文件的下载和更新策略:
当 App 安装到设备的时候,会从配置域名上下载这个JSON文件到本地,从而根据配置生效相关的功能。并且设备上下载的这个JSON文件会不定期的更新(亲测过,App 发布版本更新的时候也会准确触发JSON文件的更新)。
- 使用 Smart Banner
网页中的 Smart Banner 与 Universal Links 一起配合使用,可以有效的引导未安装 app 的用户前往下载,已安装 app 的用户就可以顺畅的进行跳转
Universal Links 支持 macOS
在 XCode 中配置 Universal Links
开发 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,区别只是 UIApplication 变为 NSApplication。
由于这个 delegate 会由很多其他的快捷方式触发,不仅仅由 Universal Link 触发,所以通过 NSUserActivityTypeBrowsingWeb 来判断来源,剩下的就是标准的 url 处理了。
macOS 下的 Universal Links 工作机制与 iOS 下存在一定的差异
- 默认打开网页,会提示用户是否需要打开app
- 远程登录下无法生效
- Appstore 签名 macApp 可以在下载 App 后立刻生效
- 开发者签名的 macApp 必须在用户运行过一次后生效
在 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) { … }