# Flutter SDK 使用指南

本指南将会介绍如何使用 Flutter SDK 接入您的项目。建议在接入开始前,先阅读数据规则一章。您可以在访问 GitHub (opens new window) 获取 Flutter SDK 的源代码。

最新版本为:2.0.2

更新时间为:2021-12-31

# 一、初始化 SDK

# 1.1 集成数数 Flutter 插件

在您 Flutter 项目的 pubspec.yaml 文件中添加 thinking_analytics 依赖:

dependencies:
  # 数数 Flutter 插件
  thinking_analytics: ^2.0.2

# 1.2 使用 Flutter 插件

首先在代码中导入数数 Flutter package:

import 'package:thinking_analytics/thinking_analytics.dart';

获得 ThinkingAnalyticsAPI 实例:

final ThinkingAnalyticsAPI ta = await ThinkingAnalyticsAPI.getInstance('APP_ID', 'https://SERVER_URL');

参数说明:

  • APP_ID: 您的项目的 APP_ID,在您申请项目时会给出,可以在 TA 后台项目管理页查看,请在此处填入。
  • SERVER URL: 数据接收端的 URL:
    • 如果您使用的是云服务,请输入以下 URL:
      • https://receiver.ta.thinkingdata.cn
    • 如果您使用的是私有化部署的版本,请输入以下 URL:
      • https://YOU_SERVER_URL

之后就可以通过 ta 实例来上报数据了:

// 上报一个简单事件
ta.track('simple_event');

# 1.3 其他初始化参数

您可以在初始化的时候传入更多的命名参数,以对 SDK 进行配置。

# timeZone

默认情况下,所有数据的发生时间都将设定为本机时间。如果你的产品分布在多个时区,并且希望将数据时间对其到指定时区,可以传入 timeZone 来设定时区。timeZone 需要是一个有效的时区字符串,例如 UTC, Asia/Shanghai 等。

# mode

默认情况下,SDK 会运行在 ThinkingAnalyticsMode.NORMAL 模式下,所有线上应用都应该运行在此模式下。为了方便集成阶段的调试,我们也允许将 SDK 设置为 DEBUG 模式:

  • ThinkingAnalyticsMode.DEBUG: 数据逐条上报,并打印详细的出错日志
  • ThinkingAnalyticsMode.DEBUG_ONLY: 数据逐条上报,只校验数据,不入库

注意: 不要在生产环境开启 DEBUG 模式。规定只有指定的设备才能开启 Debug 模式。只有在客户端开启了 Debug 模式,并且设备 ID 在 TA 后台的项目信息页 Debug 模式设备管理 中配置了的设备才能开启 Debug 模式。设备 ID 可以通过以下三种方式获取:

  • *TA 平台中事件数据中的 #device_id 属性
  • *客户端日志:SDK 初始化完成后会打印设备 DeviceId
  • *通过实例接口调用:获取设备 ID

# 二、设置用户 ID

SDK 默认会使用随机 UUID 作为每个用户的访客 ID,该 ID 将会作为用户在未登录状态下身份识别 ID。需要注意的是,默认访客 ID 在用户重新安装游戏以及更换设备时将会变更。

# 2.1 设置访客 ID(可选)

如果您对每个用户有自己的访客 ID 管理体系,则您可以调用 identify 来设置访客 ID:

ta.dentify('your_distinct_id');

如果需要获得当前访客 ID,可以调用 getDistinctId 获取:

String distinctId = await ta.getDistinctId();

# 2.2 设置与清除账号 ID

在用户进行登录时,可调用 login 来设置用户的账号 ID,在设置完账号 ID 后,将会以账号 ID 作为用户标识 ID,并且设置的账号 ID 将会在调用 logout 之前一直保留:

// 设置账号 ID
ta.login('your_account_id');

// 清除账号 ID
ta.logout();

注意:该方法不会上传用户登录、用户登出等事件。

# 三、上传事件

通过 track 可以上报事件及其属性。一般情况下,您可能需要上传十几到上百个不同的事件,如果您是第一次使用 TA 后台,我们推荐您先上传几个关键事件。

# 3.1 上传事件

建议您根据先前梳理的文档来设置事件的属性以及发送信息的条件。事件名称是 String 类型,只能以字母开头,可包含数字,字母和下划线 "_",长度最大为 50 个字符,对字母大小写不敏感。

ta.track('TEST_EVENT', properties: <String, dynamic>{
  'PROP_INT': 5678,
  'PROP_DOUBLE': 12.3,
  'PROP_DATE': DateTime.now().toUtc(),
  'PROP_LIST': ['apple', 'ball', 1234],
  'PROP_BOOL': false,
  'PROP_STRING': 'flutter test',
});
  • 事件属性是 Map<String, dynamic> 类型,其中每个元素代表一个属性;
  • 事件属性 Key 为属性名称,为 String 类型,规定只能以字母开头,包含数字,字母和下划线 "_",长度最大为 50 个字符,对字母大小写不敏感;
  • 属性值支持五种类型:String、数值类、bool、DateTime、List 类型。

当您调用 track() 时,SDK 会取系统当前时间作为事件发生的时刻,如果您需要指定事件时间,可以传入 DateTime 类型的参数 dateTime 来设置事件触发时间,传入 timeZone 表示事件的时区信息. SDK 会根据 dateTime 的时间戳和 timeZone 来将时间类型转化为字符串,表示事件时间。

DateTime dateTime = DateTime.parse('2020-01-01');
ta.track('test', dateTime: dateTime, timeZone: 'UTC');

/*
原始数据示例:
{
  "#type": "track",
  "#time": "2019-12-31 16:00:00.000",
  "#event_name": "test",
  "#distinct_id": "1ed3465e-17f6-4205-8f86-2e7a2b18027b",
  "properties": {
      "#network_type": "WIFI",
      "#app_version": "1.0",
      "#zone_offset": 0
  },
  "#uuid": "3d74c56f-8b2c-44c4-8683-2dcb3010c231"
}
*/

注意:尽管事件可以设置触发时间,但是接收端会做如下的限制:只接收相对服务器时间在前 10 天至后 3 天的数据,超过时限的数据将会被视为异常数据,整条数据无法入库。

# 3.2 设置公共事件属性

公共事件属性指的就是每个事件都会带有的属性,您可以调用 setSuperProperties 来设置公共事件属性,我们推荐您在发送事件前,先设置公共事件属性。

Map<String, dynamic> superProperties = {
  'SUPER_STRING': 'super string value',
  'SUPER_INT': 1234,
  'SUPER_DOUBLE': 66.88,
  'SUPER_DATE': DateTime.now(),
  'SUPER_BOOL': true,
  'SUPER_LIST': [1234, 'hello', DateTime.now().toUtc()]
};

ta.setSuperProperties(superProperties);

公共事件属性将会被保存到缓存中,无需每次启动 APP 时调用。如果调用 setSuperProperties 上传了先前已设置过的公共事件属性,则会覆盖之前的属性。如果公共事件属性和 track 上传的某个属性的 Key 重复,则该事件的属性会覆盖公共事件属性。

如果您需要删除某个公共事件属性,您可以调用 unsetSuperProperty 清除其中一个公共事件属性;如果您想要清空所有公共事件属性,则可以调用 clearSuperProperties.

// 清除属性名为 SUPER_LIST 的公共属性
ta.unsetSuperProperty('SUPER_LIST');

// 清空所有公共属性
ta.clearSuperProperties();

# 3.3 设置动态公共属性

如果公共属性的值不是常量,您可以通过设置动态公共属性的方式实现。动态公共属性也会加入到所有事件中,在事件上报时会动态获取实际上报值。动态公共属性目前不支持自动采集事件。

设置动态公共属性,需要传入一个返回 Map<String, dyanmic> 类型的函数,样例如下:

// 设置动态公共属性, 动态公共属性不支持自动采集事件
ta.setDynamicSuperProperties((){
  return <String, dynamic> {
    'DYNAMIC_DATE': DateTime.now().toUtc(),
    'PROP_INT': 8888
  };
});

注意:如果事件属性出现重名,动态公共属性的优先级大于公共事件属性,小于 track 中设置的事件属性。

# 3.4 记录事件时长

如果您需要记录某个事件持续时长,您可以调用 timeEvent 来开始计时,配置您想要计时的事件名称,当您上传该事件时,将会自动在您的事件属性中加入 #duration 这一属性来表示记录的时长,单位为秒。

// 调用 timeEvent 开启对 TIME_EVENT 事件的计时
ta.timeEvent('TIME_EVENT');

// do some thing...

// 通过 track 上传 TIME_EVENT 事件时,会在属性中添加 #duration 属性
ta.track("TIME_EVENT");

# 四、用户属性

TA 平台目前支持的用户属性设置接口为 userSet、userSetOnce、userAdd、userUnset、userDelete、userAppend.

# 4.1 userSet

对于一般的用户属性,您可以调用 userSet 来进行设置,使用该接口上传的属性将会覆盖原有的属性值,如果之前不存在该用户属性,则会新建该用户属性。

ta.userSet(<String, dynamic>{
  'USER_INT': 1,
  'USER_DOUBLE': 50.12,
  'USER_LIST': ['apple', 'ball', 'cat', 1, DateTime.now().toUtc()],
  'USER_BOOL': true,
  'USER_STRING': 'a user value',
  'USER_DATE': DateTime.now(),
});

与事件属性类似:

  • 用户属性是 Map<String, dynamic> 类型,其中每个元素代表一个属性;
  • 用户属性 Key 为属性名称,为 String 类型,规定只能以字母开头,包含数字,字母和下划线“_”,长度最大为 50 个字符,对字母大小写不敏感;
  • 用户属性值支持四种类型:String、数值类、bool、DateTime、List.

# 4.2 userSetOnce

如果您要上传的用户属性只要设置一次,则可以调用 userSetOnce 来进行设置,当该属性之前已经有值的时候,将会忽略这条信息:

ta.userSetOnce(<String, dynamic>{
  'USER_INT': 2,
  'USER_DOUBLE': 10.12,
});

注意:userSetOnce设置的用户属性类型及限制条件与 userSet 一致。

# 4.3 userAdd

当您要上传数值型的属性时,您可以调用 userAdd 来对该属性进行累加操作,如果该属性还未被设置,则会赋值 0 后再进行计算,可传入负值,等同于相减操作。

ta.userAdd(<String, num>{
  'USER_INT': 2,
  'USER_DOUBLE': 10.1,
});

注意: userAdd 中的属性类型以及 Key 值的限制与 userSet 一致,但 Value 只允许上报数值类型属性。

# 4.4 userUnset

如果您需要重置用户的某个属性,可以调用 userUnset 将该用户指定用户属性的值删除:

ta.userUnset('USER_INT');

# 4.5 userDelete

如果您要删除某个用户,可以调用 userDelete 将这名用户删除,您将无法再查询该名用户的用户属性,但该用户产生的事件仍然可以被查询到:

ta.userDelete();

# 4.6 userAppend

您可以调用 userAppend 为 List 类型的用户属性追加元素:

ta.userAppend(<String, List>{
  'USER_LIST': [DateTime.now()],
});

# 五、自动采集事件

您可以调用 enableAutoTrack 并传入一个 ThinkingAnalyticsAutoTrackType 类型的 List 来开启自动采集。当前支持四种:

  • ta_app_start: 应用进入前台,对应类型为 ThinkingAnalyticsAutoTrackType.APP_START
  • ta_app_end: 应用进入后台,对应类型为 ThinkingAnalyticsAutoTrackType.APP_END
  • ta_app_install: 安装后首次打开应用,对应类型为 ThinkingAnalyticsAutoTrackType.APP_INSTALL
  • ta_app_crash: 出现未捕获异常导致应用闪退,对应类型为 ThinkingAnalyticsAutoTrackType.APP_CRASH

关于自动采集事件,需要注意:

  1. 自动采集事件是在 native SDK 中实现的,因此动态公共属性目前无法在自动采集事件中添加。
  2. 如果您需要设置访客 ID,或者公共属性,请在开启自动采集事件之前完成设置。

开启自动采集示例:

ta.enableAutoTrack([
  ThinkingAnalyticsAutoTrackType.APP_START,
  ThinkingAnalyticsAutoTrackType.APP_END,
  ThinkingAnalyticsAutoTrackType.APP_INSTALL,
  ThinkingAnalyticsAutoTrackType.APP_CRASH,
]);

# 六、其他接口

