埋点原理与实现

埋点原理与实现

一、曝光埋点

1、使用 VisibilityDetector 监听子组件可见性的变化(什么时候触发变化)

2、

二、tableview的曝光埋点

  1. 使用UIViewwindow属性:可以通过检查视图的window属性是否为空来判断视图是否在屏幕上可见。如果window属性为非空值,表示视图正在显示。
  2. 使用CGRectCGRectIntersectsRect函数:可以通过将视图的frame转换到屏幕坐标系中,然后与屏幕的bounds进行比较,来确定视图是否在屏幕上可见以及可见的比例。
  3. 使用CADisplayLink:可以通过CADisplayLink来定期检查视图的可见性。在每次屏幕刷新时,都可以检查视图的window属性和frame,从而实现对视图可见性的实时监控。
  4. 使用isDisplayedInScreen类别方法:可以通过为UIView添加一个类别,并在该类别中实现isDisplayedInScreen方法,来判断视图是否在屏幕上可见。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// UIView+isDisplayedInScreen.h


// 使用CADisplayLink定期检查
- (void)startMonitoringViewVisibility:(UIView *)view {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(checkViewVisibility:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)checkViewVisibility:(CADisplayLink *)displayLink {
// 在这里检查视图的可见性
}

// 检查视图是否在屏幕上可见
- (BOOL)isViewVisible:(UIView *)view {
CGRect viewRect = [view convertRect:view.bounds toView:nil];
CGRect screenRect = [UIScreen mainScreen].bounds;
CGRect intersectionRect = CGRectIntersection(viewRect, screenRect);
CGFloat visibleArea = CGRectGetArea(intersectionRect);
CGFloat viewArea = CGRectGetArea(view.bounds);
return (visibleArea / viewArea) > 0.5;
}

曝光(曝光结束才计算曝光。曝光过程不计算,那初始曝光的怎么办)

当然我们对滑动曝光有一些额外的要求:

  • 需要滑出一定比例(只出现一点点不算)、一定时间(太快划走不算)的时候才出发曝光
  • 滑出视野的模块,再次滑入视野时需要再次上报
  • 模块在视野中上下反复移动只触发一次曝光(还未实现)

1、Flutter visibility_detector 原理解析

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@override
Widget build(BuildContext context) {
return VisibilityDetector(
key: Key('exposure_$hashCode'),
onVisibilityChanged: (visibilityInfo) {
var visiblePercentage = visibilityInfo.visibleFraction * 100;
var inScreen = visiblePercentage >= widget.exposeFactor;
if (inScreen != show) {
if (inScreen) {
_onExpose();
} else {
_onHide();
}
}
},
child: widget.child,
);
}

底层核心逻辑代码如下:

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
// 1、VisibilityDetector 继承自 SingleChildRenderObjectWidget 类
class VisibilityDetector extends SingleChildRenderObjectWidget

// 2、 重写其 createRenderObject(BuildContext context) 方法 返回RenderVisibilityDetector
@override
RenderVisibilityDetector createRenderObject(BuildContext context) {
return RenderVisibilityDetector(
key: key,
onVisibilityChanged: onVisibilityChanged,
);
}
}


class RenderVisibilityDetector extends RenderProxyBox {
RenderVisibilityDetector({
RenderBox child,
@required this.key,
@required VisibilityChangedCallback onVisibilityChanged,
}) : assert(key != null),
_onVisibilityChanged = onVisibilityChanged,
super(child);

// 省略一些代码...

// 3、在 RenderVisibilityDetector 中,重写绘制方法 paint(),push 一个用于监听可见状态的 Layer(VisibilityDetector)
@override
void paint(PaintingContext context, Offset offset) {
if (onVisibilityChanged == null) {
VisibilityDetectorLayer.forget(key);
super.paint(context, offset);
return;
}

final layer = VisibilityDetectorLayer(
key: key,
widgetSize: semanticBounds.size,
paintOffset: offset,
onVisibilityChanged: onVisibilityChanged);
context.pushLayer(layer, super.paint, offset);
}
}

在 RenderVisibilityDetector 中,在绘制方法 paint() 里 push 了一个 Layer,这个Layer就是 VisibilityDetector 用于监听可见状态的关键。

二、埋点的数据上报

将监听到的数据添加到 final List<Map<String, dynamic>> _buriedPointModels = []; 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> 	_init() {
> // print('BuriedPointManager _init');
> /// 每5秒上传一次,避免请求太多
> _uploadTimer = Timer.periodic(const Duration(milliseconds: 5000), (timer) {
> _uploadBuriedPoints();
> });
> }
>
> addEvent(String eventName, Map<String, dynamic> eventAttr) async {
> Map<String, dynamic> eventMap = {};
>
> eventMap.addAll({
> "event_name": eventName,
> "event_attr": eventAttr,
> "request_time": DateTime.now().millisecondsSinceEpoch, // 单条记录生成时间,精确到毫秒
> });
>
> _buriedPointModels.add(eventMap);
> }
>

三、曝光率

曝光率(曝光率= 点击量 / 曝光量)来分析用户(比如用户的喜好推荐数据的统计)

参考文档: