iOS 唤起 APP: Universal Link(通用链接)

16,461 阅读10分钟

iOS 9 之前,一直使用的是 URL Schemes 技术来从外部对 App 进行跳转,但是 iOS 系统中进行 URL Schemes 跳转的时候如果没有安装 App,会提示 Cannot open Page 的提示,而且当注册有多个 scheme 相同的时候,目前没有办法区分,但是从 iOS 9 起可以使用 Universal Links 技术进行跳转页面,这是一种体验更加完美的解决方案。

直到 2019 年 7 月,Universal Link一直是被封的,市面上你能看到从微信里 H5 直接跳转到 App 的,大多是微信的协议 Api,只开放给“自己人”,比如:京东、得到...

那现在解封了,给了大家一个机会:可以考虑更多场景,将用户从私域流量导向 App,提升微信生态和 App 的联动效应,有利于更好的做服务和转化。

Universal Link

Universal Link 是 Apple 在 iOS 9 推出的一种能够方便的通过传统 HTTPS 链接来启动 APP 的功能。

如果你的应用支持 Universal Link,当用户点击一个链接时可以跳转到你的网站并获得无缝重定向到对应的APP,且不需要通过 Safari 浏览器。如果你的应用不支持的话,则会在 Safari 中打开该链接。

支持Universal Link(通用链接) 先决条件:必须有一个支持HTTPS的域名,并且拥有该域名下上传到根目录的权限(为了上传Apple指定文件)。

这个功能是 iOS 9.0 就推出的通过访问 http 链接去启动 App 的方式,相信你在刷知乎或者百度的时候,浏览器顶部一直会飘着用 App 打开这个,这个东西就是上面说的通用链接。

如下图,我在微信中打开虎嗅的分享链接,在微信的网页中会有一个横幅,点击横幅上的 "Go" 按钮,就会打开虎嗅的 App 并跳转到指定的那篇新闻(App 内部路由根据传参跳转)。

RPReplay_Final1577325901.gif

准备条件

拥有一个网站域名,且这个网站域名支持> https。 如果你没有这个东西,后面这个教程就不用看了,直接去使用 mob 推出的 moblink 吧,可以使用他默认的域名和配置,按他的接入教程就可以了。

Xcode 项目配置

1、打开工程配置中的 Associated Domains

如果用的是 Xcode11,可以在 Signing&Capabilities 中添加 Associated Domains,如果用的 Xcode10,那就在 Capabilities 中打开 Associated Domains。

截屏2019-12-26上午10.23.19.png

2、添加网站域名

网站域名以 applinks: 开头,那这里填写的就是 applinks:www.lazypig.net

3、配置文件

如果你的项目时自动生成签名和配置文件,那么这步可以省略,如果项目不是自动生成的,就需要去苹果开发后台,将对应的 appid 的 Associated Domains 配置打开,然后重新生成一次打包的配置文件。

截屏2019-12-26上午10.26.12.png

Universal Link 的基本运作流程

  • APP 第一次启动 or APP 更新版本后第一次启动

  • APP 向工程里配置的域名发起 Get 请求拉取 apple-app-association Json File

  • APP 将 apple-app-association 注册给系统

  • 由任意 webview 发起跳转的 url,如果命中了 apple-app-association 注册过的通用链接

  • 打开 App,触发 Universal Link delegate

  • 没命中,webview 继续跳转 url

iOS_13_Issue_Universal-e1574352789653.png

在你进行 apple-app-association 以及 App 工程的配置之后,整个 Universal Link 的运作流程完全由系统控制了。

网站配置

1.创建配置文件

新建一个名字为 apple-app-site-association 的纯文本文件,不要有任何后缀,文件内容为:

{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "团队ID.软件BundleID",
                "paths": [ "限制的域名"]
            }
        ]
    }
}

比如你团队 ID 是 6PA8SXXXXX,app 的 bundle id 是 com.hudongdong.blog,只在 访问 www.lazypig.net/app/lazypig… 链接时才显示顶部的用 App 打开,其他网站层次不显示,那么这个文件的内容就是:

