Universal Links iOS 开发攻略|设定技巧与本地测试环境搭建
掌握 iOS Universal Links 设定与更新机制,解决 Apple CDN 快取延迟问题,并透过本地 nginx + ngrok 环境快速模拟测试,提升多域名与复杂路径匹配准确度,确保 APP 与网站无缝串接体验。
Click here to view the English version of this article.
點擊這裡查看本文章正體中文版本。
基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。
文章目录
Universal Links 新鲜事
iOS 13, iOS 14 Universal Links 新鲜事&建立本地测试环境
Photo by NASA
前言
对于一个有网站又有 APP 的服务, Universal Links 的功能对于使用者体验来说无比的重要,能达到 Web 与 APP 之间的无缝接轨;但一直以来都只有简单设置,没有太多的著墨;前阵子刚好又遇到花了点时间研究了一下,把一些有趣的事记录下来。
常见考量
经手过的服务,对于实作 Universal Links 的考量都是 APP 上并没有实作完整的网站功能,Universal Links 认的是域名,只要域名匹配到就会开启 APP;关于这个问题可以下 NOT 排除 APP 上没有相应功能的网址,若网站服务网址很极端,那干脆新建一个 subdomain 用来做 Universal Links。
apple-app-site-association 何时更新?
iOS < 14,APP 在第一次安装、更新时会去询问 Universal Links 网站的 apple-app-site-association。
iOS ≥ 14 ,则是由 Apple CDN 做快取定期更新 Universal Links 网站的 apple-app-site-association;APP 在第一次安装、更新时会去跟 Apple CDN 拿取;但这边就会有个问题,Apple CDN 的 apple-app-site-association 可能还是旧的。
关于 Apple CDN 的更新机制,查了一下文件,没有提到;查了下 讨论 ,官方也只回应「会定期更新」细节之后会发布在文件…但至今依然还没看到。
我自己觉得应该最慢 48 小时,就会更新吧。。。所以下次有更改到 apple-app-site-association 的话建议在 APP 上架更新前几天就先改好 apple-app-site-association 上线。
apple-app-site-association Apple CDN 确认:
1
2
Headers: HOST=app-site-association.cdn-apple.com
GET https://app-site-association.cdn-apple.com/a/v1/你的网域
可以取得当前 Apple CDN 上的版本长怎样。(记得加上 Request Header Host=https://app-site-association.cdn-apple.com/
)
iOS ≥ 14 Debug
因前述的 CDN 问题,那我们在开发阶段该如何 debug 呢?
还好这部分苹果有给解决方法,不然没办法即时更新真的要吐血了;我们只需要再 applinks:domain.com
加上 ?mode=developer
即可,另外还有 managed(for 企业内部 APP)
, or developer+managed
模式可设定。
加上 mode=developer 后,APP 在模拟器上每次 Build & Run 时都会直接跟网站拿最新的 app-site-association 来用。
如果要 Build & Run 在实机则要先去「设定」->「开发者」-> 打开「Associated Domains Development」选项即可。
⚠️ 这边有个坑 ,app-site-association 可以放在网站根目录或是
./.well-known
目录下;但在 mode=developer 下他只会问./.well-known/app-site-association
,害我以为怎么没效。
开发测试
如果是 iOS <14 记得有更改过 app-site-association 的话要删掉再重 Build & Run APP 才会去抓最新的回来,iOS ≥ 14 请参考前述方法加上 mode=developer。
app-site-association 内容的修改,好一点的话可以自行修改伺服器上的档;但对于有时候碰不到伺服器端的我们来说,如果要做 universal links 的测试会非常的麻烦,要不停的麻烦后端同事帮忙,变成要很确定 app-site-association 内容后一次上线,一直改来改去会把同事逼疯。
在本地建一个模拟环境
为了解决上述问题,我们可以在本地起一个小服务。
首先在 mac 上安装 nginx:
1
brew install nginx
如果没安装过 brew 可先安装:
1
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
安装完 nginx 后,前往 /usr/local/etc/nginx/
打开编辑 nginx.conf
档案:
1
2
3
4
5
6
7
8
9
10
11
...略
server {
listen 8080;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /Users/zhgchgli/Documents;
index index.html index.htm;
}
...略
大概在第 44 行的位置将 location / 里的 root 换成你想要的目录位置(这边以 Documents 为例)。
listen on 8080 port ,如果没有冲突则不需要修改。
储存修改完后,下指令启动 nginx:
1
nginx
若要停止时,则下:
1
nginx -s stop
停止。
如果有更改 nginx.conf
记得要下:
1
nginx -s reload
重新启用服务。
建立一个 ./.well-known
目录在刚设定的 root
目录内,并将 apple-app-site-association
档案放到 ./.well-known
内。
⚠️
.well-known
建立后若消失,请注意 Mac 要打开「显示隐藏资料夹」功能:
在 terminal 下:
1
defaults write com.apple.finder AppleShowAllFiles TRUE
再下 killall finder 重启所有 finder,即可。
⚠️
apple-app-site-association
看起来没有副档名,但实际还是有 .json 副档名:
在档案上按右键 -> 「取得资讯 Get Info」->「Name & Extension」-> 检查有无副档名&同时可取消勾选「隐藏档案类型 Hide extension」
没问题后,打开浏览器测试以下连结是否正常下载 apple-app-site-association:
1
http://localhost:8080/.well-known/apple-app-site-association
如果能正常下载代表本地环境模拟成功!
如果出现 404/403 错误则请检查 root 目录是否正确、目录/档案是否有放入、apple-app-site-association 是否不小心带了副档名( .json)。
注册&下载 Ngrok
解压缩出 ngrok 执行档
进入 Dashboard 页面 执行 Config 设定
1
./ngrok authtoken 你的TOKEN
设定好之后,下:
1
./ngrok http 8080
因我们的 nginx 在 8080 port。
启动服务。
这时候我们会看到一个服务启动状态视窗,可以从 Forwarding 中取的此次分配到的公开网址。
⚠️ 每次启动分配到的网址都会变,所以仅能作为开发测试使用。
这边以此次分配到的网址
https://ec87f78bec0f.ngrok.io/
为例
回到浏览器改输入 https://ec87f78bec0f.ngrok.io/.well-known/apple-app-site-association
看看能不能正常下载浏览 apple-app-site-association 档案,如果没问题则可继续下一步。
将 ngrok 分配到的网址输入到 Associated Domains applinks: 设定中。
记得带上 ?mode=developer
方便我们测试。
重新 Build & Run APP:
打开浏览器输入相应的 Universal Links 测试网址(EX: https://ec87f78bec0f.ngrok.io/buy/123
)查看效果。
页面出现 404 不要理他,因为我们实际没有那一页;我们只是要测 iOS 对网址匹配的功能符不符合我们预期;如果上方有出现 「Open」代表匹配成功,另外也可以测 NOT 反向的状况。
点击「Open」后开启 APP -> 测试成功!
开发阶段都测试 OK 后,将确认修改过之后的 apple-app-site-association 档案再交给后端上传到伺服器就能确保万无一失啰~
最后记得将 Associated Domains applinks: 改为正试机网址。
另外我们也可以从 ngrok 运行状态视窗中看到每次 APP Build & Run 有没有跟我们要 apple-app-site-association 档案:
Applinks 设定内容
iOS < 13 之前:
设定档较简单,只有以下内容可设定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"applinks": {
"apps": [],
"details": [
{
"appID" : "TeamID.BundleID",
"paths": [
"NOT /help/",
"*"
]
}
]
}
}
将 TeamID.BundleId
换成你的专案设定 (ex: TeamID = ABCD
, BundleID = li.zhgchg.demoapp
=> ABCD.li.zhgchg.demoapp
)。
如果有多个 appID 则要重复加入多组。
paths 部分则为匹配规则,能支援以下几种语法:
*
:匹配 0~多个字元,ex:/home/*
(home/alan…)?
:匹配 1 个字元,ex:201?
(2010~2019)?*
:匹配 1 个~多个字元,ex:/?*
(/test、/home. . )NOT
:反向排除,ex:NOT /help
(any url but /help)
更多玩法组合可自己依照实际情况决定,更多资讯可参考 官方文件 。
- 请注意,他不是 Regex,不支援任何 Regex 写法。
- 旧版不支援 Query (?name=123)、Anchor ( #title)。
- 中文网址须先转成 ASCII 后才能放在 paths 中 (所有url 字元均要是 ASCII)。
iOS ≥ 13 之后:
强化了设定档内容的功能,多增加支援 Query/Anchor、字符集、编码处理。
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": {
"details": [
{
"appIDs": [ "TeamID.BundleID" ],
"components": [
{
"#": "no_universal_links",
"exclude": true,
"comment": "Matches any URL whose fragment equals no_universal_links and instructs the system not to open it as a universal link"
},
{
"/": "/buy/*",
"comment": "Matches any URL whose path starts with /buy/"
},
{
"/": "/help/website/*",
"exclude": true,
"comment": "Matches any URL whose path starts with /help/website/ and instructs the system not to open it as a universal link"
},
{
"/": "/help/*",
"?": { "articleNumber": "????" },
"comment": "Matches any URL whose path starts with /help/ and that has a query item with name 'articleNumber' and a value of exactly 4 characters"
}
]
}
]
}
转贴自官方文件,可以看到格式有所改变。
appIDs
为阵列,可放入多组 appID,这样就不用像以前一样只能整个区块重复输入。
WWDC 有提到与旧版兼容, 当 iOS ≥ 13 有读到新的格式就会忽略旧的 paths 。
匹配规则改放在 components
中;支援 3 种类型:
/
: URL?
:Query,ex: ?name=123&place=tw#
:Anchor,ex: #title
并且可以搭配使用,假设今天 /user/?id=100#detail
才需要跳到 APP 则可写成:
1
2
3
4
5
{
"/": "/user/*",
"?": { "id": "*" },
"#": "detail"
}
其中匹配语法同原本语法,也是支援 *
?
?*
。
新增 comment
注解栏位,可输入注解方便辨识。(但请注意这是公开的,别人也看得到)
反向排除则改为指定 exclude: true
。
新增 caseSensitive
指定功能,可指定匹配规则是否对大小写敏感, 预设:true
,有这需求的话可以少写许多规则。
新增 percentEncoded
前面说到的,旧版需要先将网址转为 ASCII 放到 paths 中(如果是中文字会变得很丑无法辨识);这个参数就是是否要帮我们自动 encode, 预设是 true
。 假设是中文网址就能直接放入了(ex: /客服中心
)。
详细官方文件可 参考此 。
预设字符集:
这算是这次更新蛮重要的功能之一,新增支援字符集。
系统帮我们定义好的字符集:
$(alpha)
:A-Z 和 a-z$(upper)
:A-Z$(lower)
:a-z$(alnum)
:A-Z 和 a-z 和 0–9$(digit)
:0–9$(xdigit)
:十六进制字符,0–9 和 a,b,c,d,e,f,A,B,C,D,E,F$(region)
:ISO 地区编码 isoRegionCodes ,Ex: TW$(lang)
:ISO 语言编码 isoLanguageCodes ,Ex: zh
假设我们的网址有多语系,我想要支援 Universal links 时,可以这样设定:
1
2
3
"components": [
{ "/" : "/$(lang)-$(region)/$(food)/home" }
]
这样不管是 /zh-TW/home
、 /en-US/home
都能支援,非常方便,不用自己写一整排规则!
自订字符集:
除了预设字符集之外,我们也能自订字符集,增加设定档复用、可读性。
在 applinks
中加入 substitutionVariables
即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"applinks": {
"substitutionVariables": {
"food": [ "burrito", "pizza", "sushi", "samosa" ]
},
"details": [{
"appIDs": [ ... ],
"components": [
{ "/" : "/$(food)/" }
]
}]
}
}
范例中自订了一个 food
字符集,并在后续 components
中使用。
以上范例可匹配 /burrito
, /pizza
, /sushi
, /samosa
。
细节可参考 此篇 官方文件。
没有灵感?
如果对设定档内容没有灵感,可偷偷参考其他网站福的内容,只要在服务网站首页网址加上 /app-site-association
或 /.well-known/app-site-association
即可读取他们的设定。
例如: https://www.netflix.com/apple-app-site-association
补充
在有使用 SceneDelegate
的情况下,open universal link 的进入点是在SceneDelegate 中:
1
func scene(_ scene: UIScene, continue userActivity: NSUserActivity)
而非 AppDelegate 的:
1
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool
延伸阅读
参考资料
有任何问题及指教欢迎 与我联络 。
本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。