埋点方案

埋点方案-页面和视图的进入和离开

一、整个页面进入、离开的监控方案

视图创建基类 LifeCyclePage,使用 route 方法,并提供以下方法

1
2
3
4
5
// 当前页面显示了,底层必须先执行 super.viewDidAppear
void viewDidAppear(AppearBecause appearBecause)

// 当前页面消失了,底层必须先执行 super.viewDidDisappear
void viewDidDisappear(DisAppearBecause disAppearBecause);

二、页面内部分视图进入、离开的监控方案

场景:运行需要知道页面下的tab、页面下的tab的《子tab/子视图》的浏览时间。

方案1:点击–view_show/hide–不推荐

使用点击 view_show/hide 方案,实现步骤如下:

点击tab1:记tab1 view_show 事件,并记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。

点击tab2:记tab1 view_hide 事件,tab2 view_show 事件,并记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。

缺点:tab点击时候,记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。

方案2:视图是否显示监控–visibility–推荐方案

视图创建基类 LifeCycleView,使用 visibility 方案,并提供以下方法

1
2
3
4
5
// 当前页面显示了,底层必须先执行 super.viewDidAppear
void viewDidAppear(AppearBecause appearBecause)

// 当前页面消失了,底层必须先执行 super.viewDidDisappear
void viewDidDisappear(DisAppearBecause disAppearBecause);

2.1、原理:创建 VisibilityDetector 视图类,并设置 onVisibilityChanged 回调方法;

1
2
3
4
5
6
7
const VisibilityDetector({
required Key key,
required Widget child,
required this.onVisibilityChanged,
}) : assert(key != null),
assert(child != null),
super(key: key, child: child);

优点:不用每个视图(如tab页)去处理。

总结

页面和视图的进入和离开方案,总结如下:

序号 类型 方案 优点与不足
1 页面 LifeCyclePage + route 能比view增加具体显示和离开的原因
2 页面内的视图 LifeCycleView + visibility 只能知道显示、还是离开

附一、Flutter 曝光埋点

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

2、

附二、iOS tableview的曝光埋点

其他参考文档:iOS-曝光埋点

  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
43
// 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 用于监听可见状态的关键。

End