# 6.1 获取设备 ID

SDK 在初始化完成后,会自动生成设备 ID,并记录在本地缓存,对于同一应用/游戏,一台设备的设备 ID 是不变的,可以调用 getDeviceId 获取设备 ID:

String deviceId = await ta.getDeviceId();

// 以设备 ID 作为访客 ID
// ta.identify(deviceId);

# 6.2 暂停/停止数据上报

有两类停止 SDK 上报的接口:

# 6.2.1 暂停 SDK 上报(enableTracking)

您可能希望在一些场景下,暂时停止 SDK 的数据采集以及上报,比如用户处于测试环境中、或者用户登录了一个测试账号,此时您可以调用下列接口,暂时停止 SDK 的上报。

您可以通过某一实例(包括主要实例以及轻实例)调用 enableTracking,传入 false 来暂停 SDK 的上报,该实例已经设置的 #distinct_id、#account_id、公共属性等将保留;该实例已经采集但还未上报成功的数据将继续尝试上报;后续该实例不能采集以及上报任何新数据、不能设置访客 ID、账户 ID 以及公共属性等,但是可以读取该实例已经设置的公共属性和设备 ID、访客 ID、账号 ID 等信息。

实例的停止状态将会被保存在本地缓存,直到调用 enableTracking、传入 true,SDK 实例将会重新恢复数据采集以及上报,需要注意轻实例因为不进行缓存,因此每次打开 APP 后,轻实例的暂停状态不会被保留,将重新开启上报。

// 暂停实例的上报,已缓存数据和已经设置的信息不被清除
ta.enableTracking(false);

// 恢复实例的上报
ta.enableTracking(true);

# 6.2.2 停止 SDK 上报(optOutTracking)

在一些特殊场景下,您可能需要完全停止 SDK 的功能,比如在适用 GDPR 的地区,用户选择不提供数据采集权限,则您可以调用如下接口完全关闭 SDK 的功能。

optOutTracking 只能通过主要实例调用,与 enableTracking 的最大区别在于,其将会清空该实例的本地缓存,包括本实例的访客 ID,账号 ID,公共属性,以及未上报的数据队列。之后再关闭该实例的采集和上报功能。

// 停止实例的上报, 并清空本地缓存
ta.optOutTracking();

如果您希望关闭 SDK 功能的同时,删除该用户在 TA 集群中的用户数据,可以传入 deleteUser 参数,这将会在停止 SDK 实例的功能前,上报一条 userDelete数据,以删除该用户的用户数据。

// 停止实例的上报,并发送 user_del
ta.optOutTracking(deleteUser: true);

实例的停止状态也将保存在本地缓存,直到调用 optInTracking,后续可以继续上报,但此时相当于一个全新的实例

// 重新开启上报
ta.optInTracking();

# 6.3 创建轻实例

您可以通过轻实例的方式,创建同一个 APP ID 下的多个实例

// 创建轻实例
ThinkingAnalyticsAPI light = await ta.createLightInstance();

// 为轻实例设置访客 ID
light.identify('light_d_id');

// 通过轻实例上报事件
light.track('TEST_EVENT_FROM_LIGHT');

注意:子轻量实例与父实例的 APP ID、上报地址以及部分设置一致,但其他信息不共享

# 6.4 时间校准

SDK 默认会使用本机时间作为事件发生时间上报,如果用户手动修改设备时间会影响到您的业务分析,自 v1.1.0 版本开始,您可以使用从服务端获取的当前时间戳对 SDK 的时间进行校准。此后,所有未指定时间的调用,包括事件数据和用户属性设置操作,都会使用校准后的时间作为发生时间。

// 1585633785954 为当前 unix 时间戳,单位为毫秒,对应北京时间 2020-03-31 13:49:45
ThinkingAnalyticsAPI.calibrateTime(1585633785954);

我们也提供了从 NTP 获取时间对 SDK 校准的功能。您需要传入您的用户可以访问的 NTP 服务器地址。之后 SDK 会尝试从传入的 NTP 服务地址中获取当前时间,并对 SDK 时间进行校准。如果在默认的超时时间(3 秒)之内,未获取正确的返回结果,后续将使用本地时间上报数据。

