# flutter无痕埋点 **Repository Path**: tonyistudio/flutter_no_trace_buried_point ## Basic Information - **Project Name**: flutter无痕埋点 - **Description**: 依赖于树遍历实现的Flutter无痕埋点项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2022-12-21 - **Last Updated**: 2022-12-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
## Flutter树结构
### Widget树
包含各种配置信息,是平时业务开发直接接触到的树。
### Element树
中间树,管理Widget树和RenderObject树,将Widget生成RenderObject并进行一些更新操作。
### RenderObject树
渲染树,负责计算布局,绘制方面。Flutter引擎是根据这棵树进行渲染的
这里有一点需要注意的,RenderObject虽然是Widget创建的,但是实际上是在element(RenderObjectElement)中调用widget.createRenderObject方法,并将返回值赋值给renderObject。源码如下:
```dart
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this); //
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
```
最终生成如下图对应关系:
1.Widget树与Element树一一对应。
2.第三个图中的RenderView和RenderBox都是继承自RenderObject,遍历定位的时候,用的是RenderBox。
```dart
class RenderView extends RenderObject with RenderObjectWithChildMixin
同一事件序列中DOWN点和UP点均在Rect内部,关键代码如下:
```dart
RenderBox box = element.renderObject;
Offset origin = box.localToGlobal(Offset.zero);
Rect rect = Rect.fromLTWH(origin.dx, origin.dy, box.size.width, box.size.height);
// 点击位在手势部件范围内
var upGlobalPosition = tapUpDetails.globalPosition;
var downGlobalPosition = tapUpDetails.globalPosition;
if (rect.contains(upGlobalPosition) && rect.contains(downGlobalPosition)) {}
```
### 手势
#### 开启手势
- **acceptGesture**
- **rejectGesture**
Flutter中的手势拦截类似于一个竞技场,当有一个手势在竞争中胜出的时候,会执行它的**acceptGesture**方法,而其余失败的手势,就会执行**rejectGesture**方法。如果需要自定义一个能够响应手势的部件,则重写**rejectGesture**方法,在内部调用**acceptGesture**方法,打开手势即可。
#### 监听Listener
Listener是FLutter中所有手势的祖先,它能够监听原始指针事件。
> 手势冲突只是手势级别的,而手势是对原始指针的语义化的识别,所以在遇到复杂的冲突场景时,都可以通过`Listener`直接识别原始指针事件来解决冲突。
我们这里不需要解决冲突,但也能直接拿Listener来监听页面内的所有点击事件,并对其进行处理。
具体使用方式如下:
```dart
@override
Widget build(BuildContext context) {
return Listener(
onPointerDown: (details) {},
child: MaterialApp()
);
}
```
## PV埋点实现
### 1.一个容器对应一个Flutter页面
这种情况下,一般直接基于原生生命周期的无痕埋点即可进行PV埋点。
### 2.一个容器对应多个Flutter页面
这种情况下因为只有一个容器,无法知道Flutter端页面路由栈,所以无法再复用原生埋点方案。
#### 2.1Flutter端的路由监测方式
```dart
class RouterTrackObserver extends NavigatorObserver {
final Function(String) pvEventTrancking;
final List
## UV - FAQ
- **1.列表Item问题**
**问题:**列表中的所有Item的布局层级是一模一样的
**解决办法:**
- 向下遍历子节点的时候,遍历到最底层。而一般在Flutter中为Text或者Image,这样的话,我们可以取Text的data属性和Image的image属性值,一般而言在列表中的item的数据是不一样的。
- 当找到最终element后,回溯往上,直到回到列表层级的时候,记录下该item在列表中的下标。
- **2.列表两两嵌套问题,或者更多层的嵌套**
当出现四个列表两两嵌套,这时候,需要再增加一层由命中节点往祖先节点的回溯,记录下当前item在每一个列表中的下标位置。
- **3.普通层叠手势问题**
当出现手势层叠如下图:
**问题:**由于两个手势是层叠的,导致最上层的祖先节点到最下层的孙子节点的路径是一样的
**解决办法:**路径中记录当前节点的父节点层级数和字节点层级数量,由于两个手势是层叠的,所以,祖先节点数量肯定是不一样的,内层要比外层大。
- **4.列表与AppBar重叠问题**
**问题:**当列表滑动到AppBar下面的时候,点击AppBar,按照之前的遍历逻辑,会导致最终获取到的是列表的element而不是AppBar内部的element,造成Listener命中错误
**解决办法:**标题栏额外处理,判定标题栏内child被命中后,不再遍历body内child。
Scaffold. appbar list
- Stack. List. Appbar
