Developer Center


黄建敏(@kuailejim) | Full Stack Developer | Crazy Driver


神奇的Cookie互通魔法

有这么一种业务场景,让PD们很头痛。PD们绞尽乳汁想尽一切办法去引流用户下载自己设计的App,但是却无法统计真正用户的下载量,这样就无法得出准确的转化率。有没有办法,能统计用户通过引导页并且下载完app的真实体量呢?其实在iOS 9之后是可以做到的。

场景分析

用户通过浏览器打开一个H5页面,然后通过此H5页面打开App Store下载链接。这中间涉及到一个黑盒即App Store下载过程是不可见的,开发者完全感知不到。那么我们想想有没有一种方式,能取巧的破解这个难题。

首先从浏览器开始分析,浏览器即WebView。这里会有两种情况:

  • 应用内的UIWebView、WKWebView
  • 应用外的Safari

实际上,虽然很多用户会在应用内的Webview(下面简称WV)打开引导页,但是真正的场景下,例如在流量巨头微信里头,并不会机会给你引流到App Store,会将你拦截并且忽视请求(Deep Link是另一个玩意,这里不做讨论)。

OK,所以我们反观很多引导用户下载的引导页,通常会检测WV的User-Agent,如果判断不是Safari,通常会引导用户到Safari打开这个链接。至此,其实对于需求来说,我们可以先排除应用内的WV,事实上排除这个对接下来的分析很有益处。

技术选型

有了具体的使用场景后,我们就可以分析,并且选出可行性的技术路线了。我们思考一下,其实归根结到,也就是如何将Safari访问过引导页的数据让开发者感知到,然后传输给后台就完成了。

iOS独有的沙盒机制,导致如果想直接从Safari传输数据给App,是不可能的,更何况我们的App根本没下载完。如果是在引导页点击下载完,然后下载完App再跳回H5,接下来在H5再打开App确实是可以满足统计的。但是这么麻烦的步骤,有几个用户会遵循,并且不觉得用户体验实在是太low了吗?

OK,回到根本,我们想在用户毫无感知的情况下,仅仅通过引导页打开App Store,并且确认下载动作。

把思路转向Cookie,说到这里,每个应用内的WV之间的Cookie是独立的,不能共享,并且和Safari的Cookie也是独立的。

这里思路卡住了,但是iOS 9有一个新东西:SFSafariViewController,它可以在App内用外部的Safari打开H5,并且与外部Safari共享缓存、Cookie等等。但是它却不能像应用内WV一样取得Cookie等,因为它没有Api给你取。

好了,我们已经找到一条路径,能让Safari与App共享数据,接下来要解决的就是如何让Safari将数据传到App呢。思考一下,可以用scheme的方式唤起App,然后将参数通过URL带过去。至此,技术过程描述结束。

实现细节

1.png

上面是细节流程图,实现上首先在Safari打开引导页时写入一段Cookie,然后在App下载完成后,打开App时通过SFSafariViewController加载引导页,然后通过window.location.href唤起已经打开的App(注意:如果在已经打开的App再通过这种方式唤起,用户将无感知,而开发者能感知到),这样就能在AppDelegate中拿到传进来的URL了。

下面我们来看一下代码,首先是一个H5 Demo:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<html>  
<head>
<script type="text/javascript">
function getCookie()
{
if (document.cookie.length>0)
{
return document.cookie.replace("downloadFlag=", '')
}
}

function setCookie()
{
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days*24*60*60*1000);
document.cookie="downloadFlag=true"+";expires="+exp.toGMTString();
}

function checkCookie()
{
downloadFlag=getCookie()
if (downloadFlag=="true")
{
window.location.href = "testCookie://downloadFlag"
}
else
{
setCookie()
}
}
</script>

<title>
SafariDataToAppDemo
</title>
<meta charset="utf-8">
</head>
<body onLoad="checkCookie()">
<div>
SafariDataToAppDemo
</div>
</body>
</html>

大致解释下这里做了什么,在这个Demo中,在加载的时候判断是否已经存在Cookie,若存在则直接通过window.location.href隐式唤起App,否则写入Cookie。而在Safari中第一次打开,会写入Cookie。

接下来上native代码:

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
29
#import "ViewController.h"
#import <SafariServices/SafariServices.h>

@interface ViewController ()

@property (nonatomic, strong) SFSafariViewController *sfVC;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.sfVC = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:@"http://127.0.0.1/test.html"]];
[self addChildViewController:self.sfVC];
[self.sfVC didMoveToParentViewController:self];
[self.sfVC.view setFrame:CGRectMake(0, 0, 200, 200)];
[self.view addSubview:self.sfVC.view];
}


- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}


@end

这里就很简单,加载一个小到用户看不见的SFSafariViewController,然后偷偷加载H5 Demo,由于共享Cookie的原因,会读取到有Cookie键值对downloadFlag=true,然后就直接window.location.href隐式唤起App了。然后在AppDelegate上传信息给后台吧!

PS:这里必须得让SFSafariViewController在当前Window可见,否则iOS将不会加载请求。

这里还要注意Cookie的失效时间,比如设置10分钟,20分钟(增强准确性,如果设置过长,那很有可能用户通过引导页打开过App Store但是不下载,然后很长一段时间后再下载,也许就是通过另一个渠道下载了)。

总结

市面上没有一个埋点平台能做到iOS下载统计,并且iOS 9之前的系统占有率已经很低了,完全可以试试这种方式,而且这种需求是非常旺盛的。