// 使用苹果公司 NTP 服务对时间进行校准
ThinkingAnalyticsAPI.calibrateTimeWithNtp("time.apple.com");

注意:

  • 您需要谨慎地选择您的 NTP 服务器地址,以保证网络状况良好的情况下,用户设备可以很快的获取到服务器时间。
  • 使用 NTP 服务进行时间校准存在一定的不确定性,建议您优先考虑用时间戳校准的方式。

# 七、相关预置属性

# 7.1 获取预置属性

v2.0.1 及以后的版本可以调用 getPresetProperties() 方法获取预置属性。

服务端埋点需要 App 端的一些预置属性时,可以通过此方法获取 App 端的预置属性,再传给服务端。

//获取属性对象
TDPresetProperties presetProperties = await _ta.getPresetProperties();

//生成事件预置属性
Map<String, dynamic>? eventPresetProperties = presetProperties.toEventPresetProperties();
/*
   {
  "#carrier": "中国电信",
  "#os": "iOS",
  "#device_id": "A8B1C00B-A6AC-4856-8538-0FBC642C1BAD",
  "#screen_height": 2264,
  "#bundle_id": "com.sw.thinkingdatademo",
  "#manufacturer": "Apple",
  "#device_model": "iPhone7",
  "#screen_width": 1080,
  "#system_language": "zh",
  "#os_version": "10",
  "#network_type": "WIFI",
  "#zone_offset": 8
    }
*/

//获取某个预置属性
String bundleId = presetProperties.bundleId;//包名
String os = presetProperties.os;//os类型,如Android、iOS
String systemLanguage = presetProperties.systemLanguage;//手机系统语言类型
int screenWidth = presetProperties.screenWidth;//屏幕宽度
int screenHeight = presetProperties.screenHeight;//屏幕高度
String deviceModel = presetProperties.deviceModel;//设备型号
String deviceId = presetProperties.deviceId;//设备唯一标识
String carrier = presetProperties.carrier;//手机SIM卡运营商信息,双卡双待时,取主卡的运营商信息
String manufacture = presetProperties.manufacturer;//手机制造商 如HuaWei、Apple
String networkType = presetProperties.networkType;//网络类型
String osVersion = presetProperties.osVersion;//系统版本号
double zoneOffset = presetProperties.zoneOffset;//时区偏移值

IP,国家城市信息由服务端解析生成,客户端不提供接口获取这些属性

# 八、进阶功能

从 v1.3.0 开始,Flutter SDK 支持上报三种特殊类型事件: 首次事件、可更新事件、可重写事件。这三种事件需要配合 TA 系统 2.8 及之后的版本使用。由于特殊事件只在某些特定场景下适用,所以请在数数科技的客户成功和分析师的帮助下使用特殊事件上报数据。

# 8.1 首次事件

首次事件是指针对某个设备或者其他维度的 ID,只会记录一次的事件。例如在一些场景下,您可能希望记录在某个设备上第一次发生的事件,则可以用首次事件来上报数据。

// 示例:上报设备首次事件, 假设事件名为 DEVICE_FIRST
var properties = {
  'PROP_INT': 5678,
  'PROP_DOUBLE': 12.3,
  'PROP_DATE': DateTime.now().toUtc(),
  'PROP_LIST': ['apple', 'ball', 1234],
  'PROP_BOOL': false,
  'PROP_STRING': 'flutter test',
};
var firstModel = TrackFirstEventModel('DEVICE_FIRST', '', properties);
_ta.trackEventModel(firstModel);

如果您希望以设备以外的其他维度来判断是否首次,则可以为首次事件设置 FIRST_CHECK_ID. 例如您需要记录某个账号的首次事件,可以将账号 ID 设置为首次事件的 FIRST_CHECK_ID:

// 示例:上报用户账号的首次事件, 假设事件名为 USER_FIRST
var properties = {
  'PROP_INT': 5678,
  'PROP_DOUBLE': 12.3,
  'PROP_DATE': DateTime.now().toUtc(),
  'PROP_LIST': ['apple', 'ball', 1234],
  'PROP_BOOL': false,
  'PROP_STRING': 'flutter test',
};
var firstModel = TrackFirstEventModel('USER_FIRST', 'YOU_ACCOUNT_ID', properties);
_ta.trackEventModel(firstModel);

