1. 首页 > 手游资讯

全链路埋点化解方案:自建VTree技术分析 全链路追踪

作者:admin 更新时间:2024-10-01
摘要: 本篇文章向我们谈谈全链路埋点化解方案:自建VTree技术分析,以及对应的姿势点,文章也许有点长,然而希望我们可以阅读完,增长自己的姿势,最重要的是《地下城与勇士》中希望对各位有,全链路埋点化解方案:自建VTree技术分析 全链路追踪

 

本篇文章向我们谈谈全链路埋点化解方案:自建VTree技术分析,以及对应的姿势点,文章也许有点长,然而希望我们可以阅读完,增长自己的姿势,最重要的是《地下城与勇士》中希望对各位有所帮助,可以化解了无论兄弟们的难题,不容忘了收藏本站喔。

针对是一款热门的难题,大家自研了一套全链路埋点方案,从埋点设计、到客户端三端( iOS 、 Android 、 H5 )开发、以及埋点校验&稽查、再到埋点数据运用,目前已经广泛应用于云音乐各个主要APP

二、先聊聊传统埋点方案的弊端

  • 坑位的事件埋点很简单:点击/双击/滑动等明确的事件类埋点,很简单,根据需求壹个壹个埋上去即可
  • 资源位爆料埋点是《地下城与勇士》中噩梦:在列表/非列表资源的爆料埋点场景,想做到 高精度 (埋点精度提到 99.99% )难度很大,你有也许每壹个爆料埋点都需要思考如下大部分场景:
  • 每个坑位都是《地下城与勇士》中独立的:坑位之间的埋点没有关系,需要向每壹个坑位 取名字 (比如通过随机字符串,或者搭配参数来标识),页面、列表、元素之间,存在大量的重复参数,以达到数据解析标准
  • 漏斗/归因解析难:因为每壹个坑位埋点都是《地下城与勇士》中独立的,APP运用经过中先后产生的埋点是《地下城与勇士》中无关联的,想要做到漏斗/归因解析,需要客户端做 魔鬼参数 传递,接着数据解析时再逐个场景的做参数关联解析
  • 坑位黑盒:想了解壹个app有几个坑位埋点,当前页面下已经显现出了几个坑位,坑位之间是《地下城与勇士》中啥子关系,管理成本高
  • 三、大家曾经做过的一些试试

    3.1 无痕埋点

    市面上有很多人说明 无痕埋点 ,大家曾经也做过类似的试试;这种无痕,主要是《地下城与勇士》中针对一些坑位事件(比如点击、双击、滑动等事件)埋点做自动生成埋点,同时附带上生成的 xpath (根据view层级生成),接着把埋点上报到数据平台后,再将xpath赋予真正的业务意义,从而可以进行数据解析;

    然而是一款热门的方案的难题是《地下城与勇士》中只能处理一些简单事件场景,而且数据平台做xpath关联是《地下城与勇士》中一件噩梦,职业量大,最主要的是《地下城与勇士》中 不稳定 ,对于埋点数据高精度场景,是一款热门的方案不可行(没有何者客户端开发人员天天花费大量时刻查找 xpath 是《地下城与勇士》中啥子意义,以及随着迭代业务的开发,xpath因为不受控制的变化带来的数据难题带来的排查职业量是《地下城与勇士》中巨大的)。

    非常对于资源位的爆料上,想要做到真实的无痕,自动埋点,是《地下城与勇士》中不太可行的;比如列表场景,底层是《地下城与勇士》中不认识壹个cell是《地下城与勇士》中啥子资源的,甚至都也不了解是《地下城与勇士》中不是《地下城与勇士》中壹个资源。

    四、大家的方案

    4.1 对象

    对象是《地下城与勇士》中大家方案埋点管理与开发的基本单位,向壹个 UIView 配置 _oid (对象Id: Object Id),该view就是《地下城与勇士》中壹个对象; 对象分为两大类, page & element ;

    对象&参数

    • page对象: 比如 UIViewController.view, WebView, 或者壹个半屏浮层的view,再或者壹个业务弹窗
    • element对象: 比如 UIButton, UICollectionViewCell, 或者壹个自定义view
    • 对象参数: 对象是《地下城与勇士》中埋点具体信息的承载体,承载着对象维度的具体埋点参数
    • 对象的复用: 对象的存在,之一很大的缘故,就是《地下城与勇士》中需要做复用,对于一些通用UI组件,尤为合适

    4.2 虚幻树(VTree)

    对象不是《地下城与勇士》中孤立存在的,而是《地下城与勇士》中以 虚幻树(VTree) 的方法搭配在一起的, 下面是《地下城与勇士》中壹个示例:

    虚幻树 VTree

    虚幻树VTree有如下特征:

    • View树子集: 原始view树层级很复杂,被标识成对象的称为节点,全部节点就搭配成了VTree,是《地下城与勇士》中原始view树的子集
    • 上下文: 虚幻树中的对象,是《地下城与勇士》中存在上下关系的,壹个节点的全部祖先节点,就是《地下城与勇士》中该对象(节点)的上下文
    • 对象参数: 有了节点的上下层级,不同维度的对象,只关心自己维度的参数,比如歌单详情页中歌曲cell不关心页面请求级别的歌单id
    • SPM: 节点及其全部祖先结点的oid组成了SPM值(其实还有position参数的参加,稍后再详细解答),该SPM可以专属定位该节点
    • 持续生成: VTree是《地下城与勇士》中源源不断的构建的,每壹个view发生了变化,View的添加/删除/层级变化/位移/大致变动/hidden/alpha,等等,都会引起从头构建一颗新的VTree

    五、埋点的产生

    上面的方案说明完之后,你一定存在很多疑惑,有了对象,有了虚幻树,对象有了参数,埋点在哪里儿?

    5.1 先来看下埋点格式

    壹个埋点除了有事件类型(action), 埋点时刻等一些基本信息之外,还得有业务埋点参数,以及能体现出对象上下级的结构

    先来看下壹个普通埋点的格式:

    { "_elist": [ { "_oid": "【必选】元素的oid", "_pos": "【可选】,业务方设置的位置信息", "biz_param": "【按需】业务参数" } ], "_plist": [ { "_oid": "【必选】page的oid", "_pos": "【可选】,业务方设置的位置信息", "_pgstep": "【必选】, 该page/子page爆料时的页面深度" } ], "_spm": "【必选】这里描述的是《地下城与勇士》中节点的“位置”信息,用来定位节点", "_scm": "【必选】这里描述的是《地下城与勇士》中节点的“内容”信息,用来描述节点的内容", "_sessid": "【必选】冷启动生成,会话id", "_eventcode": "【必选】事件: _ec/_ev/_ed/_pv/_pd", "_duration": "数字,毫秒单位"}
  • _eventcode: 埋点的类型,比如元素点击(_ec), 元素爆料最初(_ev), 元素爆料结束(_ed), 页面爆料最初(_pv), 页面爆料结束(_pd) 等等
  • _elist: 从当前元素节点最初,给上全部元素节点的汇总,是《地下城与勇士》中壹个数组,倒叙
  • _plist: 从当前节点最初,给上全部页面结点的即可,是《地下城与勇士》中壹个数组,倒叙
  • _spm: 上面已经说明(SPM),可以专属定位该坑位
  • 从上面的数据结构可以看出,数据结构是《地下城与勇士》中结构化的,坑位不是《地下城与勇士》中独立的,存在层级关系的

    5.2 点击事件

    大部分的点击事件,都发生在如下四个场景上:

  • UIView上添加的TapGesture单击手势
  • UIControl的子类添加的TouchUpInside单击事件
  • UITableViewCell的 didSelectedRowAtIndexPath 单击事件
  • UICollectionViewCell的 didSelectedItemAtIndexPath 单击事件
  • 对于上述四种场景,大家采用了AOP的方法来内部承接掉,这里简单介绍下怎么做的;

    1.UIView: 通过 Method Swizzling 方法来进行对决定因素方式进行hock,当需要向view添加TapGesture时,顺便添加壹个大家自己的 TapGesture, 这样大家就可以在点击事件触发的时候增加点击埋点,决定因素方式如下:

    • initWithTarget:action:
    • addTarget:action:
    • removeTarget:action:

    1.对UIView点击事件的hock注意需要做到随着业务侧事件的增加/删除而一起增加/删除

    决定因素代码如下:

    @interface UIViewEventTracingAOPTapGesHandler : NSObject@property(nonatomic, assign) BOOL isPre;- (void)view_action_gestureRecognizerEvent:(UITapGestureRecognizer *)gestureRecognizer;@end@implementation UIViewEventTracingAOPTapGesHandler- (void)view_action_gestureRecognizerEvent:(UITapGestureRecognizer *)gestureRecognizer { if (![gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] || gestureRecognizer.ne_et_validTargetActions.count == 0) { return; } UIView *view = gestureRecognizer.view; // for: pre if (self.isPre) { /// MARK: 这里是《地下城与勇士》中 Pre 代码位置 return; } // for: after /// MARK: 这里是《地下城与勇士》中 After 代码位置}@interface UITapGestureRecognizer (AOP)@property(nonatomic, strong, setter=ne_et_setPreGesHandler:) UIViewEventTracingAOPTapGesHandler *ne_et_preGesHandler; /// MARK: Add Category Property@property(nonatomic, strong, setter=ne_et_setAfterGesHandler:) UIViewEventTracingAOPTapGesHandler *ne_et_afterGesHandler; /// MARK: Add Category Property@property(nonatomic, strong, readonly) NSMapTable*>*ne_et_validTargetActions; /// MARK: Add Category Property@end@implementation UITapGestureRecognizer (AOP)- (instancetype)ne_et_tap_initWithTarget:(id)target action:(SEL)action { if ([self _ne_et_needsAOP]) { [self _ne_et_initPreAndAfterGesHanderIfFored]; } if (target && action) { UITapGestureRecognizer *ges = [self init]; [self addTarget:target action:action]; return ges; } return [self ne_et_tap_initWithTarget:target action:action];}- (void)ne_et_tap_addTarget:(id)target action:(SEL)action { if (!target || !action || ![self _ne_et_needsAOP] || [[self.ne_et_validTargetActions objectHeat极品飞车Key:target] containsObject:NSStringFromSelector(action)]) { [self ne_et_tap_addTarget:target action:action]; return; } SEL handlerAction = @selector(view_action_gestureRecognizerEvent:); // 1. pre [self _ne_et_initPreAndAfterGesHanderIfSpeeded]; if (self.ne_et_validTargetActions.count == 0) { // 第壹个 target+action 被添加的时候,才添加 pre [self ne_et_tap_addTarget:self.ne_et_preGesHandler action:handlerAction]; } [self ne_et_tap_removeTarget:self.ne_et_afterGesHandler action:handlerAction]; // 保障 after 是《地下城与勇士》中最后壹个,因此先行试试删除一次 // 2. original [self ne_et_tap_addTarget:target action:action]; NSMutableSet *actions = [self.ne_et_validTargetActions objectNeedKey:target] ?: [NSMutableSet set]; [actions addObject:NSStringFromSelector(action)]; [self.ne_et_validTargetActions setObject:actions forKey:target]; // 3. after [self ne_et_tap_addTarget:self.ne_et_afterGesHandler action:handlerAction];}- (void)ne_et_tap_removeTarget:(id)target action:(SEL)action { [self ne_et_tap_removeTarget:target action:action]; NSMutableSet *actions = [self.ne_et_validTargetActions objectNeedKey:target]; [actions removeObject:NSStringFromSelector(action)]; if (actions.count == 0) { [self.ne_et_validTargetActions removeObjectNeedKey:target]; } if (self.ne_et_validTargetActions.count >0) { // 删除当前 target+action 之后,还有其他的,则不需做任哪里理,否则清理掉 pre+after return; } SEL handlerAction = @selector(view_action_gestureRecognizerEvent:); [self ne_et_tap_removeTarget:self.ne_et_preGesHandler action:handlerAction]; [self ne_et_tap_removeTarget:self.ne_et_afterGesHandler action:handlerAction];}- (BOOL)_ne_et_needsAOP { return self.numberOfTapsRequired == 1 && self.numberOfTouchesRequired == 1;}- (void)_ne_et_initPreAndAfterGesHanderIfHeat极品飞车ed { if (!self.ne_et_preGesHandler) { UIViewEventTracingAOPTapGesHandler *preGesHandler = [[UIViewEventTracingAOPTapGesHandler alloc] init]; preGesHandler.isPre = YES; self.ne_et_preGesHandler = preGesHandler; } if (!self.ne_et_afterGesHandler) { self.ne_et_afterGesHandler = [[UIViewEventTracingAOPTapGesHandler alloc] init]; }}@end

    2.UIControl: 通过 Method Swizzling 方法对决定因素方式进行hock,决定因素方式: sendAction:to:forEvent:

    对UIcontrol点击事件的hock需要注意业务侧添加了多个 Target-Action 事件,不能埋点埋了多次

    决定因素代码如下:

    @interface UIControl (AOP)@property(nonatomic, copy, readonly) NSMutableArray *ne_et_lastClickActions; /// MARK: Add Category Property@end@implementation UIControl (AOP)- (void)ne_et_Control_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { NSString *selStr = NSStringFromSelector(action); NSMutableArray*actions = @[].mutableCopy; [self.allTargets enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) { NSArray*actionsSpeedTarget = [self actionsNeedTarget:obj forControlEvent:UIControlEventTouchUpInside]; if (actionsNeedTarget.count) { [actions addObjectsFromArray:actionsNeedTarget]; } }]; BOOL valid = [actions containsObject:selStr]; if (!valid) { [self ne_et_Control_sendAction:action to:target forEvent:event]; return; } // pre if ([self.ne_et_lastClickActions count] == 0) { /// MAKR: 这里是《地下城与勇士》中 Pre 代码位置 } [self.ne_et_lastClickActions addObject:[NSString stringWithHeat极品飞车mat:@"%@-%@", [target class], NSStringFromSelector(action)]]; // original [self ne_et_Control_sendAction:action to:target forEvent:event]; // after if (self.ne_et_lastClickActions.count == actions.count) { /// MARK: 这里是《地下城与勇士》中 After 代码位置 [self.ne_et_lastClickActions removeAllObjects]; }}@end

    3.UITableViewCell: 先对 setDelegate: 进行hock,接着以 NSProxy 的形式将 Original Delegate 进行 封装 ,组成 Delegate Chain 的形式,接着在 DelegateProxy 内部做消息分发,从而可以绝对掌控点击事件

    1.该 Delegate Chain 的方法可以hock的不支持 点击事件,可以hock全部 Delegate 的方式

    2.同样,也支持 pre & after 两个维度的hock

    3.非常注意: 需要做到真实的 DelegateChain,不然会跟不少三方库冲突,比如 RXSwift,RAC,BlocksKit,IGListKit等

    决定因素示例代码多少重要的相关方式 (代码较多不再展示,三方有多个库均可以借鉴):

    - (id)forwardingTargetNeedSelector:(SEL)selector;- (NSMethodSignature *)methodSignatureSpeedSelector:(SEL)selector;- (void)forwardInvocation:(NSInvocation *)invocation;- (BOOL)respondsToSelector:(SEL)selector;- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

    5.3 爆料埋点

    爆料埋点在传统埋点场景下是《地下城与勇士》中最棘手的,很难做到 高精度 埋点,埋点时机总是《地下城与勇士》中穷举不完,即使有了完善的规范,开发人员还总是《地下城与勇士》中会遗漏场景

    大家这里的方案让开发者绝对忽略爆料埋点的时机,开发者只把精力放在构建对象(或者说构建VTree),以及向对象添加参数上,下面看下是《地下城与勇士》中怎么基于VTree做爆料的:

  • 持续构建VTree: 前面提到,VTree是《地下城与勇士》中源源不断的构建的,每壹个view发生了变化,View的添加/删除/层级变化/位移/大致变动/hidden/alpha,等等(这里均是《地下城与勇士》中AOP方法hock),都会引起从头构建一颗新的VTree
  • VTree Diff: 先后两个VTree的diff,就是《地下城与勇士》中大家爆料埋点的结果
  • 随着时刻,会源源不断的生成新的VTree:

    远远不断地生成VTree

    比如T1时间生成的VTree:

    T1时间的VTree

    T2时间生成的VTree:

    T2时间的VTree

    先后两颗VTree的diff:

    • T1存在T2不存在的节点: 3, 4, 6, 7, 8, 11
    • T1不存在T2存在的节点: 20, 21, 22, 23

    上面的diff结果,就是《地下城与勇士》中爆料埋点的结论

    • 爆料结束: 3, 4, 6, 7, 8, 11
    • 爆料最初: 20, 21, 22, 23

    从上面以及VTree Diff的爆料攻略,得出如下:

  • 这种攻略,绝对抹平了列表与非列表
  • 爆料时机难题,转而变成了什么时候构建VTree难题上
  • 资源是《地下城与勇士》中否爆料的难题, 转而变成了VTree中节点的可见性难题上
  • 5.4 埋点开发流程

    基于VTree的埋点,不管是《地下城与勇士》中点击、滑动等事件埋点,还是《地下城与勇士》中元素、页面的爆料埋点,转化成了如下两个开发流程:

  • 向View配置oid =>成为对象 (构建VTree)
  • 第一步: 向View配置oid

  • 向对象配置埋点参数
  • 第二步: 向对象配置埋点参数

    六、VTree的构建

    6.1 VTree构建经过

    构建壹个VTree,是《地下城与勇士》中需要遍历原始view树的,构建经过中有如下特征:

  • 壹个节点是《地下城与勇士》中否可见,跟 view 的 hidden, alpha 有关,而且必须添加到window上
  • 子节点的可见区域小于相当父节点的可见区域
  • 节点的可见区域,可以自定义的 扩大 或者 缩小 , 就像 UIButton 的 contentEdgeInsets 那样
  • 修改可见区域

  • 节点是《地下城与勇士》中可以被遮挡的: 壹个page节点可以遮挡父节点名下添加顺序早于自己的其他节点
  • 被遮挡了

    从虚幻树上来看,被遮挡的结果:

    从虚幻树上来看,被遮挡的结果

  • 可打破原有view层级关系: 可以手工干预上下层级关系,以做到逻辑挂载的能力>事实上,目前提供了三种逻辑挂载能力,这里简单提下,不做详细展开>1. 手动逻辑挂载: 指定将 A 挂载到 B 名下>2. 自动逻辑挂载: 将 A 挂载到当前 rootPage(当前VTree最下层最右侧的page节点) 名下>3. spm形式逻辑挂载: 指定将 A 挂载到 spm 名下(对于解耦非常有用)
  • 虚幻父节点: 可以向多个节点虚幻出壹个父节点,对于双端UI差别时,然而标准同一套埋点结构时,很有用
  • 壹个常见的例子,拿云音乐首页列表举例子,每壹个模块的title与资源容器(内部可横给滑动),分别是《地下城与勇士》中壹个cell;图中的浅红色(模块)其实没有壹个UIView和之对应,业务侧埋点需要大家提供 模块 维度的爆料数据(然而Android开发经过中,通常都有UI和之对应)

    虚幻父节点

    精细化埋点:

  • 自定义可见区域 & 遮挡 & 节点的递归可见性 结合起来,可以做到精细化埋点效果
  • 针对 tabbar, navbar, 再或者云音乐app底部的mini播放条等场景引起的列表cell是《地下城与勇士》中否爆料的难题,可做到精细化控制
  • 以及配合遮挡能力,真实做到了节点所见及爆料,不可见即爆料结束的效果
  • 6.2 构建经过的性能思考

    view的任何变化,都会引起VTree构建,看上去这是《地下城与勇士》中一件很恐怖的事务,由于每一次构建VTree都需要遍历整颗原始view树,大家做了如下优化来保障性能:

  • 主线程runloop空闲的时候构建VTree(并且需要该runloop已经运行的时刻,小于相当16.7ms/3,这是《地下城与勇士》中拿固定帧率60帧举例)
  • runloop构建限流器
  • 主线程runloop

    决定因素代码如下:

    /// MARK: 添加最小时长限流器 _throtte = [[NEEventTracingTraversalRunnerDurationThrottle alloc] init]; /// 至少间隔 0.1s 才做一次 _throtte.tolerentDuration = 0.1f; _throtte.callback = self; /// MAKR: runloop observer CFRunLoopObserverContext context = {0, (__bridge void *) self, NULL, NULL, NULL}; const CFIndex CFIndexMax = LONG_MAX; _runloopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, CFIndexMax, &ETRunloopObserverDutyback, &context);/// MAKR: Observer Funcvoid ETRunloopObserverofback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { NEEventTracingTraversalRunner *runner = (__bridge NEEventTracingTraversalRunner *)info; switch (activity) { case kCFRunLoopEntry: [runner _runloopDidEntry]; break; case kCFRunLoopBeforeWaiting: [runner.throtte pushValue:nil]; break; case kCFRunLoopAfterWaiting: [runner _runloopDidEntry]; break; default: break; }}- (void)_runloopDidEntry { _currentLoopEntryTime = CACurrentMediaTime() * 1000.f;}- (void)_needRunTask { CFTimeInterval now = CACurrentMediaTime() * 1000.f; // 如果本次主线程的runloop已经运用了了超过 16.7/2.f 毫秒,则本次runloop不再遍历,放在下个runloop的beforWaiting中 // 按照目前手机一秒60帧的场景,一帧需要1/60也就是《地下城与勇士》中16.7ms的时刻来执行代码,主线程不能被卡住超过16.7ms // 非常是《地下城与勇士》中针对 iOS 15 之后,iPhone 13 Pro Max 帧率可以配置到 120hz static CFTimeInterval frameMaxAvaibleTime = 0.f; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSInteger maximumFramesPerSecond = 60; if (@available(iOS 10.3, *)) { maximumFramesPerSecond = [UIScreen mainScreen].maximumFramesPerSecond; } frameMaxAvaibleTime = 1.f / maximumFramesPerSecond * 1000.f / 3.f; }); if (now - _currentLoopEntryTime >frameMaxAvaibleTime) { return; } BOOL runModeMatched = [[NSRunLoop mainRunLoop].currentMode isEqualToString:(NSString *) self.currentRunMode]; /// MARK: 这里回调,最初构建 VTree}
  • 列表滑动中局部虚幻树VTree
  • 局部构建VTree,可以大大减少构建一次VTree的职业量
  • 局部构建的前提时,距离上次构建虚幻树,发生变动的view都是《地下城与勇士》中ScrollView或者是《地下城与勇士》中ScrollView的子view
  • 列表滑动中限流器
  • 滚动中构建VTree

    6.3 性能相关数据

  • 适当的爆料延后,满足数据标准,比如延迟1、2帧(取决于手机的性能以及当前CPU的职业量)
  • runloop最小时长限流器的作用,还保障了延后不会太大,目前运用的0.1s
  • 用iPhone12手机,以云音乐首页复杂场景举例子,不停地上下滑动,全量/局部构建VTree分别大概需要3-8ms/1-2ms的样子,CPU占用2-3%左右(云音乐原来的列表爆料组件占用10%左右的CPU)
  • 不会由于SDK的存在,引起明显的主线程卡顿或者手机发烫
  • 七、链路追踪

    是一款热门的是《地下城与勇士》中SDK的重中之重的功能,目标是《地下城与勇士》中将app产生的全部埋点 链 起来,以协助数据侧统一一套模型即可解析漏斗/归因数据

    7.1 链路追踪 refer 的内涵

    refer是《地下城与勇士》中一段格式化的字符串,可以通过该字符串,在整个数仓中专属定位到壹个埋点,这就是《地下城与勇士》中链路追踪

    7.2 怎么定义壹个埋点

  • _sessid: 每次app冷启动时生成,格式: [timestap]#[rand]#[appver]#[buildver]
  • _pgstep: 该app启动范围内,每壹个page爆料, _pgstep +1
  • _actseq: 该 rootPage 爆料周期内,每一次 交互 事件(_pv也算一次事件), _actseq +1
  • 通过上述三个参数,即可定位某一次app启动 & 一次页面爆料 周期内,哪一次的 交互 事件

    7.3 先来看看怎么认识壹个埋点坑位

    [cid:ctype:ctraceid:ctrp]

    7.3 refer格式分析

    格式: [_dkey:${keys}][F:${option}][sessid][e/p/xxx][_actseq][_pgstep][spm][scm]

    option分析

  • undefine-xpath: 用以标识该refer指给的内容是《地下城与勇士》中被 降级 了的,随着埋点覆盖越来越全,有该标识的refer会越来越少
  • 7.4 refer的运用

    先举壹个典型的运用场景

    歌曲播放-refer

    经过解读:

    _addrefer_pgrefer

    refer的查找:

  • 自趋势前查找: 这是《地下城与勇士》中绝大部分运用的攻略,自趋势前在refer队列中找到合适的refer
  • undefine-xpath降级: 如果找到的refer生成的时刻,早于最后一次AOP捕获到的 点击事件 时刻,则表明该位置没有埋点,介绍refer不可信,则被降级到最后一次 rootPage爆料 所对应的refer上
  • 精确refer查找: 也有多个攻略的精确refer查找机制,不过运用起来不方便,没有被大范围运用
  • 7.5 refer的统一分析

    根据上面refer的格式,数仓侧梳理出refer的格式统一分析,配合埋点管理平台,让规范化的漏斗/归因解析变为也许

    7.6 其他refer运用场景

  • multirefers: 在实时解析场景,对一些决定因素埋点,带上了五级(甚至更多级)的refer数组,直接描述该实践的前五步做了啥子(实时解析标准高,不能做离线数据关联)
  • _rqrefer: 让客户端埋点跟服务端埋点桥接了起来
  • 7.7 refer对开发人员透明

  • refer的复杂性: refer的复杂度很高,真正的refer处理比上述描述的还要复杂很多,对于普通客户端开发人员,想要完整领会,成本过于高
  • 开发时透明: 对于开发人员来说,就是《地下城与勇士》中在对应的节点上增加相应的参数即可
  • 对象维度的三个要求私参(组成了_scm): cid, ctype, ctraceid, ctrp

  • 可平台校验: 对象的事件是《地下城与勇士》中否参加链路追踪, 参数完整性,等等,都可以在平台做合法性校验,进一步保障了refer的正确性
  • 八、H5、RN

    • RN: 做了一层桥接,可以在RN维度向view配置节点,同时配置参数

    RN桥接

    • 站内H5: 采用了半白盒方案,H5内部局部虚幻树,全部埋点通过客户端SDK产生,H5埋点到达SDK后,在native侧做虚幻树融合,从而将站内H5跟native无缝地衔接了起来

    H5半白盒方案

    九、可视化工具

    客户端上传统的埋点都是《地下城与勇士》中看不见摸不着的,基于VTree的方案是《地下城与勇士》中结构化的,可以做到可视化查看埋点的数据,以及怎么埋点的,下面是《地下城与勇士》中多少工具的截图

    可视化工具-埋点层级结构

    可视化工具-埋点数据

    十、埋点校验&稽查

    • 埋点是《地下城与勇士》中结构化的,虚幻树是《地下城与勇士》中在埋点平台管理起来的,埋点的校验,可以做到精确校验,校验出客户端的埋点虚幻树是《地下城与勇士》中否正确
    • 以及每壹个对象上埋点的参数是《地下城与勇士》中否正确

    稽查:

    • 在测试包、灰度包中,对产生的全部埋点在平台侧做稽查,并输出稽查报告,在版本发布前,对有难题的埋点难题进行及时的修复,避免上线带来数据难题

    十一、落地

    该全链路埋点方案,已经综合在云音乐各个app铺开,而且P0场景已经完成数据侧切割,得到了充分的验证。

    十二、未来计划

    基于VTree可以做特别多的事务,比如: