iOS Universal Links

背景

iOS 9 以前,从外部启动 App 都是通过 URL Scheme 实现跳转的。这种方式虽然可自定程度很高,能够巧妙地实现很多跳转,但是也存在很多弊端。

在 2015 年苹果 WWDC 上,苹果宣布了 Universal Links 这项新技术,实现了在 iOS 9 及之后的系统上,在只做少量额外开发的条件下,不同 App 之间能够直接、顺畅、无缝衔接的跳转,让用户体验又提升了一个级别!


URL schemes

非常有效,能够让 app 之间彼此交流,传递数据。

缺陷

  • 需要提前加入到白名单,且会询问用户“是否打开‘xxx’应用”。
  • 不会总能映射到正确的 app,两个 app 可能拥有同样的 scheme,而开发者不能明确地表示他们指的是那个 app。
  • app 没有安装的时候不能工作。
  • 不能有效地保护用户隐私。app 需要查明是否已经被安装在设备上,这意味着它可以嗅探出用户是否安装了某些 app,而这本应属于用户的个人信息。

Universal Links 是苹果在 2015 年 WWDC 上推出的一项新功能,官方功能描述如下:

In iOS 9 and later, 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.

即当用户在 WKWebView、UIWebView 或者 Safari 中点击一个链接,如果设备上安装了适配该链接的 app,就可以跳转该 app 对应的页面,否则仍然展示网页 。
由于目前微信内置浏览器不支持 openURL 的方式进行应用间的跳转,不少 app 都是通过接入 Universal Links 实现微信浏览器一键跳转到自己 app 的功能,比如网易新闻,知乎等。

优点

相比于 Custom URL Scheme,官方文档中给出了几个优点:

  • Unique. Unlike custom URL schemes, universal links can’t be claimed by other apps, because they use standard HTTP or HTTPS links to your website.
  • Secure. When users install your app, iOS checks a file that you’ve uploaded to your web server to make sure that your website allows your app to open URLs on its behalf. Only you can create and upload this file, so the association of your website with your app is secure.
  • Flexible. Universal links work even when your app is not installed. When your app isn’t installed, tapping a link to your website opens the content in Safari, as users expect.
  • Simple. One URL works for both your website and your app.
  • Private. Other apps can communicate with your app without needing to know whether your app is installed.

即:

  • 独特性 与自定义的URL链接相比,通用链接不能被其他的应用程序所访问,因为它们使用标准的 HTTP 或 HTTPS 链接到您的网站。
  • 安全性 当用户安装您的应用程序时,iOS 会检查您已上传到 Web 服务器的文件,以确保您的网站允许您的 app 代表其打开 URL (点击一个 URL 链接时,直接打开相应 app)。 只有您可以创建和上传此文件,因此您的网站与您的应用程序的关联是安全的。
  • 灵活性 即使没有安装 app,Universal links 也可以正常工作。 当没有安装 app 时,点击一个指向您网站的链接,会按照用户的期望在 Safari 中打开。
  • 简单性 通用链接同时适用于你的网站和 app。
  • 私有性 其他应用程序可以与您的应用程序通信,而无需知道您的应用程序是否已安装。

缺陷

  • 至少 iOS 9 以上的系统才支持 UL。

UL 开发流程

准备

  • 一个 HTTPS 的后台域名
  • 可以上传一个 JSON 文件到服务器的权限
  • iOS 9 at least
  • Xcode 7 at least

iOS 端

1. 添加 Associated Domain

在开发者网站找到想要开启 UL 的 App ID(Account -> Certificates, Identifiers & Profiles -> App IDs -> YourAppId),点击 Edit 进入编辑页面,找到 Associated Domains 将对应的复选框选中,然后点击 Done 就可以了。

Associated Domains in developer.apple.com

如果有对应的 Provision Profiles,记得点击 Edit,重新 Generate,并 Download 更新到 Xcode 中。

然后,在 Xcode 中,TARGETS -> YourTarget -> Capabilities -> Associated Domains,打开此功能开关。
点击 + 添加需要支持跳转的 domain,需要以 applinks: 为前缀;同时可以添加一些子域名。比如:applinks:www.example.comapplinks:news.example.com 等。

Associated Domains in Xcode

Tips:
做完上面这些步骤,你的 app 将能够从你指定的这些域名请求一个特殊的 JSON 文件:apple-app-site-association。当你 第一次安装,或者版本更新 的时候,设备将从指定的域名请求文件:https://www.example.com/.well-known/apple-app-site-association,如果不存在,则会请求:https://www.example.com/apple-app-site-association

2. 创建 apple-app-site-association 文件

格式及内容如下:

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
{
"applinks" : {
"apps" : [],
"details" : [
//如果有不止一个 app,或者 app 有多种环境(比如:dev,pro),可以在这个数组中添加多个元素。
{
//appID: App Prefix(Team ID) + Bundle ID
"appID" : "ABCDE12345.com.apple.wwdc",
"paths" : [
//路径数组:告知我们 网站的哪部分可以通过 app 支持。
//如果整个网站都可以通过,可以直接使用 `*`。
"*", //全部网站都支持
"/wwdc/news/", //`完全匹配`
"/videos/wwdc/2015/*" // `路径前缀`
]
},
{
"appID" : "HIJKL67890.com.apple.wwdc",
"paths" : [
//"?" 表示替换路径中的一个字符
"/foo/*/bar/201?/mypage",
// 支持使用 `NOT` 关键字,指定不作为通用链接处理的区域
"NOT /path/to/exclude"
]
}
]
}
}

注意:
appID 由开发者账号的 Team ID 和 app 的 Bundle ID 构成。
paths大小写敏感的!其他 URL 组件比如 queryfragment 则不是。

此文件名必须为 apple-app-site-association,不能有后缀。

必须为每一个你的 app 支持的拥有独立内容的 domain 创建单独的 apple-app-site-association 文件,比如 apple.comdeveloper.apple.com,需要单独的 apple-app-site-association 文件,因为这些域名提供不同的服务。

3. iOS 实现代码

AppDelegate.m 中实现如下方法:

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
// https://developer.apple.com/videos/play/wwdc2015/509/
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {

if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {

NSURL *webUrl = userActivity.webpageURL;

if (![self presentUrl:webUrl]) {
// 当跳转到 app 不能跳转到相应的界面时,苹果建议优雅地解决这种错误,
// 即:使用 Safari 打开该链接,避免 app 中可能出现空白界面等异常情况。
[[UIApplication sharedApplication] openURL:webUrl];
}
}
return YES;
}

- (BOOL)presentUrl:(NSURL *)url {
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES];

NSString *host = urlComponents.host;
NSArray *pathComponent = urlComponents.path.pathComponents;

// 此处根据不同的业务需求进行判断
if ([host isEqualToString:@"www.example.com"]) {
if (pathComponent.count > 4) {
//TODO: Do some router here.
return YES;
}
}
return NO;
}

4. 从认可的证书签发机构获取 SSL 证书

5. 签名 JSON 文件

使用上一步的 SSL 证书对 JSON 文件进行签名。需要是用 Mac 终端的 openssl smime 命令。

1
2
3
4
5
6
7
8
openssl smime \
-sign \
-nodetach \
-in "unsigned.json" \ # JSON file 文件名(未签名的 JSON 文件)
-out "apple-app-site-association" \ # 这里的名称必须完全匹配
-outform DER \
-inkey "private-key.pem" \ # 证书生成期间生成的私钥
-signer "certificate.pem" # 证书签发机构提供的证书

iOS 9 Beta 2 版本开始,👉 不再需要此签名步骤 👈,只需要上传未签名过的 apple-app-site-association 文件到 HTTPS 服务器即可,iOS 会处理其余工作。


Server 端

Upload it to the root of your HTTPS web server. The file needs to be accessible via HTTPS—without any redirects—at https:///apple-app-site-association. Next, you need to handle universal links in your app.

apple-app-site-association 文件存放在服务器跟目录,或者根目录的 .well-known 目录下。比如:

1
https://www.example.com/.well-known/apple-app-site-association

建议存放于 .well-known 目录下,以减少访问次数,因为设备会优先请求 .well-known 目录,如果没有,才会去根目录下请求。

注意:
apple-app-site-association 文件的访问必须不支持重定向。
服务器上 apple-app-site-association 的更新不会让 iOS 本地的 apple-app-site-association 同步更新,即 iOS 只会在 App 第一次启动时请求一次,以后除非 App 更新或重新安装否则不会在每次打开时请求 apple-app-site-association


前端

Smart App Banner

介绍

Smart App Banner 是在 Safari 中浮动在页面顶端的一个 Banner,它是一个对网页无侵入的 UI 元素,可以在 Safari 自动呈现。

如果用户未安装 app,点击自动跳转到 App Store 的下载页。如果已安装,则会自动启动相应的 app。

Smart App Banner in zhihu.com

让网页支持 Smart App Banner 的显示

在网站的 head 标签中添加一个 meta 的标签。

1
2
3
<head>
<meta name="apple-itunes-app" content="app-id=123456789, app-argument=https://developer.apple.com/wwdc/schedule, affiliate-data=optionalAffiliateData">
</head>

name="apple-itunes-app" 会告诉 Safari 你想要显示 Smart App Banner
content 包含 app-idapp id 是每个 app 在 iTunesConnect 网站创建之后,获取到的一个 id。此处的功能是:Safari 会通过此 idApp Store 抓取 app 的信息显示在 Smart App Banner 中,包括 app 名称,厂商等。当用户没有安装相应的 app 时,点击之后会根据此 id 跳转到 App Store 进入相应的下载页。
app-argument 属性应该包括当前显示页面的 URL,或者自定义的一些参数等,当用户点击 Banner 打开 app,此参数值会被传递到 iOS 的
-application:continueUserActivity:restorationHandler:
方法中,通过 userActivity.webpageURL 可以获取到。