{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "6PA8SXXXXX.com.hudongdong.blog",
                "paths": [ "/app/lazypigquick/*"]
            }
        ]
    }
}

如果填了限制的 paths,在其他网站例如微信后台,填写 Universal Links 就是填写 www.lazypig.net/app/lazypig…

如果没有限制 paths,填写的是*,那么前端和后台填写的地址就是 www.lazypig.net/,全站的头部都显示用 App 打开,那么 paths 修改为 "paths": [ "*"] 即可。

apps 参数

apps 这个字段保持为空数组即可。

details 参数

details 是指定哪个页面用哪个 App 打开的数组,如果你有多个路径指定不同的 App,按照 paths 规则添加对应的 appIDpaths 即可。

paths 参数

这个 paths 路径的更多限制规则可以参考下面:

  • 使用*指定整个网站
  • 包含特定的网址(例如/wwdc/news/)以指定特定的链接
  • 附加到特定的网址(例如/videos/wwdc/2015/)以指定网站的一部分
  • 除了用于匹配任何子字符串之外,您还可以?用于匹配任何单个字符。您可以将两个通配符合并在一个路径中,例如/foo//bar/201?/mypage
  • 路径字符串的开头添加 NOT 指定不应作为通用链接处理的区域,例如 "paths": [ "/videos/wwdc/201?/*" , "NOT /videos/wwdc/2010/*"]

然后将这个文件上传到网站根目录,或者在根目录新建一个名字为 .well-known 的子目录,然后把这个文件上传到这个子目录中。

2.网站验证

上传之后,可以访问 search.developer.apple.com/appsearch-v…,苹果专门提供的验证工具,然后将域名网址填进去,例如 https://www.lazypig.net/,然后点击测试。

截屏2019-12-26上午10.39.09.png

下面如果显示的是 Passed,那就证明符合规则,如果有不合规则的可以参考下面的修正。

3.网站不合规则的修正方法

3.1 Title 不符合规则

在首页的 header 标签中增加 <title> 标签,例如:

<head>
    <title>胡东东博客</title>
    ……
</head>

3.2 Description 不符合规则

在首页的 header 标签中增加 description  标签,例如:

<head>
    <meta name="description" content="胡东东博客,分享手游和app开发的日常点滴" />
    ……
</head>

3.3 Image不符合规则

在首页的 header 标签中增加 og:image 标签,例如:

<head>
    <meta property="og:image" content="https://www.baidu.com/img/superlogo_c4d7df0a003d3db9b65e9ef0fe6da1ec.png?where=super" />
    ……
</head>

3.4 Touch Icon不符合规则

在首页的 header 标签中增加 apple-touch-icon 标签,例如:

<head>
    <link rel="apple-touch-icon" href="/static/quick0012.png">
    ……
</head>

3.5 Link to Application Action required

这个基本是因为app还没有上架,上架审核通过之后发布即可。

4.显示验证

可以打一个测试包安装到手机,然后用 safari 浏览器打开指定的网页即可看到飘窗,例如打开 www.lazypig.net/app/lazypig…,就会看到 懒猪时间盒 的打开飘窗。

网页配置

网页中对需要唤起 App 的按钮或者控件添加点击事件,事件的函数代码如下:

function wakeUpApp(){
    if (navigator.userAgent.match(/(iPhone|iPod|iPad);?/i)) {
        var loadDateTime = new Date();
        window.setTimeout(function() {
            var timeOutDateTime = new Date();
            if (timeOutDateTime - loadDateTime < 5000) {
                window.location.href = "http://a.app.qq.com/o/simple.jsp?pkgname=你的应用宝BundleID";
            } else {
                window.close();
            }
        },
            25);
        window.location.href = "https://yourdomainname.com/";          
    } else if (navigator.userAgent.match(/android/i)) {
        var state = null;
        try {
            state = window.open("apps custom url schemes ", '_blank');
        } catch(e) {}
        if (state) {
            window.close();
        } else {
            window.location.href = "要跳转的页面URL";
        }
    }
}

