专栏首页iOS面试技术问题iOS Universal link 入门指南
原创

iOS 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.

使用Universal Link(通用链接)可以让用户在Safari浏览器或者其他APP的webview中拉起相应的APP,也可以在APP中使用相应的功能,从而来把用户引流到APP中。

这具体是一种怎样的情景呢?举个例子,你的用户safari里面浏览一个你们公司的网页,而此时用户手机也同时安装有你们公司的App;而Universal Link能够使得用户在打开某个详情页时直接打开你的app并到达app中相应的内容页面,从而实施用户想要的操作(例如查看某条新闻,查看某个商品的明细等等)。比如在Safari浏览器中进入淘宝网页点击打开APP则会使用Universal Link(通用链接)来拉起淘宝APP。

  • 唯一性: 不像自定义的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相互通信。

2. 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](https://jq.qq.com/?_wv=1027&k=lzJejkSl),不管你是大牛还是小白都欢迎入驻

具体步骤如下图:

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文件。

  • 可以使用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    }}

3. 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的东西,那个东西点了后就能打开

4. 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页面

5. 附:打开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>下载页面<a href="javascript:;" onclick="open_iOS_App()">ios 点击链接</a></p>     </body> <a href="" id="aaa"></a> <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>

原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

登录 后参与评论
0 条评论

相关文章

  • 趣谈 iOS Universal Link

    说起 Universal Link(通用链接),对于有过 iOS 开发的同学,一定有用上过。目前在申请微信分享或登陆时,需要配置 Universal Link ...

    37手游iOS技术运营团队
  • iOS9 Universal Link实现

    王大锤
  • iOS防止在WKWebView中打开Universal Link

    在wap中唤起app应用最最广泛的方式并不是Universal Link,而是直接Schema跳转

    公众号iOS逆向
  • iOS Charles 抓包指南 – 从入门到精通

    从 https://www.charlesproxy.com/download/ 下载安装 Charles 最新版。

    全栈程序员站长
  • iOS 编译器__Attribute__的入门指南

    37手游iOS技术运营团队
  • iOS 创建 Universal Links【修订】

    When you support universal links, iOS users can tap a link to your website and g...

    公众号iOS逆向
  • Flutter 插件url_launcher简介

    url_launcher是用于在移动平台中启动URL的Flutter插件,适用于IOS和Android平台。他可以打开网页,发送邮件,还可以拨打电话。

    砸漏
  • WWDC案例解读:大众点评相机直接扫描支付是怎么实现的

    去年12月4日,Apple CEO Tim Cook 和王兴共同出现在上海的一家老字号生煎店“大壶春”,现场用大众点评App体验了iOS 11新功能,包括用地图...

    美团技术团队
  • Universal Link 前端部署采坑记

    前言 文章会适当说一些如何开发iOS上的universal link,但类似的文章太多了一艘一大堆,每篇都介绍的挺清楚,因此也不是重点 本文更加会侧重从前端的...

    顶级程序员
  • 深度链接(deeplink)唤醒直达App指定内页

    深度链接(Deeplink)是泛用性极高的一项通用技术,在我们日常生活中非常容易接触到。比如:

    走在河边的小鹿
  • 【API使用系列】App间跳转专题

    如果一个应用程序支持一些已知类型的URL,您就可以通过对应的URL模式和该程序进行通讯。然而,在大多数情况下,URL只是用于简单地启动一个应用程...

    江中散人_Jun
  • iOS 创建 Universal Links

    When you support universal links, iOS users can tap a link to your website and g...

    公众号iOS逆向
  • JavaScript在微信、微博、QQ、Safari唤起App的解决方案

    前端爱好者的聚集地 背景 最近在做微信、QQ、微博中使用js唤起App,之前也做过类似的功能,不过比较粗糙,考虑的情况不太全,而且那已经是很久之前的事情了,很...

    用户1097444
  • H5网页唤醒App有哪些做法

    在这个流量为王的互联网背景下,移动端的H5页面显然在导流上承担着重要作用,在H5页面上,我们对引流的需求有两种:

    走在河边的小鹿
  • Deeplink技术是什么?

    Deeplink,又叫深度链接技术,是实际中是应用非常广泛的,你一定有见过,我们来看一个例子:

    GA小站
  • 从deep link到信息流广告,魔窗sdk的演变

    从最初的移动端运营活动到深度链接(deep link),再到现在的移动端原生广告,魔窗sdk经历了多个版本的迭代之后,功能逐步完善,开始步入4.x版本的时代。

    fengzhizi715

扫码关注腾讯云开发者

领取腾讯云代金券