神奇的Cookie互通魔法

aassbab1 8年前
   <p>有这么一种业务场景,让PD们很头痛。PD们绞尽乳汁想尽一切办法去引流用户下载自己设计的App,但是却无法统计真正用户的下载量,这样就无法得出准确的转化率。有没有办法,能统计用户通过引导页并且下载完app的真实体量呢?其实在iOS 9之后是可以做到的。</p>    <h2>场景分析</h2>    <p>用户通过浏览器打开一个H5页面,然后通过此H5页面打开App Store下载链接。这中间涉及到一个黑盒即App Store下载过程是不可见的,开发者完全感知不到。那么我们想想有没有一种方式,能取巧的破解这个难题。</p>    <p>首先从浏览器开始分析,浏览器即WebView。这里会有两种情况:</p>    <ul>     <li>应用内的UIWebView、WKWebView</li>     <li>应用外的Safari</li>    </ul>    <p>实际上,虽然很多用户会在应用内的Webview(下面简称WV)打开引导页,但是真正的场景下,例如在流量巨头微信里头,并不会机会给你引流到App Store,会将你拦截并且忽视请求(Deep Link是另一个玩意,这里不做讨论)。</p>    <p>OK,所以我们反观很多引导用户下载的引导页,通常会检测WV的User-Agent,如果判断不是Safari,通常会引导用户到Safari打开这个链接。至此,其实对于需求来说,我们可以先排除应用内的WV,事实上排除这个对接下来的分析很有益处。</p>    <h2>技术选型</h2>    <p>有了具体的使用场景后,我们就可以分析,并且选出可行性的技术路线了。我们思考一下,其实归根结到,也就是如何将Safari访问过引导页的数据让开发者感知到,然后传输给后台就完成了。</p>    <p>iOS独有的沙盒机制,导致如果想直接从Safari传输数据给App,是不可能的,更何况我们的App根本没下载完。如果是在引导页点击下载完,然后下载完App再跳回H5,接下来在H5再打开App确实是可以满足统计的。但是这么麻烦的步骤,有几个用户会遵循,并且不觉得用户体验实在是太low了吗?</p>    <p>OK,回到根本,我们想在用户毫无感知的情况下,仅仅通过引导页打开App Store,并且确认下载动作。</p>    <p>把思路转向Cookie,说到这里,每个应用内的WV之间的Cookie是独立的,不能共享,并且和Safari的Cookie也是独立的。</p>    <p>这里思路卡住了,但是iOS 9有一个新东西:SFSafariViewController,它可以在App内用外部的Safari打开H5,并且与外部Safari共享缓存、Cookie等等。但是它却不能像应用内WV一样取得Cookie等,因为它没有Api给你取。</p>    <p>好了,我们已经找到一条路径,能让Safari与App共享数据,接下来要解决的就是如何让Safari将数据传到App呢。思考一下,可以用scheme的方式唤起App,然后将参数通过URL带过去。至此,技术过程描述结束。</p>    <h2>实现细节</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d2cc6c3ec6bdae20312a03e8636b33c0.png"></p>    <p>上面是细节流程图,实现上首先在Safari打开引导页时写入一段Cookie,然后在App下载完成后,打开App时通过SFSafariViewController加载引导页,然后通过window.location.href唤起已经打开的App(注意:如果在已经打开的App再通过这种方式唤起,用户将无感知,而开发者能感知到),这样就能在AppDelegate中拿到传进来的URL了。</p>    <p>下面我们来看一下代码,首先是一个H5 Demo:</p>    <pre>  <code class="language-objectivec"><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>  </code></pre>    <p>大致解释下这里做了什么,在这个Demo中,在加载的时候判断是否已经存在Cookie,若存在则直接通过window.location.href隐式唤起App,否则写入Cookie。而在Safari中第一次打开,会写入Cookie。</p>    <p>接下来上native代码:</p>    <pre>  <code class="language-objectivec">#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  </code></pre>    <p>这里就很简单,加载一个小到用户看不见的SFSafariViewController,然后偷偷加载H5 Demo,由于共享Cookie的原因,会读取到有Cookie键值对downloadFlag=true,然后就直接window.location.href隐式唤起App了。然后在AppDelegate上传信息给后台吧!</p>    <p>PS:这里必须得让SFSafariViewController在当前Window可见,否则iOS将不会加载请求。</p>    <p>这里还要注意Cookie的失效时间,比如设置10分钟,20分钟(增强准确性,如果设置过长,那很有可能用户通过引导页打开过App Store但是不下载,然后很长一段时间后再下载,也许就是通过另一个渠道下载了)。</p>    <h2>总结</h2>    <p>市面上没有一个埋点平台能做到iOS下载统计,并且iOS 9之前的系统占有率已经很低了,完全可以试试这种方式,而且这种需求是非常旺盛的。</p>    <p> </p>    <p>来自:http://kuailejim.com/2017/03/06/神奇的Cookie互通魔法/</p>    <p> </p>