注:代码中 yourdomainname.com 还可以后面添加 /goodid/123456 之类的属性id后缀,用来传递事件类型和 id,方便移动端做更丰富的功能需求,比如通过 goodid 识别要做的下一步动作是打开商品 id 为123456 的商品详情页,等等。

测试一下

配置完成,查看是否能唤起 App,也可以通过如下图中,输入域名,长按来唤起 App 做验证。

网站向 App 传值

在 AppDelegate 中,可以通过回调函数获取网站向 App 传的链接,通过链接做不同的逻辑处理。

Swift 代码实现

//从通用链接进来
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
        if let url = userActivity.webpageURL {
            print("从通用链接进入app",url.absoluteString)
        }
    }
    return true;
}

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是我们希望处理的){
            //进行我们的处理
        }
    }
    return YES;
}

注意点

1.Universal Link 有跨域问题

Universal Link 必须要求跨域,如果不跨域,就不会跳转(iOS 9.2 之后的改动) 假如当前网页的域名是 A,当前网页发起跳转的域名是 B,必须要求 B 和 A 是不同域名才会触发 Universal Link,如果 B 和 A 是相同域名,只会继续在当前 WebView 里面进行跳转,哪怕你的 Universal Link 一切正常,根本不会打开 App。

前端开发经常面临跨域问题,恩 Universal Link 也有跨域问题,但不一样的是,Universal Link 必须要求跨域,如果不跨域,就不行,就失效,就不工作。

iOS 9.2之后的改动,苹果就这么规定这么设计的~

这也是上面拿知乎举例子的时候重点强调的一个问题,知乎为什么使用 oia.zhihu.com 做 Universal Link?

  • 假如当前网页的域名是 A
  • 当前网页发起跳转的域名是 B
  • 必须要求 B 和 A 是不同域名,才会触发Universal Link
  • 如果B 和 A 是相同域名,只会继续在当前WebView里面进行跳转,哪怕你的Universal Link一切正常,根本不会打开App

是不是不太好理解,那直接拿知乎举例子,知乎的一般网页 URL 都是 www.zhihu.com 域名,你在微信朋友圈看到了知乎的问题分享,如果 copy url 你就能看到这样的链接: www.zhihu.com/question/22…

微信里其实是屏蔽 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 生效。

简单一句话

  • 只有当前 webview 的 url 域名,与跳转目标 url 域名不一致时,Universal Link 才生效。

2.Universal Link 请求 apple-app-site-association 时机

当我们的 App 在设备上第一次运行时,如果支持 Associated Domains 功能,那么 iOS 会自动去 GET 定义的 Domain 下的 apple-app-site-association 文件。

iOS 会先请求 domain.com/.well-known… domain.com/apple-app-s… GET 请求,可以直接把 apple-app-site-association放在./well-known 目录下。

服务器上 apple-app-site-association 的更新不会让 iOS 本地的 apple-app-site-association 同步更新,即 iOS 只会在 App 第一次启动时请求一次,以后除非 App 更新或重新安装,否则不会在每次打开时请求 apple-app-site-association。

3.apple-app-association 被覆盖后如何更新

线上已经工作的 Universal Link 功能,突然有一天发现坏了,查了一圈最后查到被覆盖了,那就修复呗,修复倒是没问题,问题在于修复后的 Universal Link,用户必须重新安装一次 App,才能重新生效。

所以关键是需要掌握 apple-app-association 的更新时机,反复重新杀 App 重开完全没用,删了 App 重装确实有用,但不可能让用户这么去做。

stackoverflow.com/questions/3…

这里解释了,每次 App 安装后的第一次 Launch,会拉取 apple-app-association,除此之外在 AppStore 每次 App 的版本更新后的第一次 Launch,也会拉取 apple-app-association。

也就是说,一旦不小心因为意外 apple-app-association,想要挽回又让那部分用户无感,App 再发一个版本就好了。

参考

www.geekhub.cn/a/2629.html