Skip to content

Commit

Permalink
Prepare for release 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanCaiCoding committed May 10, 2022
1 parent ef560ec commit eab90fb
Show file tree
Hide file tree
Showing 51 changed files with 865 additions and 218 deletions.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Tracker is a lightweight tracking framework based on [the tracking idea of Buzzv

## Tracking idea

- [Why use chain of responsibility tracking?](https://dylancaicoding.github.io/Tracker/#/idea)
[Why use chain of responsibility tracking?](https://dylancaicoding.github.io/Tracker/#/idea)

## Sample

Expand Down Expand Up @@ -39,7 +39,7 @@ dependencies {

### Usage

See the documentation for [Java usage](https://dylancaicoding.github.io/Tracker/#/java).
> See the documentation for [Java usage](https://dylancaicoding.github.io/Tracker/#/java).
#### Initialization

Expand All @@ -64,15 +64,15 @@ trackNode = TrackNode("channel_name" to "recommend")
```

```kotlin
holder.itemView.trackNode = TrackNode("video_id" to item.id)
holder.itemView.trackNode = TrackNode("video_id" to item.id, "video_type" to item.type)
```

#### Establish a page source responsibility chain

When starting an activity, you need to call `intent.setReferrerTrackNode(activity/fragment/view)` to set the source node.
When starting an activity, you need to call `intent.putReferrerTrackNode(activity/fragment/view)` to set the source node.

```kotlin
val intent = Intent(activity, DetailsActivity::class.java).setReferrerTrackNode(view)
val intent = Intent(activity, DetailsActivity::class.java).putReferrerTrackNode(view)
activity.startActivity(intent)
```

Expand Down Expand Up @@ -102,27 +102,27 @@ view.postTrack("click_favorite")
In the activity, you can set up a thread node, which can share the tracking parameter between views or pages.

```kotlin
class ResultThreadNode : TrackNode {
var result: String? = null
class RecordTrackNode : TrackNode {
var isRecord = false

override fun fillTackParams(params: TrackParams) {
result?.let { params.put("result", it) }
params.put("is_record", it)
}
}

activity.putTrackThreadNode(ResultThreadNode())
activity.putThreadTrackNode(RecordTrackNode())
```

You can then update the parameters of the thread node in Activity, Fragment, View.

```kotlin
view.updateTrackThreadNode<ResultThreadNode> { result = "success" }
view.updateThreadTrackNode<RecordTrackNode> { isRecord = true }
```

Declare the thread node class when reporting.

```kotlin
view.postTrack("click_favorite", ResultThreadNode::class.java)
view.postTrack("click_publish", RecordTrackNode::class.java)
```

## Author's other libraries
Expand Down
36 changes: 20 additions & 16 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

[![](https://www.jitpack.io/v/DylanCaiCoding/Tracker.svg)](https://www.jitpack.io/#DylanCaiCoding/Tracker) [![](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/Tracker/blob/master/LICENSE)

Tracker 是基于[西瓜视频责任链的埋点思路](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ)实现的轻量级埋点框架。个人理解其核心思想后进行改进和优化,最后使用了不到 200 行代码(不含注释)实现,整体更加简洁易用,同时兼顾了 Kotlin 和 Java 的用法
Tracker 是基于[西瓜视频责任链的埋点思路](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ)实现的轻量级埋点框架。个人理解其核心思想后进行了改进和优化,最后使用了不到 200 行代码(不含注释)实现,学习成本更低,并且兼顾了 Kotlin 和 Java 用法

## 埋点思路

- [为什么使用责任链的埋点方案?](https://dylancaicoding.github.io/Tracker/#/idea)
[为什么使用责任链的埋点方案?](https://dylancaicoding.github.io/Tracker/#/idea)

## 示例

Expand Down Expand Up @@ -39,7 +39,7 @@ dependencies {

### 用法

[Java 用法](https://dylancaicoding.github.io/Tracker/#/java) 请查看文档。
> [Java 用法](https://dylancaicoding.github.io/Tracker/#/java) 请查看文档。
#### 初始化

Expand All @@ -57,26 +57,26 @@ class UMTrackHandler : TrackHandler {
}
```

#### 建立页面上下级责任链
#### 建立页面内上下级责任链

Activity、FragmentView 设置埋点节点 `trackNode` 添加埋点参数。
可以给 Activity、FragmentView 设置埋点节点 `trackNode` 添加埋点参数。

```kotlin
trackNode = TrackNode("channel_name" to "recommend")
```

```kotlin
holder.itemView.trackNode = TrackNode("video_id" to item.id)
holder.itemView.trackNode = TrackNode("video_id" to item.id, "video_type" to item.type)
```

通过视图树的层级关系(比如:`Activity -> Fragment -> ViewHolder -> Button`)就能建立节点的上下级责任链关系。

#### 建立页面来源责任链

页面跳转时需要调用 `intent.setReferrerTrackNode(activity/fragment/view)` 设置来源节点。
页面跳转时需要调用 `intent.putReferrerTrackNode(activity/fragment/view)` 设置来源节点。

```kotlin
val intent = Intent(activity, DetailsActivity::class.java).setReferrerTrackNode(view)
val intent = Intent(activity, DetailsActivity::class.java).putReferrerTrackNode(view)
activity.startActivity(intent)
```

Expand All @@ -86,7 +86,7 @@ activity.startActivity(intent)
trackNode = PageTrackNode("page_name" to "details")
```

`PageTrackNode` 会将前面所有节点的参数添加进节点中,添加的时候可以设置一些转换规则。比如上个页面的 `page_name`,跳转后上报 `from_page`
`PageTrackNode` 会添加前面所有节点的参数,添加的时候可以设置一些转换规则。比如上个页面的 `page_name`,跳转后上报 `from_page`

```kotlin
val referrerKeyMap = mapOf("page_name" to "from_page", "channel_name" to "from_channel_name")
Expand All @@ -103,34 +103,38 @@ view.postTrack("click_favorite")

#### 线索节点

线索节点适合用于具有会话特性的流程中,方便在流程中共享参数,常见的有登录、注册的流程订单创建流程等。
线索节点适合用于具有会话特性的流程中,方便在流程中共享参数,常见的有登录、注册的流程订单创建流程等。

在 Activity 可以设置线索节点,线索节点能在 View 或页面之间共享埋点参数。

```kotlin
class ResultThreadNode : TrackNode {
var result: String? = null
class RecordTrackNode : TrackNode {
var isRecord = false

override fun fillTackParams(params: TrackParams) {
result?.let { params.put("result", it) }
params.put("is_record", it)
}
}

activity.putTrackThreadNode(ResultThreadNode())
activity.putThreadTrackNode(RecordTrackNode())
```

之后就能在 Activity、Fragment、View 更新线索节点中的参数。

```kotlin
view.updateTrackThreadNode<ResultThreadNode> { result = "success" }
view.updateThreadTrackNode<RecordTrackNode> { isRecord = true }
```

上报的时候需要对线索节点进行声明才会收集参数。

```kotlin
view.postTrack("click_favorite", ResultThreadNode::class.java)
view.postTrack("click_publish", RecordTrackNode::class.java)
```

## 反馈

有问题可以提 [issues](https://github.com/DylanCaiCoding/Tracker/issues) 或加个人微信 `DylanCaiCoding`直接反馈。

## 作者其它的库

|| 简介 |
Expand Down
8 changes: 6 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[![](https://www.jitpack.io/v/DylanCaiCoding/Tracker.svg)](https://www.jitpack.io/#DylanCaiCoding/Tracker) [![](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/Tracker/blob/master/LICENSE)

Tracker 是基于[西瓜视频责任链的埋点思路](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ)实现的轻量级埋点框架。个人理解其核心思想后进行改进和优化,最后使用了不到 200 行代码(不含注释)实现,整体更加简洁易用,同时兼顾了 Kotlin 和 Java 的用法
Tracker 是基于[西瓜视频责任链的埋点思路](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ)实现的轻量级埋点框架。个人理解其核心思想后进行了改进和优化,最后使用了不到 200 行代码(不含注释)实现,学习成本更低,并且兼顾了 Kotlin 和 Java 用法

### 埋点思路

- [为什么使用责任链的埋点方案?](/idea)
[为什么使用责任链的埋点方案?](/idea)

### 示例

Expand Down Expand Up @@ -38,6 +38,10 @@ dependencies {
- [Kotlin 用法](/kotlin)
- [Java 用法](/java)

### 反馈

有问题可以提 [issues](https://github.com/DylanCaiCoding/Tracker/issues) 或加个人微信 `DylanCaiCoding`直接反馈。

### 作者其它的库

|| 简介 |
Expand Down
42 changes: 24 additions & 18 deletions docs/idea.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
## 责任链埋点思路

### 埋点需求

行为分析埋点通常需要包括某一事件发生时的前因、后果,以及事件发生对象的特征。在复杂的数据分析、模型训练等需求中,不仅仅需要获知某个事件的发生次数,对埋点上下文尤为关注。此处上下文指的通常有 2 类,分别是:

- 事件发生的页面信息和页面位置信息
- 用户经过怎样的路径来到当前页面,也就是“来源”信息
- 事件发生的页面信息和页面位置信息
- 用户经过怎样的路径来到当前页面,也就是“来源”信息

下面结合具体西瓜视频的场景,看一个简单的埋点需求,“点击收藏”事件
下面结合具体的西瓜视频场景,看一个简单的埋点需求,“点击收藏”事件

![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85f841476fd74b33ad4caa095bc2ceea~tplv-k3u1fbpfcp-zoom-1.image)

作为最常见的消费场景,列表和详情都有收藏按钮,产品希望知道每一个收藏事件发生的场景,方便后续优化收藏功能,以及结合用户收藏的情况,优化推荐模型。西瓜视频埋点需求是上报收藏按钮的点击事件 click_favorite,要求包含收藏影片的信息,所在的场景信息等。

1. 如果收藏事件发生在列表页,会上报如下的内容
1. 如果收藏事件发生在列表页,会上报如下的内容

```
{
Expand All @@ -26,7 +28,7 @@
}
```

2. 如果收藏事件发生在详情页,会上报如下的内容
2. 如果收藏事件发生在详情页,会上报如下的内容

```
{
Expand All @@ -50,19 +52,19 @@

通过平台支持的参数传递方式,逐个定义并且读写参数;或者基于面向对象程序设计,对每个类添加相应的埋点参数,在类对象的关系中进行埋点参数传递。直接传参有非常显著的缺陷:

- 每增加一个参数,都需要写大量的重复代码,工程代码膨胀
- 模块间约定了很多埋点参数的协议,耦合程度高,难以维护
- 一些场景的嵌套层次深,经过很多层的参数传递,非常容易漏报埋点参数
- 每增加一个参数,都需要写大量的重复代码,工程代码膨胀
- 模块间约定了很多埋点参数的协议,耦合程度高,难以维护
- 一些场景的嵌套层次深,经过很多层的参数传递,非常容易漏报埋点参数

#### 单例传参

上述问题有一种轻微缓解的办法,使用单例来进行埋点参数的访问。通过一个单例进行埋点参数的维护,由于单例提供了全局唯一访问入口,程序中的任何位置都能方便地读和写埋点参数。这种方式带来的好处是不需要在每个类都定义大量的埋点参数,只需要访问单例进行修改和读取。

会比前面的直接传参更简单,但这种方案治标不治本,同样有明显的弊端:

- 首先,无法解决列表页这种多实例场景的问题,比如一个推荐列表中有多个卡片,每个卡片的埋点参数都不一样,卡片的埋点参数还是需要自己传
- 单例的数据可能被多个位置写入,且一旦被覆盖就没法恢复,比如这样的路径:列表 -> 详情页 1 -> 相关推荐 -> 详情页 2,进到详情页 2 以后,单例的数据被覆盖了,这时候再回到详情页 1,获取到的埋点参数实际是详情页 2 的,导致埋点参数上报错误。
- 存放和清理的时机难以控制,清理早了会导致埋点参数缺失,忘记清理可能导致后面的埋点获取不到参数
- 首先,无法解决列表页这种多实例场景的问题,比如一个推荐列表中有多个卡片,每个卡片的埋点参数都不一样,卡片的埋点参数还是需要自己传
- 单例的数据可能被多个位置写入,且一旦被覆盖就没法恢复,比如这样的路径:列表 -> 详情页 1 -> 相关推荐 -> 详情页 2,进到详情页 2 以后,单例的数据被覆盖了,这时候再回到详情页 1,获取到的埋点参数实际是详情页 2 的,导致埋点参数上报错误。
- 存放和清理的时机难以控制,清理早了会导致埋点参数缺失,忘记清理可能导致后面的埋点获取不到参数

#### 无埋点

Expand All @@ -72,11 +74,11 @@

有这么好的事?为什么字节没有广泛使用?此方案的缺陷在于:

1. 仅能上报有限的简单事件类型,如页面视图曝光、点击等,无法完成复杂事件的上报,如一次支付行为的操作路径、结果、错误信息等
2. 无法自定义参数,主要指跳转的来源、所处的场景等上下文信息,无法满足复杂的数据分析和推荐模型所需的数据要求
3. 由产品经理、数据分析师等在埋点管理后台进行的事件录入,把复杂度从开发转嫁给了产品,消费成本较高
4. 对页面视图的稳定性有很高的要求,需要约定 id、文本、视图的层级,保持页面结构不变,如果客户端工程师因为一些新需求开发、性能优化等调整了视图结构,将会导致已录入的埋点失效,增加了额外的维护成本
5. 全场景的数据上报,可能产生大量的无用数据,消耗大量传输、存储、计算资源
1. 仅能上报有限的简单事件类型,如页面视图曝光、点击等,无法完成复杂事件的上报,如一次支付行为的操作路径、结果、错误信息等
2. 无法自定义参数,主要指跳转的来源、所处的场景等上下文信息,无法满足复杂的数据分析和推荐模型所需的数据要求
3. 由产品经理、数据分析师等在埋点管理后台进行的事件录入,把复杂度从开发转嫁给了产品,消费成本较高
4. 对页面视图的稳定性有很高的要求,需要约定 id、文本、视图的层级,保持页面结构不变,如果客户端工程师因为一些新需求开发、性能优化等调整了视图结构,将会导致已录入的埋点失效,增加了额外的维护成本
5. 全场景的数据上报,可能产生大量的无用数据,消耗大量传输、存储、计算资源

### 西瓜视频的责任链方案

Expand All @@ -86,12 +88,16 @@

![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c777c72524ee4fd3b75b7cbb05ab967f~tplv-k3u1fbpfcp-zoom-1.image)

比如在收藏按钮被点击时,只需要从收藏按钮的节点按照`卡片 -> 推荐频道 -> 放映厅Tab`的顺序向上找,就能够拿到所有需要的参数了,并且这个上下级关系(责任链)是已经客观存在的。
比如在收藏按钮被点击时,只需要从收藏按钮的节点按照`卡片 -> 推荐频道 -> 放映厅Tab` 的顺序向上找,就能够拿到所有需要的参数了,并且这个上下级关系(视图树责任链)是已经客观存在的。

再来看看页面的跳转链路,跳转到详情页,有很多种路径。比如`推荐列表 -> 详情页``推荐列表 -> 选集 -> 详情页`,但逻辑上也是一个树状结构。

![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4906defb017f474391880c5399951459~tplv-k3u1fbpfcp-zoom-1.image)

![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e4056fcff5a84c75988f4fa60e7e6ab5~tplv-k3u1fbpfcp-zoom-1.image)

所以我们需要的埋点上下文参数,理论上都可以通过节点的关系找到。
所以我们需要的埋点上下文参数,理论上都可以通过节点的关系找到。

### 参考文章

- [《西瓜客户端埋点实践:基于责任链的埋点框架》](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ)
28 changes: 13 additions & 15 deletions docs/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ public class UMTrackHandler implements TrackHandler {
}
```

### 建立页面上下级责任链
### 建立页面内上下级责任链

Activity、FragmentView 设置埋点节点 `trackNode` 添加埋点参数。
可以给 Activity、FragmentView 设置埋点节点 `trackNode` 添加埋点参数。

```java
Tracker.setTrackNode(this, params -> params.put("channel_name", "recommend"));
```

```java
Tracker.setTrackNode(holder.itemView, params -> params.put("video_id", item.id));
Tracker.setTrackNode(holder.itemView, params -> params.put("video_id", item.getId()).put("video_type", item.getType()));
```

通过视图树的层级关系(比如:`Activity -> Fragment -> ViewHolder -> Button`)就能建立节点的上下级责任链关系。

### 建立页面来源责任链

页面跳转时需要调用 `Tracker.setReferrerTrackNode(intent, activity/fragment/view)` 设置来源节点。
页面跳转时需要调用 `Tracker.putReferrerTrackNode(intent, activity/fragment/view)` 设置来源节点。

```java
Intent intent = new Intent(activity, DetailsActivity.class);
Tracker.setReferrerTrackNode(intent, view);
Tracker.putReferrerTrackNode(intent, view);
activity.startActivity(intent);
```

Expand All @@ -48,7 +48,7 @@ activity.startActivity(intent);
Tracker.setPageTrackNode(this, params -> params.put("page_name", "details"));
```

`PageTrackNode` 会将前面所有节点的参数添加进埋点中,添加的时候可以设置一些转换规则。比如上个页面的 `page_name`,跳转后上报 `from_page`
`PageTrackNode` 会添加前面所有节点的参数,添加的时候可以设置一些转换规则。比如上个页面的 `page_name`,跳转后上报 `from_page`

```java
HashMap<String, String> referrerKeyMap = new HashMap<>();
Expand All @@ -67,36 +67,34 @@ Tracker.postTrack(view, "click_favorite")

### 线索节点

线索节点适合用于具有会话特性的流程中,方便在流程中共享参数,常见的有登录、注册的流程订单创建流程等。
线索节点适合用于具有会话特性的流程中,方便在流程中共享参数,常见的有登录、注册的流程订单创建流程等。

在 Activity 可以设置线索节点,线索节点能在 View 或页面之间共享埋点参数。

```java
public class ResultTrackNode implements TrackNode {
public class RecordTrackNode implements TrackNode {

public String result = null;
public boolean isRecord = false;

@Override
public void fillTackParams(@NonNull TrackParams params) {
if (result != null) {
params.put("result", result);
}
params.put("is_record", isRecord);
}
}
```

```java
Tracker.putThreadTrackNode(new ResultTrackNode());
Tracker.putThreadTrackNode(new RecordTrackNode());
```

之后可以在 Activity、Fragment、View 更新线索节点中的参数。

```java
Tracker.updateThreadTrackNode(v, ResultTrackNode.class, node -> node.result = "success");
Tracker.updateThreadTrackNode(v, RecordTrackNode.class, node -> node.isRecord = true);
```

上报的时候需要对线索节点进行声明才会收集参数。

```java
Tracker.postTrack(view, "click_sign_in", ResultTrackNode.class);
Tracker.postTrack(view, "click_publish", RecordTrackNode.class);
```
Loading

0 comments on commit eab90fb

Please sign in to comment.