可以通过扒取别人的网页代码,看看一些其他大公司的例子。
比如:

1
2
3
4
5
6
7
8
//网易新闻
<meta name="apple-itunes-app" content="app-id=425349261, affiliate-data=myAffiliateData, app-argument=newsapp:​/​/​doc/​CHLFGFCB00097U7S?​s=sps_article">

//知乎:
<meta name="apple-itunes-app" content="app-id=432274380, app-argument=zhihu:​/​/​answers/​154659733" data-react-helmet="true">

//多看
<meta name="apple-itunes-app" content="app-id=517850153, app-argument=duokan-reader:​/​/​store/​book/​09d88212e38611e18f0200163e0123ac">

可以关注一下,这些代码中 app-argument 的值的写法。

Smart App Banner 在模拟器中也会出现,但显示的是一个空白的 Banner,app 名称等信息不会显示。

温馨提示:
如果一个网站的域名是被 iOS 开发人员配置在了 Associated Domains 中的,且在该域名上传了 apple-app-site-association 的文件,那么当一台 iOS 设备安装了相应的 app 之后,在 Safari 浏览器打开该域名下的网址,则会自动显示 Smart App Banner,网页不用做任何额外开发,包括添加 meta 标签。但是这种情况有一个 缺点,就是如果没有安装相应的 app,则 Smart App Banner 不会显示。

如果想要 时刻显示 Smart App Banner,则前端开发人员必须手动添加 meta 标签。

当前端开发手动添加了 meata 标签,则在网页中显示的 banner 左边会有一个 X 按钮,允许用户关闭掉这个 banner,而一旦用户关闭了,则在这个网站上就不会再显示了,即便重新加载网页也一样。

目前唯一能够让 Smart App Banner 再次显示的方法就是 还原所有设置 (设置 -> 通用 -> 还原 -> 还原所有设置)。具体请查看 raywenderlich - Smart App Banners TutorialApple - Promoting Apps with Smart App Banners

自定义链接支持 UL 跳转

如果想要在网页中支持自定义 UL 跳转,比如在底部浮动一个 banner ,上面显示 打开 App,在安装了相应的 app 的情况下,如果想要达到点击这个链接自动跳转到该 app 的目的,此时这个 banner 的跳转链接必须是之前 Xcode 中配置的 Associated Domains 中域名下的链接。

* 跨域条件

想要达到自定义跳转的目的,必须满足 跨域 的条件,举例假设当前 Safari 或者微信浏览器中网页链接为 www.baidu.com/XXX/YYY,底部“打开App”按钮对应的链接URL为 www.baidu.com/AAA/BBB,由于两者都在百度的域名下,因此实际不能完成跳转到 app。

参考链接

由于对前端开发不熟,所以没怎么研究,这里提供两个前端使用 UL 的参考链接。

浏览器中唤起native app || 跳转到应用商城下载(一)
浏览器中唤起native app || 跳转到应用商城下载(二) 之 universal links


注意点及总结

注意点

  1. iOS 9.3 之后必须跨域才能支持 UL 跳转。
  2. 服务端必须是 HTTPS。
  3. apple-app-site-association 文件的访问必须不支持重定向。

总结

放一张自己总结的简单的 UL 工作流程图,方便理解跟回顾:

Apple Universal Links Work Flow


补充

域名验证

苹果官方提供了网址验证工具:App Search API Validation Tool
用于验证 UL 的条件有没有满足:

  1. 服务器后台正确上传了 apple-app-site-association 文件。
  2. iOS 端开启且正确配置了 Associated Domains

当验证通过,但是你的 app 还没有发布到 App Store 时显示的结果是:

如果 app 已经发布,且验证通过,则显示的是 PASSED 状态:

第三方支持 UL 跳转的 APP/平台

非完整版 : )

App Name 是否支持 UL 跳转
微博 ✔︎
微信 ✔︎
Safari ✔︎
QQ ✔︎
手机百度 ✔︎
UC 浏览器 ✔︎
支付宝 ✔︎
QQ 浏览器
Chrome 浏览器

其他

当在 Safari 或者第三方分享的平台中点击 UL 链接打开 app 后,会在 app 右上角看到类似 example.com 的箭头指示,如果点击它会跳转到 Safari 打开之前点击的链接,同时系统会认为你选择使用 Safari 打开该域名的链接,而不是 app。那么下次你再点击该链接,它只会在 Safari 里面加载该链接。那么如何再次开启 app 跳转呢?在 Safari 页面中,手指往下拉动显示出 Smart App Banner,点击右侧的 打开 按钮,就会跳转到 app,此时系统就会认为你选择使用 app 跳转,下次再次点击链接时也会直接跳转到 app 了。

-w300 -w300

参考链接