注意:由于在服务端完成对是否首次的校验,首次事件默认会延时 1 小时入库。

# 8.2 可更新事件

您可以通过可更新事件实现特定场景下需要修改事件数据的需求。可更新事件需要指定标识该事件的 ID,并在创建可更新事件对象时传入。TA 后台将根据事件名和事件 ID 来确定需要更新的数据。

// 示例: 上报可被更新的事件,假设事件名为 UPDATABLE_EVENT
var properties = {
  'status': 3,
  'price': 100
};
var updateModel = TrackUpdateEventModel('UPDATABLE_EVENT', 'test_event_id', properties);

// 上报后事件属性 status 为 3, price 为 100
_ta.trackEventModel(updateModel);


var properties_new = {
  'status': 5
};
var updateModel_new = TrackUpdateEventModel('UPDATABLE_EVENT', 'test_event_id', properties_new);

// 上报后事件属性 status 被更新为 5, price 不变
_ta.trackEventModel(updateModel_new);

可更新事件默认会使用设备当前时间更新历史数据的事件时间,如果您希望指定事件时间,可以在上报可更新事件的时候,指定事件时间:

var updateModel = TrackUpdateEventModel('UPDATABLE_EVENT', 'test_event_id', {});
// 指定可更新事件的事件时间
updateModel.dateTime = DateTime.now().toUtc();
updateModel.timeZone = 'UTC';
_ta.trackEventModel(updateModel);

# 8.3 可重写事件

可重写事件与可更新事件类似,区别在于可重写事件会用最新的数据完全覆盖历史数据,从效果上看相当于删除前一条数据,并入库最新的数据。TA 后台将根据事件名和事件 ID 来确定需要更新的数据。

// 示例: 上报可被重写的事件,假设事件名为 OVERWRITABLE_EVENT

var properties = {
    'status': 3,
    'price': 100
};
var overwriteModel = TrackOverwriteEventModel('OVERWRITABLE_EVENT', 'test_event_id', properties);
// 上报后事件属性 status 为 3, price 为 100
_ta.trackEventModel(overwriteModel);


var properties_new = {
    'status': 5
};
var overwriteModel_new = TrackOverwriteEventModel('OVERWRITABLE_EVENT', 'test_event_id', properties_new);
// 上报后事件属性 status 为 5, price属性被删除
_ta.trackEventModel(overwriteModel_new);

可重写事件默认会使用设备当前时间重写历史数据的事件时间,如果您希望指定事件时间,可以在上报可重写事件的时候,指定事件时间:

var overwriteModel = TrackOverwriteEventModel('OVERWRITABLE_EVENT', 'test_event_id', {});
// 指定可重写事件的事件时间
overwriteModel.dateTime = DateTime.now().toUtc();
overwriteModel.timeZone = 'UTC';
_ta.trackEventModel(overwriteModel);

# Release Note

# v2.0.1 2021/06/28

  • 支持预置属性获取

# v2.0.0 2021/05/17

  • 适配Flutter2.0

# v1.3.3 2020/10/29

  • 适配 iOS 5G 网络
  • 优化 install,start 事件上报逻辑
  • 优化数据传输格式
  • 默认网络上报策略调整为 2G/3G/4G/5G/WIFI

# v1.3.2 2020/08/25

  • 修复特殊事件不设置 timeZone 导致上报错误的#zone_offset 的问题.

# v1.3.0 2020/08/24

  • 支持首次事件, 允许传入自定义的 ID 校验是否首次上报
  • 支持可更新、可重写的事件

# v1.2.1 2020/06/28

  • 优化代码:避免极端情况下的空指针异常

# v1.2.0 2020/06/23

  • 优化 Debug 模式,配合后台埋点管理
  • 支持 #system_language 属性

# v1.1.1 2020/04/14

  • 修复 Android 平台 DEBUG 模式下事件上传的 BUG

# v1.1.0 2020/04/03

  • 支持使用服务器时间校准 SDK 时间

# v1.0.0 2020/03/10

  • 支持事件和用户属性数据上报
  • 支持多实例和轻实例
  • 支持公共事件属性和动态公共属性
  • 支持自动采集事件
  • 支持 Debug 模式
  • 支持设置默认时区