共计 8227 个字符,预计需要花费 21 分钟才能阅读完成。
1.1 Universal link 是什么
Universal Link 是苹果在 WWDC 上提出的 iOS9 的新个性之一。此个性相似于深层链接,并可能不便地通过关上一个 Https 链接来间接启动您的客户端利用(手机有装置 App)。对比起以往所应用的 URL Scheme, 这种新个性在实现 web-app 的无缝链接时可能提供极佳的用户体验。
当你的利用反对 Universal Link(通用链接),当用户点击一个链接是能够跳转到你的网站并取得无缝重定向到对应的 APP,且不须要通过 Safari 浏览器。如果你的利用不反对的话,则会在 Safari 中关上该链接。在苹果开发者中能够看到对它的介绍是:
Seamlessly link to content inside your app, or on your website in iOS 9 or later. With universal links, you can always give users the most integrated mobile experience, even when your app isn’t installed on their device.
1.2 Universal link 的利用场景
应用 Universal Link(通用链接)能够让用户在 Safari 浏览器或者其余 APP 的 webview 中拉起相应的 APP,也能够在 APP 中应用相应的性能,从而来把用户引流到 APP 中。
这具体是一种怎么的情景呢?举个例子,你的用户 safari 外面浏览一个你们公司的网页,而此时用户手机也同时装置有你们公司的 App;而 Universal Link 可能使得用户在关上某个详情页时间接关上你的 app 并达到 app 中相应的内容页面,从而施行用户想要的操作 (例如查看某条新闻, 查看某个商品的明细等等)。比方在 Safari 浏览器中进入淘宝网页点击关上 APP 则会应用 Universal Link(通用链接) 来拉起淘宝 APP。
1.3 Universal link 跳转的益处
- 唯一性: 不像自定义的 URL Scheme,因为它应用规范的 HTTPS 协定链接到你的 web 站点,所以个别不会被其它的 APP 所申明。另外,URL scheme 因为是自定义的协定,所以在没有装置 app 的状况下是无奈间接关上的 (在 Safari 中还会呈现一个不可关上的弹窗),而 Universal Link(通用链接) 自身是一个 HTTPS 链接,所以有更好的兼容性;
- 平安: 当用户的手机上安装了你的 APP,那么零碎会去你配置的网站下来下载你上传上去的阐明文件(这个阐明文件申明了以后该 HTTPS 链接能够关上那些 APP)。因为只有你本人能力上传文件到你网站的根目录,所以你的网站和你的 APP 之间的关联是平安的;
- 可变: 当用户手机上没有装置你的 APP 的时候,Universal Link(通用链接)也可能工作。如果你违心,在没有装置你的 app 的时候,用户点击链接,会在 safari 中展现你网站的内容;
- 简略: 一个 HTTPS 的链接,能够同时作用于网站和 APP;
- 公有: 其它 APP 能够在不须要晓得你的 APP 是否装置了的状况下和你的 APP 互相通信。
- Universal link 配置和运行
2.1 配置 App ID 反对 Associated Domains
登录 https://developer.apple.com/ 苹果开发者核心,找到对应的 App ID,在 Application Services 列表里有 Associated Domains 一条,把它变为 Enabled 就能够了。
2.2 配置 iOS App 工程
Xcode 11.0 版本
工程配置中相应性能:targets->Signing&Capabilites->Capability->Associated Domains,在其中的 Domains 中填入你想反对的域名,也必须必须以 applinks: 为前缀。563513413,不论你是大牛还是小白都欢送入驻
具体步骤如下图:
Xcode 11.0 以下版本
工程配置中相应性能:targets->Capabilites->Associated Domains,在其中的 Domains 中填入你想反对的域名,必须以 applinks: 为前缀。
配置我的项目中的 Associated Domains:
2.2 配置和上传 apple-app-association
到底哪些的 url 会被辨认为 Universal Link,全看这个 apple-app-association 文件 Apple Document UniversalLinks.html
- 你的域名必须反对 Https
- 域名 根目录 或者
.well-known
目录下放这个文件apple-app-association
,不带任何后缀 - 文件为 json 保留为文本即可
- json 按着官网要求填写即可
apple-app-site-association
模板:
{
“applinks”: {
“apps”: [],
“details”: [
{
“appID”: “9JA89QQLNQ.com.apple.wwdc”,
“paths”: [“/wwdc/news/”, “/videos/wwdc/2015/*”]
},
{
“appID”: “ABCD1234.com.apple.wwdc”,
“paths”: [“*”]
}
]
}
}
阐明:
appID:组成形式是 teamId.yourapp’s bundle identifier。如下面的 9JA89QQLNQ 就是 teamId。登陆开发者核心,在 Account -> Membership 外面能够找到 Team ID。
paths:设定你的 app 反对的门路列表,只有这些指定的门路的链接,能力被 app 所解决。星号的写法代表了可识 别域名下所有链接。
上传指定文件: 上传该文件到你的域名所对应的根目录或者.well-known 目录下,这是为了苹果能获取到你上传的文件。上传完后, 本人先拜访一下, 看看是否可能获取到,当你在浏览器中输出这个文件链接后,应该是间接下载 apple-app-site-association 文件。
2.4 如何验证 Universal link 失效
- 能够应用 iOS 自带的备忘录程序,输出链接,长按链接,如果弹出菜单中有”在‘xxx’中关上”,即示意配置失效。
- 或者将要测试的网址在
Safari
中关上,在呈现的网页上方下滑,能够看到有在”xxx”利用中关上, 呈现菜单:
当点击某个链接,间接能够进咱们的 app 了,然而咱们的目标是要可能获取到用户进来的链接,依据链接来展现给用户相应的内容。
在 AppDelegate
里中实现代理办法,官网链接:Handling Universal Links
Objective-C:
- (BOOL)application:(UIApplication )application continueUserActivity:(NSUserActivity )userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb])
{
NSURL *url = userActivity.webpageURL;
if (url 是咱们心愿解决的)
{
// 进行咱们的解决
}
else
{
[[UIApplication sharedApplication] openURL:url];
}
}
return YES;
}
Swift:
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool
{
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let path = components.path,
let params = components.queryItems else {
return false
}
print(“path = (path)”)
if let albumName = params.first(where: { $0.name == “albumname”} )?.value,
let photoIndex = params.first(where: { $0.name == “index”})?.value {
print(“album = (albumName)”)
print(“photoIndex = (photoIndex)”)
return true
} else {
print(“Either album name or photo index missing”)
return false
}
}
- Universal link 遇到的问题和解决办法
3.1 跨域
前端开发常常面临跨域问题,恩 Universal Link 也有跨域问题,但不一样的是,Universal Link,必须要求跨域,如果不跨域,就不行,就生效,就不工作。(iOS 9.2 之后的改变,苹果就这么规定这么设计的)
这也是下面拿知乎举例子的时候重点强调的一个问题,知乎为什么应用 oia.zhihu.com
做 Universal Link?
- 如果以后网页的域名是 A
- 以后网页发动跳转的域名是 B
- 必须要求 B 和 A 是不同域名,才会触发 Universal Link
- 如果 B 和 A 是雷同域名,只会持续在以后 WebView 外面进行跳转,哪怕你的 Universal Link 一切正常,基本不会关上 App
是不是不太好了解,那间接拿知乎举例子
有心人可能看到,知乎的 Universal Link 配置的是 oia.zhihu.com
这个域名,并且对这个域名下比方 /answers /questions /people 等 urlpath 进行了辨认,也就是说,知乎的 universal link,只有当你拜访 https://oia.zhihu.com/questions/xxxx
,在挪动端会触发 Universal Link,而知乎正经的 Urlhttps//www.zhihu.com/questions/xxx
是不会触发 Universal Link 的,知乎为什么制作,为什么不把他的主域名配置 Universal Link,就是因为 Universal Link 的跨域的起因。
知乎的个别网页 URL 都是 www.zhihu.com
域名,你在微信朋友圈看到了知乎的问题分享,如果 copy url 你就能看到这样的链接
https://www.zhihu.com/question/22914651
微信里其实是屏蔽 Schema 的,然而你仍然能看到大大的一个按钮 App 内关上
,这的确就是通过 Universal Link 来实现的,但如果知乎把 Universal Link 配在了www.zhihu.com
域名,那么即使曾经装置了 App,Universal Link 也是不会失效的。
个别的公司都会有本人的主域名,比方知乎的 www.zhihu.com
,在各处分享流传的时候,也都是间接分享基于主域名的 url,但为了解决苹果强制要求跨域才失效的问题,Universal Link 就不能配置在主域名下,于是知乎才会筹备一个oia.zhihu.com
域名,专为 Universal Link 应用,不会跟任何被动流传分享的域名撞车,从而在任何流动 WAP 页面里,都能顺利让 Universal Link 失效。
跨域的另外一个益处是能够 冲破微信跳转限度,反对微信无缝跳转到 App.
简略一句话
只有以后 webview 的 url 域名,与跳转指标 url 域名不统一时,Universal Link 才失效
3.2 更新
apple-app-association 的更新机会有以下两种:
- 每次 App 装置后的第一次 Launch,会拉取 apple-app-association
- Appstore 每次 App 的版本更新后的第一次 Launch,也会更新 apple-app-association
所以重复从新杀 APP 重开齐全没用,删了 App 重装的确有用,但不可能让用户这么去做。也就是说,一旦不小心因为意外 apple-app-association,想要挽回又让那局部用户无感,App 再发一个版本就好了
3.3 Universal Link 用户行为
Universal Link 触发后关上 App,这时候 App 的状态栏右上角会有文字提醒来自 XXApp,能够点状态栏的文字疾速返回原来的 AP
如果用户点了返回微信,就会被苹果记住,认为用户并不需要跳出原 App 关上新 App,因而这个 App 的 Universal Link 会被敞开,再也有效。
想要开启也不是不行,让用户从新用 safari 关上,universal link 的页面,而后会呈现很像苹果 smart bar 的货色,那个货色点了后就能关上
- H5 端的 Universal Link 业务部署
H5 端的 Universal Link 跳转,从产品经理的角度看,须要满足以下 2 个需要:
- 如果已装置 App,跳转对应界面
- 如果没装置 App,跳转 App 下载界面
H5 端部署 Universal Link 示例:
router.use(‘/view’, function (req, res, next) {
var path = req.path;
res.redirect(‘https://www.xxx.com/view’ + path + ‘?xxx=xxx’);
});
整个成果就是
- 跳转
https://www.xxx.com/view/*
- 已装置 App
- 关上 App 触发 handleUniversalLink
- 走到 /view/ 分支,拼接浏览页路由跳转
- 未装置 AppWebView
- 原地跳转
https://
`www.xxx.com`/view/*
- 命中服务器的重定向逻辑
- 重定向到
https://
`www.xxx.com`/view/*
- 关上相应的 H5 页面
- 附:关上 App 过渡页.html 示例源码
<!DOCTYPE html”>
<html>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<meta name=”viewport” content=”width=device-width,minimum-scale=1.0″>
<body bgcolor = “#FFEEDD”>
<input type=”text” id=”acti”>
<p> 下载页面 ios 点击链接 </p>
</body>
<script type=”text/javascript”>
var timeout;
function open_iOS_App() {
if (isWeiXin()) {
open_App();
}else{
open_App();
timeout = setTimeout(‘open_itunes()’, 3000);
};
}
function open_Android_App() {
if (isWeiXin()) {
open_android_weixin();
}else{
open_App_Android();
};
}
function open_android_weixin() {
var acti = document.getElementById(“acti”).value;
var linkUrl = “xxxxxxxxxxxxx://utils?action=sendIntent¶ms=” + acti;
var yingyongbaoUrl = “http://a.app.qq.com/o/simple?pkgname=com.xxxx.xxxxx&android_schema=”
- encodeURI(linkUrl);
window.location = yingyongbaoUrl;
}
function open_App() {
var ver = (navigator.appVersion).match(/OS (d+)_(d+)_?(d+)?/);
ver = parseInt(ver[1], 10);
if(ver<9)
{
if (isWeiXin()) {
open_weixin_App();
}else{
open_itunes();
}
} else{
var activity = document.getElementById(“acti”).value;
document.getElementById(“aaa”).href = “https://joeychang.me/s.html?params=”+ encodeURI(activity);
// alert(document.getElementById(“aaa”).href);
// alert(activity);
document.getElementById(“aaa”).click();
}
}
function open_App_Android() {
var acti = document.getElementById(“acti”).value;
window.location = “intent://xxxxxxxxx?params=”+ acti +”#Intent;package=com.xxxx.xxxxx;scheme=xxxxxxx;launchFlags=268435456;end;”;
}
function open_itunes() {/ 关上 app store /
window.location=”http://itunes.apple.com/cn/app/idxxxxxxxx”;
}
function open_weixin_App() {/ 关上腾讯利用宝 间接跳转 /
var acti = document.getElementById(“acti”).value;
window.location=”http://a.app.qq.com/o/simple.jsp?pkgname=com.xxxx.xxxxx&activity=” + acti;
}
/*
判断是否是微信浏览器
*/
function isWeiXin(){
var ua = window.navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i) == ‘micromessenger’){
return true;
}else{
return false;
}
}
function linktoApp() {
var queryStr = decodeURI(window.location.search.substr(1));
var ua = navigator.userAgent.toLowerCase();
if (queryStr.indexOf(“}”) >= 0 && queryStr.indexOf(“{“) >= 0) {
var activity = queryStr.substring(queryStr.indexOf(“{“), queryStr.lastIndexOf(“}”) + 1);
document.getElementById(“acti”).value = activity;
if (/iphone|ipad|ipod/.test(ua)) {
// IOS
} else if (/android/.test(ua)) {
// Android
open_Android_App();
} else {
// 其余浏览器
window.location=”http://a.app.qq.com/o/simple.jsp?pkgname=com.xxxxxxx.xxxxxxxx”;
}
} else {
document.getElementById(“acti”).value = “ 参数格局谬误 ”;
}
}
// 用 js 实现在加载实现一个页面后主动执行一个办法
/用 window.onload 调用 myfun()/
window.onload=linktoApp;// 不要括号
</script>
</head>
<body>
</body>
</html>