有货iOS数据非侵入式手动采集探索实践
优采云 发布时间: 2020-08-12 14:37随着有货APP的不断迭*敏*感*词*发,数据和业务部门对于客户端用户行为数据的需求越来越多;为了更好的监控APP使用的状况,客户端团队对于APP自身的运行的数据需求也日渐急迫。迫切地须要一套客户端数据采集的工具,自动、全量采集用户行为数据,满足各个部门对于数据的需求。
\\
有货APP团队因此开发一套数据采集的SDK,主要的功能如下:
\\页面访问流。用户在使用APP期间浏览了什么页面。\\t浏览数据爆光。用户在某个页面上浏览了什么商品。\\t业务数据手动采集。用户在使用APP期间点击了什么位置,触发了什么操作。\\t性能数据手动采集。用户使用APP期间,页面加载时长是多少,图片加载时长多少,网络恳求时长多少等。\
此外,所有的数据采集要自动化,无侵入,即不需要人工埋点,集成SDK即可使用,不改动或尽量少改动原有代码。
\\
基于以上需求,AOP是技术方案的最佳选择,而iOS上实现AOP则须要借助Objective-C中runtime的黑魔法--Method Swizzle实现。漫漫的踩坑填坑的旅程由此开端,接下来我们一一尝尝实现思路和技巧吧。
\\页面访问流\\
用户访问页面统计须要解决的问题有两个:
\\统计风波切入点,即何时统计。\\t统计数据数组,即统计什么数据。\
整体流程如下图:
\\
\\统计风波切入点\\
用户访问页面统计的通常思路是在View Controller生命周期方式:
\\
即可得出用户访问页面路径,两个风波时间戳之差即为用户在页面逗留的时间。
\\
通常我们APP中的View Controller就会承继自某个泛型,我们在泛型的对应方式中进行统计即可,然而对于没有从泛型承继的View Controller就无能为力了。
\\
借助于AOP,我们可以更高贵的完成这项工作:在UIViewController的load方式里swizzle viewDidAppear和viewDidDisappear方式,原有代码无需改动。
\\统计数据数组\\
根据数据需求,设置了如下的统计数组:
\\
页面步入和退出的风波,均上报上述的数据结构。
\\
其中还有几个问题是须要考虑的:
\\1.PAGE_ID和SOURCE_ID怎样定义\\
因为须要统一iOS和Android的PAGE_ID,所以须要做配置下发。iOS端领到的是一份plist的文件,文件的key的View Controller的类名的字符串表示,value则是PAGE_ID。
\\2.PAGE_ID和SOURCE_ID怎么获取\\
PAGE_ID直接按照当前View Controller的class即可取到,SOURCE_ID稍显复杂,需要按照APP页面嵌套堆栈结构来确认具体的获取方式,通常是从UINavigationController的导航栈中取前一个View Controller的page id即可。
\\
至此,页面访问流统计已基本完成,根据页面步入退出的PAGE_ID和SOURCE_ID串出一条完整的用户浏览路径,并得出用户在每位页面的逗留时间。
\\浏览数据爆光\\
采集到用户的浏览路径,以及在每位页面的逗留时间后,在个别特定的页面,如首页、商品列表页面,我们还想晓得用户在页面上滑动了几屏,看了什么活动、商品,以便于更好的为用户推荐喜欢的商品。
\\
用户听到的屏幕上的一块区域,认为是资源位,那么用户听到的内容是由一个个资源位组成。那么爆光的含意如下:
\\
我们晓得iOS中页面元素的基本组成单位是view,因此我们只须要判定view是否在可视区域,即可知悉当前view上的资源位是否须要爆光,从而作出相应的爆光操作,采集数据,上报插口等。
\\
由以上的剖析可知,待解决的问题主要有两个:
\\view的可见性判断\\tview爆光数据采集\view的可见性判断\\
查询UIView Class Reference可以看见setFrame:和layoutSubivews方式,可用于设置subview的frame。每次view fame更新均会调用此方式。因此,我们可以通过runtime swizzle此方式实现,添加一些数据采集相关的操作。
\\
我们为UIView添加了以下属性:
\\
首先明晰下几个术语的定义和规则:
\\
1.view的subview可见须要同时满足的3个条件:
\\
反之,只要以上任何一个条件不满足,我们就觉得此subview当前是不可见的。
\\
2.设置view为可见
\\
3.设置view为不可见
\\
Swzzile setFrame:,执行以下操作:
\\
\\
Swzzile layoutSubivews,调用yh_updateVisibleSubViews方式,其中执行以下操作:
\\
\\
经过以上的那些操作,我们能够晓得某个view及其subview的是否可见。
\\view爆光数据采集\\
为了取到view对应的数据,同样为UIView添加了以下属性:
\\
那么还有两个问题存在:
\\view爆光数据的细度\\tview及其subview的节点的爆光数据组装时机\
view爆光数据的细度
\\
根据项目中的实践经验,一般以UITableViewCell或则UI采集ViewCell为最小细度。同时,在最末节点的yh_exposureData字典中,增加一个key:isEnd,用来标示是否早已是最末的节点。
\\
view及其subview的爆光数据组装时机
\\
一般是在最末节点的可见性变化时,由下向下的遍历最末节点的superview,组装所有数据。
\\
因此我们覆写了setYh_viewVisible:的方式,即yh_viewVisible的set技巧。执行以下操作:
\\
至此,我们早已解决了view的可见性判定和爆光数据采集的问题。数据上报及策略不在赘言。
\\
此方案有几个缺点
\\需要自动设置爆光数据。\\t须要在合适时机手工调用view.yh_viewVisible触发数据采集,如viewdidappear等。\\t须要消耗一定的资源进行可视区域估算和爆光数据采集。\
还有两个问题是值得注意的:
\\UITableView在setBounds:时会对view的frame导致改变,因此须要swizzle setBounds:方法,需要在设置bounds后,调用[self yh_updateVisibleSubViews];\\tUIScrollView在setContentInset:时会影响view的可见区域,因此须要swizzle setContentInset:方法,需要在设置contentInset后,调用self.yh_viewVisibleRect = UIEdgeInsetsInsetRect(self.frame, contentInset);\业务数据手动采集\\
业务数据手动采集即业界流行的无埋点数据采集。
\\
传统的客户端用户点击数据采集是基于手工埋点的,对那个位置的数据感兴趣,就在这打个点,用户操作以后,随即触发数据上报。手工埋点的缺点很明显:错埋、漏埋。新版本发布后,经常有数据部门的小伙伴来反馈说,某某点位没有上报,某某点位上报错误的问题,开发的朋友也苦不堪言。
\\
无埋点数据采集带来了新的改变。首先基本上避开了手工埋点,个别情况须要特殊处理。其次由选择性的采集数据,变成了全量采集用户的所有点击触摸数据。
\\
新的改变也会带来新的挑战,无埋点数据采集的成为现实的可能性依然是基于Objective-C的runtime特点。实践过程中,思路上我们借鉴了iOS无埋点数据SDK的整体设计与技术实现,实现上借鉴了Sensors Analytics iOS SDK和Mixpanel iPhone。接下来,结合具体实践,介绍下我们的实现思路和遇见的一些问题。主要分以下三方面:
\\自动采集的点位怎么确保唯一性。\\t不同的点位类型,需要swizzle什么方式。\\tswizzle过程中踩到的坑。\自动采集的点位怎么确保唯一性\\
自动采集脱离了手工埋点,因此也没了点位的惟一标示。那我们要如何惟一定位到手动采集的点位呢?很容易想到的一个方案是:基于页面view的树状结构。此方案可以分解为两个问题:
\\view惟一标示怎样定义。\\tview惟一标示怎样生成。\
view惟一标示(view path)的定义
\\
我们规定,一个典型的view path如下:
\\
\ViewController[0]/UIView[0]/UITableView[0]/UITableViewCell[0:2]/UIButton[0]
\\
其中:
\\通过此标示可以在当前页面view树状结构中惟一的确定此元素。\\t标示的每一项由两部份组成:一是当前元素的class的字符串表示,二是当前元素在同级元素中的序号,自0开始估算。如当前第二个UIImageView,则是UIImageView1。\\t标示不同项之间以/拼接。\\t标示的最顶楼是当前view所在的ViewController。\\t对于UITableViewCell和UI采集ViewCell及类似的自定义组件,序号部份由两部份组成:section和row,并以:拼接。\\t标示的最末端是当前被点击或触摸的元素。\
view惟一标示怎样生成
\\
view path生成过程:由触发操作的最末端元素向下查询,一直查到ViewController为止。假设当前点击view为A_View,从当前的A_View入手遍历view树,每一级的数据存入P_Array中,过程如下:
\\
\\如果A_View是UI采集ViewCell类型,获取A_View所处UI采集View的indexPath,P_Array push路径信息[NSString stringWithFormat:@\"%@[%ld:%ld]\