SDK 无埋点技术在百分点的探索和实践

owqq4199 7年前
   <p>大家好,我分享的主题是《百分点SDK无埋点技术实践》,将介绍百分点由原来的手动写代码的埋点技术如何发展出不需要写代码的无埋点技术。</p>    <p>今天带来SDK无埋点技术,大家可以了解到SDK无埋点技术是怎么实现的,百分点通过之前的埋点技术怎么演化了这种无埋点技术。我分三个部分来讲一下。</p>    <p>第一部分,就是讲一下什么是SDK无埋点技术;</p>    <p>第二部分,SDK无埋点技术如何实现?讲一些技术细节;</p>    <p>第三部分,在实践中遇到的一些坑和解决方法。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/0f7530c1081ce5df87eda1886c6ddaf7.png"></p>    <p>我们看第一部分,这一块介绍一下我们无埋点技术,既然我们要做这个无埋点,肯定要解决一些问题。这个解决什么问题呢?主要三个问题。第一,开发者依赖性,如果做埋点,需要开发者下载第三方SDK,熟悉SDK编写埋点代码。我们目的就是不用开发人员来做这些。通过运维人员也是可以达到这个效果。第二个问题就是解决成本过高,我们可以通过这种可视化操作,把代码和配制进行分离,这样效率更高,降低成本。第三,避免代码写死。这样就是可以动态更新,更加高效。</p>    <p>首先介绍一下埋点,大家知道一个大数据分析系统经过4个阶段,第一个阶段数据采集,把数据采集上来。第二,传输到服务器,第三,进行建模和统计。我们都是做数据清洗,把脏数据清洗掉。第四步进行数据展示。这一块儿,百分点会把这些数据做推荐,还有一些标签和画像。而埋点就发生在这个源头第一阶段。所谓埋点就是通过在代码的关键部位植入统计代码。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/27942fc91046523dad135db944a63f2f.jpg"></p>    <p>这个是埋点代码。很简单,就是有一个评论按纽,在点击的时候,我们拿一些评论的信息,用户ID,还有评论,最后就是SDK通过这一段代码,把这些信息发送到服务端。这个就是埋点代码。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a7b0f9ec0e9cc2baa5f7d546d3ebdc9d.jpg"></p>    <p>我们看一下无埋点,说了这么多,并不是一行代码不用植入。还是需要写一些代码,需要调用SDK初始化代码,我们的目标是什么?少写代码,尽可能通过几行代码,就可以把SDK集成进去,关键业务统计指标都是通过程序来自动收集,通过后台的配制。然后,可以做什么呢?就是热更新。通过这种热更新的方法,直接改后台配制,不需要再一次发布版本。第三,就是可视化操作。可以不依赖开发者,一些实施人员都是通过后台的配制,就达到埋点的配制,还有新增埋点改动都是很方便的实现。最后就是配制和代码,可以很灵活地扩展,动态地更新。</p>    <p>说了这么多,大家肯定想看一下无埋点什么样子?我精心挑选了两张高清无码照给大家看一下。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/14185ecabb7eabdf6e15fc430f225cc9.jpg"> <img src="https://simg.open-open.com/show/fc5b5836ce4867c812cb9527e62184b6.jpg"></p>    <p>简单说一下无埋点的流程。这是无埋点管理端,因为SDK是没有界面可以跟用户交互的。我们采用手机摇一摇来跟服务器建立连接,我们看下一个界面。我们把当前这个APP界面,可以直接投到这个服务端,服务端会把当前可点击这些元素都列出来,并且框起来。然后,这里我当时是对“活动”按钮做了一个埋点的操作。埋点操作完以后,这边有一个生效,生效了以后,其他用户使用的时候,点击活动的时候就会采集这个数据,直接发到服务端。</p>    <p>我们接着看一下,可以做埋点和无埋点的比较。埋点和无埋点,就是相辅相成的,埋点是适用于一些比较复杂的应用场景。比如说我除了想知道这个点击事件,还想多带一些参数。例如加入购物车行为,我还想知道商品价格和数量等信息,这个还是需要通过埋点写代码,把详细的参数加入到代码里面采集这些信息。而无埋点针对一些简单的操作,一个按纽统计点了没有?或者点击多少次?</p>    <p>无埋点整体架构</p>    <p>接下来第二部分,介绍一下实现SDK无埋点整个思路。看一下整体的架构。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ec064f59c2c6d5806555dd6cea4a8ca4.jpg"></p>    <p>整体的架构,后台的管理端,SDK都是属于客户端,有两台服务器,一个配制服务器和一个通信服务器,SDK和配制界面跟这个服务器进行交互,也是把这些生成配制写到配制服务器,SDK真正采集数据的时候,就是把这个数据发送到这个探头服务。最终进入这些大数据的存储Hive里面。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a8a498d1ea4a84d005bc791bb254f17a.png"></p>    <p>APP的界面如何传输到配制界面?我们当时做这一块儿设计的时候,想到两个方案,第一个,通过一些远程桌面的协议来实现。大家之前做过这种应该有了解,我之前也是做过这些东西。第一,比较的复杂,用在这种场景里面不太现实。再就是通过这种远程桌面协议把PC桌面弄到手机上面。反过来,这样做的很少。所以,最终我们是舍弃了这种方案,采用第二种。第二种就是比较的简单了,我对当前的屏幕进行截图,截图完了以后做一些处理。做一些压缩,然后传到服务端,服务端进行展现,就把屏幕截图展现出来。</p>    <p>控件信息获取及ID生成技术</p>    <p>屏幕截图做完了,下一步,需要在管理界面进行配置,对于可点击的组件进行配置,就需要把这些界面框起来。你需要把它展现出来。我们接着看一下,怎么拿到屏幕控件信息。这个是算整个技术里面比较关键的一点。这一块儿主要就是三个问题。第一,就是获取时机。第二,就是拿控件什么信息,什么信息是需要用到的。第三,就是比较关键的,如何生成控件的唯一ID,ID是在程序内部生成。需要保证在不同的手机上面,这些ID是一样的。还要保证每一次启动ID都是不变。</p>    <p>首先是Android:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/cdf376892da2c961cbe36e7e2d5ca294.jpg"> <img src="https://simg.open-open.com/show/f59d4f80707875ef72b8f909fddfa4e7.png"> <img src="https://simg.open-open.com/show/087962eae46219c9e974d1d2f2ea6057.jpg"></p>    <p>这种通用ID规则我们现在基本上解决了90%的问题,还是有一些情况会导致这个ID有一些问题,后面会讲到的。</p>    <p>然后,接下来我们可以看一下iOS,同样的,也是面临三个问题。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/83f5dafa91afbd7fe9c8034258e8a754.jpg"> <img src="https://simg.open-open.com/show/0b384c42ff6973b9fea173f2c8b002e6.jpg"> <img src="https://simg.open-open.com/show/43a36cfe54ef11e92723095fdcf43b92.jpg"></p>    <p>iOS主要是用Runtime技术,通过hook把点击事件系统函数指针替换一下,换成自己的函数。然后,我们先做一些处理,处理完以后,我们再调系统原来函数。这样的话就可以自动捕获,发送到服务端,从而采集到这个数据。</p>    <p><strong>遇到的问题和解决办法</strong></p>    <p>第三部分,讲一下我们在实现无埋点技术的时候,遇到了什么坑?以及采用什么方法来解决这些坑。</p>    <p>长连接断开的问题,我们之前是设计每隔5秒发送一次截图,一般不会产生断开。但是,后面做了一些优化,我们不会每隔5秒就发,这样就会导致这个链路长时间没有数据。然后,我们解决方法也是采取了业界通用的方法,发一个心跳,通过发一个心跳来保证这个链路一直是活着的。</p>    <p>第二个问题就是摇一摇遇到的问题,我们遇到的问题是这样的,你拿着手机摇一摇,什么时候连接成功呢?你得给用户一个反馈,要不用户就是一直摇下去,我们后来想的方法是,摇成功给他一个振动,振动了,用户有反馈,就知道这个连接成功了,不需要摇了。然后,就是带来另外一个问题。有时候运动当中,又会产生一些误操作,就是导致手机会振一下。这个怎么解决呢?我们就想了一下,可以通过给服务端有一个交互,管理界面里面发现有一个请求过来要连了会有一个确认的按钮,用户确认之后,SDK收到确认消息以后,我们再去振动。这样的话,既可以给用户反馈,也不会说在它误操作情况下振动。这样就不会造成误操作,也不会无缘无故地进行振动。</p>    <p>界面传输的优化,因为在整个配制过程当中,传输数据量最大的就是屏幕的截图。怎么把这个优化一下?就可以更好地提升我们的体验度,还有流畅性。图片采用jpeg格式,把图片质量选择0.6,刚好是可以看清楚的。然后,也最大限度地降低了这个图片大小。</p>    <p>然后,整个传输效率,有这样一个问题。我在当前界面,默认就是5秒传一次。我点了一下,我在当前一个列表界面,点了一下切换到详情界面。最坏的情况下就是等5秒,加上网络传输效果,管理界面就看到新的屏幕。这种体验是很差的,因为实时性太差。所以,这一块做了一个优化。尽量让它实时传过去,我们在屏幕切换的时候,主动发一次截图。这样的话,等的时间就是网络传输的时间,不用等5秒固定的时间。这一块用户体验上面就是更流畅一些。</p>    <p>有的时候屏幕界面没有任何的变化的,这个时候每隔5秒传一次没有必要的。所以,我们把屏幕截图做一个md5进行保留,传输时对比md5,当切换到下一个的时候,我们就发新的屏幕截图。这样就会减少很多不必要的屏幕的传输。然后,也会节省很多的流量。</p>    <p>控件ID重复的问题。前面说过我们的ID生成规则可以解决90%的问题,但是,有一些问题还是解决不了。生成ID重复,重复的话就会产生一个什么样的效果和问题?同样两个按纽,一个注册,对于注册定义了一个埋点,就是注册点击,用户实际操作的时候点登陆的时候也是发过来一个注册点击消息。这样就是统计不准,因为这种比较的特殊,我们采用的解决方案,通过服务端发一些特殊配制。把这些配制里面,因为这两个button里的text不一样,一个是注册,一个是登陆。我们把text信息放在ID的这个生成规则里面,最终生成两个不同的ID,也是可以解决这个问题。</p>    <p><strong>SDK技术发展趋势</strong></p>    <p>最后,我想给大家分享一下SDK的技术发展的一些趋势,我觉得主要是三个。</p>    <p>第一,自动化。怎么可以做到用户要集成一个SDK的时候,可以尽量简单地自动化集成,比如说,几行代码可以把SDK集成,如果可以做到更智能一些就更好。用户把SDK发给你,你通过工具来做一些操作。把你SDK植入进去,用户基本上无感应。</p>    <p>第二就是可视化,简单重复操作封装成可视化的,把开发者解放出来,让运维他们做这些操作。开发者可以专心于SDK核心模块优化和功能的拓展。</p>    <p>第三就是动态更新。移动开发一些限制,很难达到动态更新,目前我们主要采用一些技术例如热修复技术,还有一些是用脚本实现逻辑。这个是一个趋势,将来做SDK都是朝着这个目标。</p>    <p>QA环节</p>    <p><strong>Q:Android里你们怎么监控Fragment页面的埋点的,使用了什么技术?</strong></p>    <p>A:Fragment页面现在无法支持这种自动埋点的监控,现在只支持到activity.</p>    <p><strong>Q:Android端如何确定设备的唯一ID?能百分百确定唯一ID么?</strong></p>    <p>A:Android端可以使用mac地址imei等信息来确定设备的唯一ID,不能百分百,因为Android下有山寨机,一般是把设备的几种ID都发往服务端,服务端有个生成唯一ID的规则,这样能保证大部分的情况。</p>    <p><strong>Q:为什么无埋点不能拿到商品价格等更具体的一些数据信息呢?</strong></p>    <p>A:理论上也可以拿到,但是比较复杂,需要服务端跟客户端约定好一些特殊场景,例如告诉sdk,商品价格的控件类名,是什么类型,然后sdk根据这些配置信息再去动态获取。这种可变性太大,不具有通用性。</p>    <p><strong>Q:请问一下截图的时候,iOS是在hook到的方法里进行截图么,截图完毕后就发送到服务器?</strong></p>    <p>A:对,是在hook的函数里做截图,以及遍历当前可点击控件的信息一起发送给服务器。</p>    <p><strong>Q:iOS怎么确定唯一设备码?</strong></p>    <p>A:iOS 8以后主流做法都是生成一个GUID,然后存储到KeyChain里,这样只要不刷机或者恢复出厂设置就能存活很长一段时间,7的时候可以用序列号,6的时候用mac地址,再往前用UDID.</p>    <p>Q:现在已经开始流行无埋点技术了?你们这个技术对性能有没有什么影响?还有你们上报一次截图流量耗费多少?</p>    <p>A:无埋点算是一种尝试吧可以解决一些应用场景,对性能基本没影响,因为最终是生成的一些配置,sdk把这些配置转化为逻辑代码;上报一次截图流量不是很大,而且上报截图的操作都是建议在WiFi下进行的,而且这个配置只是开发人员做一次,其他安装app的用户不需要。</p>    <p><strong>Q:既然是无埋点,我们的Android SDK怎么做到去获取到各个控件view的呢?</strong></p>    <p>A:会拦截所有activity的onresume函数,然后在这里面去遍历当前的view tree,找到感兴趣的控件。</p>    <p><strong>Q:iOS可以通过读plist文件达到同样的效果,而且带参数的也同样支持,不用手动写代码打点,实现起来也简单很多,为何不用这种方式呢?</strong></p>    <p>A:plist你就写死到客户端,这样就无法保证服务端动态修改了。</p>    <p><strong>Q:感觉是类似appium的方式,获取页面元素位置信息,但是这个实时截图就算压缩了也要比纯文本的埋点信息大很多,如果是高频次操作,这里如何能保证埋点发送的准确性?对服务器要求呢?</strong></p>    <p>A:截图这些都是发生在配置过程,配置完了只是生成配置文件下发到所有客户端,所以这块对服务器没什么压力。就跟你sdk启动时请求服务器一段配置一样。</p>    <p><strong>Q:看到你介绍的无埋点是简单的点击等事件,有埋点是一些和上下文环境相关的复杂些的事件收集。你们项目中,无埋点和有埋点的比例大约是多少的呢?</strong></p>    <p>A:这个主要看app的类型和需求了,目前我们对于一些展现性质的简单app,无埋点可以超过埋点,但是对于一些业务复杂的电商类app,埋点的还是占多数。</p>    <p><strong>Q:能监控到PopWindow 和 Dialog 或者动态生成的view控件吗?</strong></p>    <p>A:第一版不可以,后续版本支持了PopWindow和Dialog,动态生成的view得看生成的时机,如果在我们拦截的时机之后估计就拿不到了。</p>    <p><strong>Q: 按理说一个界面上传一次截图和当前可点击控件的信息就可以了,为什么要每5秒发送一次,当前页面又不会变化。还有就是多久拉一次配置信息?</strong></p>    <p>A:这里是为了描述我们开发过程中的优化,最早的时候就是5秒发,后续做了很多优化,这块就是做md5值比对,发现界面不变就不发了。</p>    <p><strong>Q:如果HOOK安全可靠,是否可以通过window去HOOK取subview的frame进行绘制,将数据记录下来之后按照一定格式压缩保存上传至服务器在进行还原是不是更快捷更节省流量?另外一般情况下数据团队只对关心的按钮、用户停留时间比较关心,采用frame+自带信息(手动携带的user-id等)+控件ID 进行聚合在后台分析展开是否可行?</strong></p>    <p>A:这种理论上也可以,但是这种需要考虑的情况比较多,当前可能有多个window,然后当前window的viewcontroller的堆栈结构,每个viewcontroller里的view可能有多层;而且你需要把所有信息传给服务器,然后服务器根据这些信息来绘制,对服务端的逻辑处理要求也高。</p>    <p>所以最终我们选择的是截图,然后用户来标记感兴趣的控件。</p>    <p><br>  </p>    <p>来自:http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=2651112803&idx=1&sn=061e10ff89c15f893511688f9c7864c2&scene=0</p>    <p> </p>