http://www.cocoachina.com/ios/20160105/14871.htmlhtml
其实 deep linking 并非一个新名词,在 web 开发领域,区别于指向首页的连接(http://tech.glowing.com/),deep linking 是指向具体内容页的连接(http://tech.glowing.com/cn/advices-to-junior-developers/)。在移动开发领域,deep linking 则是指 mobile app 在 handle 特定 URI 的时候能够直接跳转到对应的内容页或触发特定逻辑,而不只仅是启动 app。好比 dianping://shopinfo?id=1859284,若是你的手机上装了大众点评的话点击这个连接能够直接跳转到商铺页面。这样作的好处主要有:ios
在 web 和 app 的切换过程当中保留上下文git
App 间带上下文切换(用于实现 app 间参数的传递,如受权协议,分享 API 等)github
Web 页能够被搜索引擎索引,能够经过 SEO 增长访问量从而提升 app 下载量和开启率web
目前处理 deep linking,主要有两种方式:服务器
在 universal links 出现以前的很长一段时间里,iOS 上主要经过 custom URL scheme 来实现 deep linking,以及 app 间的通讯。cookie
在 info plist 里设置了自定义 URL后,handle URL 的入口是 app delegate 方法 application:openURL:sourceApplication:annotation:
(iOS 9 开始被 deprecate)或 application:openURL:options:
(iOS 9 引入,但若是没有实现这个方法,在 iOS 9 上仍是会向前兼容 call 老方法,因此通常仍是实现老方法)。session
1
2
3
4
5
6
|
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
BOOL handled = NO;
// code to handle the URL
return
handled;
}
|
一个比较完整的 NSURL
能够包含如下部分:scheme://user:password@host:port/path?query#fragment
。但对于 deep linking 来讲大部分时候只须要 scheme://host/path?query
。有时候会省去 path
部分,把 host
直接做为 command,如上文提到的点评的 link;也有些 app 会省去 query
部分,用 path
传参,更接近 RESTful API 的风格。这取决于具体业务逻辑复杂程度以及 handler 的实现方式。有一点须要注意的是,规范的 URL 是 percentage encoded 的,因此取出来的参数须要用 stringByReplacingPercentEscapesUsingEncoding:
或 stringByRemovingPercentEncoding
(iOS 7+)方法 decode。反之,拼 URL 的时候应该使用 stringByAddingPercentEscapesUsingEncoding:
或 stringByAddingPercentEncodingWithAllowedCharacters:
(iOS 7+)方法 encode。app
在 iOS 7+ 上处理 query
的时候也能够配合使用 NSURLComponents
类。dom
具体 handle URL 的时候,对于须要处理的业务逻辑较少的 app 来讲,能够简单地经过字符串比较来区分业务逻辑。对于业务逻辑相对复杂,特别是在跨团队共同维护 URL handler 的时候,则须要引入 router 来分发请求。关于 router 已经有不少文章涉及,GitHub 上也有不少开源代码可供参考或使用,好比:
具体选型或本身实现 router 的时候主要考虑一些问题好比:用 code 注册仍是配置文件;是否须要去中心化;如何传参;以 view controller 仍是 block (closure) 为单位来注册 handler;是否须要像淘宝同样作 web 版的 failover 等等……这里再也不展开。
相关文档:Using URL Schemes to Communicate with Apps
Apple 在 iOS 9 上引入了 universal links,相较 custom URL scheme,universal links 有如下好处:
Custom URL scheme 由于是自定义的协议,因此在没有安装 app 的状况下是没法直接打开的,而 universal links 自己是一个 HTTP/HTTPS 连接,因此有更好的兼容性。
不一样的 app 是能够定义相同的 custom URL scheme 的,因此会存在抢占或冲突的问题,而 universal links 是从 server 查询由哪一个 app 打开的,因此不存在上述问题。
Universal links 支持从其余 app 的 MKWebView 或 UIWebView 中跳转到目标 app。
Universal links 自己能够被搜索引擎索引。
Universal links 的具体实现能够参考官方文档:Support Universal Links。简单来讲你须要:
添加一个 apple-app-site-association
文件到你的网站来描述 URL 和 app 的关联。
添加 com.apple.developer.associated-domains
entitlement 来指定要从哪些域名查询 universal links support。
在 app delegate 的 application:continueUserActivity:restorationHandler:
方法中 handle userActivity.webpageURL
。
处理 URL 自己的方法跟前面处理 custom URL 相似,再也不赘述。
顾名思义,deferred deep linking 是指用户打开一个 web page 的时候并无安装对应的 app,但愿用户在安装 app 之后能够 deep link 到对应内容。这里有三个须要解决的问题:
判断是否已经安装了 app,若是已经安装了直接 deep link 到 app,不然跳转 App Store。
用户匹配(user matching),如何把一个 install 对应到某一次 web page view 或者某一次 click。
Deep linking
之前在使用 custom URL 的时候通常用相似这样的一段 JS 处理:
1
2
3
4
|
setTimeout(
function
() {
}, 250);
|
这是由于在 iOS 9.2 之前,Safari 里是否用 app 打开 custom URL 的提示是 blocking JS 的,因此若是用户赞成用 app 打开连接之后就不会跳转 App Store,反之,用户选择取消或者并无安装 app 的时候,会跳转 App Store。iOS 9.2 Apple 作了一个更新就是这个提示再也不 block JS,因此不管如何都会跳转 App Store。所以如今会推荐使用 universal links 来实现这样的逻辑,对于须要强制安装 app 后才能浏览的内容,能够提供一个直接跳转 App Store 的中转页面,若是装了 app,iOS 会自动跳转到 app 内处理。
这曾经是个老大难的问题,受系统所限,在 iOS 上很难追踪到一个安装的来源,可是这样的需求又不少,主要的场景有:
追踪广告效果
追踪用户推荐/邀请连接
在 app 内保持网页浏览的上下文,如登陆信息,购物车等
对于这个问题,在 iOS 9 之前常见的作法是猜,没错,就是用猜的。在访问特定页面或点击特定连接的时候记录用户特征,如 IP,系统版本,手机型号,语言等等。而后在打开 app 的时候发送这些特征到服务器,查询一段时间内(如 1 小时内)有能够匹配的用户点击过的连接,而后处理这个连接。这样作的缺点很明显,由于是经过特征模糊匹配的,因此很容易匹配不到或匹配到错误的上下文。可是其实大部分第三方服务会从不一样来源收集更多信息,因此这个准确率其实比想象中高不少,尤为是在打开了 IDFA 的状况下。
这个问题却在 iOS 9 引入 SFSafariViewController
之后获得了很好的解决,由于 SFSafariViewController
和 Safari 的 cookies 是互通的!因此理论上能够作到 100% 的 match。解决方案也很简单,本地生成一个 UUID
并经过一个隐藏的 SFSafariViewController
传回给 server,server 就能够把这个 UUID
跟以前的 session 对应起来,而后经过通常的 API call 查询更多跟这个 session 有关的信息。具体的 code 能够参考 Branch SDK 的实现。
上个章节已经提到,再也不赘述,只是处理 URL 的入口换成了某个 API 请求的 callback 里。
有不少第三方提供了 deep linking 和 deferred deep linking 的服务,好比 AppsFlyer 和 Branch。目前在 Glow 的 app 里这两个 SDK 都有用到。
其中 AppsFlyer 的优点在于他们跟不少公司有合做关系,好比 Facebook,因此用于追踪 Facebook 广告效果表现较好。另外 AppsFlyer 支持不少第三方服务的 server callback,能够方便集成不少第三方服务。缺点是 AppsFlyer 按 non-organic install 量收费。并且 AppsFlyer 的 SDK 和 API doc 写的不是很好,在 track 安装之后的后续 deep link 的时候感受有不少 bug。
Branch 的优点在于免费,SDK 和 API doc 都写的比较好,并且有一些特殊的功能好比用户邀请及奖励之类的,适合作一些运营活动。另外 Branch 能够实现一个 link 根据平台自动跳转不一样 Store,甚至能够在 desktop 上经过短信发送能够追踪的连接。缺点是 Branch 运营时间不久,服务稳定性有待验证,dashboard 的功能也还比较轻量。
总的来讲 AppsFlyer 更适合 track 广告效果,Branch 更适合实现 feature。必须一提的是,由于这两个服务都是主要面向海外市场的,因此曾经都遇到过国内短暂抽风的现象,因此国内的 app 若是要用的话风险自担 :) 若是国内有相似的服务的话也欢迎留言补充。
Branch 的集成比较简单,参见官方文档。一个须要注意的是,本身实现的时候在 handle URL 或者 user activity 的时候能够直接处理 URL,可是用 Branch 的时候,第一级的 URL 是 Branch 的 URL,因此要经过 [[Branch getInstance] handleDeepLink:url]
和/或 [[Branch getInstance] continueUserActivity:userActivity]
交由 Branch 处理,而后在 init Branch 时传入的 block (closure) 中处理各种参数:
1
2
3
4
5
6
7
8
|
[branch initSessionWithLaunchOptions:launchOptions andRegisterDeepLinkHandler:^(NSDictionary *params, NSError *error) {
if
(!error) {
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
// params will be empty if no data found
// ... insert custom logic here ...
NSLog(@
"params: %@"
, params.description);
}
}];
|