Skip to content

Flutter SDK 使用文档 ​

具体的事件上报参数说明,参考文档:Lark 链接 密码:@8228T56

下载 SDK ​

按宿主工程环境选择对应包;功能版本均为 v1.5.3(fp_version 均为 4.0.0),差异仅在 Flutter / Android 构建链兼容性。

主包(推荐):适配 Flutter 3.0.5+

下载 Flutter SDK 主包 (flutter-sdk-v1.5.3.zip)

兼容包:仅用于仍停留在 Flutter 3.0.5 + Gradle 6.x + AGP 4.x 的老旧 Android 工程;新工程请勿选用。

下载 Flutter SDK 兼容包 (flutter-sdk-v1.5.3-flutter305-gradle6-agp4-compat.zip)

更多历史版本见 下载历史版本。


目录 ​

  1. 概述
  2. 安装
  3. SDK 初始化
  4. 用户管理
  5. 自动埋点
  6. 手动事件上报
  7. 枚举参考
  8. SdkConfig 全局参数
  9. 上报数据格式
  10. 调试工具
  11. 高级用法
  12. 完整集成示例
  13. 桌面平台接入(macOS / Windows)

版本更新说明

1. 概述 ​

Analytics SDK 是一个 Flutter 插件。当前交付为 v1.5.3,按宿主环境提供两套包:

  • 主包:适配 Flutter 3.0.5+(推荐)
  • 兼容包:专供 Flutter 3.0.5 + Gradle 6.x + AGP 4.x 老旧工程

主要能力:

  • 自动埋点:页面浏览(app_page_view)、全局点击(page_click)(均默认开启)
  • 手动埋点:导航、视频 / 帖子 / 剧集、小说 / 漫画、广告、推荐等客户端事件
  • 本地持久化缓存(JSONL 格式),断网重传
  • 域名并发测速,自动选择最快上报节点
  • AES-GCM 加密配置,防止配置明文泄露
  • 事件类型白名单控制(服务端下发)
  • 广告展示去重(页面级别)
  • 设备信息自动采集(通过 device_info_plus,无需外部传入)
  • 设备指纹首次生成后持久化复用(fp_version 4.0.0)
  • getParams() 方法暴露全部或指定公共参数,供服务端复用

所有公开方法均不会向外抛出异常,SDK 内部失败不影响宿主应用运行。


2. 安装 ​

解压下载的 zip 后,在宿主工程 pubspec.yaml 中添加本地 path 依赖(路径按实际放置位置调整):

yaml
dependencies:
  analytics_sdk:
    path: ../analytics_sdk # 解压后的 SDK 目录

运行:

bash
flutter pub get

若 Android 构建仍停留在 Gradle 6.x / AGP 4.x,请改用兼容包,不要使用主包。


3. SDK 初始化 ​

3.1 最简初始化 ​

dart
import 'package:analytics_sdk/analytics_sdk.dart';

await AnalyticsSdk.instance.init(
  appId: 'your_app_id',
  encryptedConfig: 'Base64加密字符串',  // 服务端下发
);

设备品牌、型号、系统名称、版本等信息由 SDK 内部通过 device_info_plus 自动采集。 device_id 由 SDK 内部生成,接入方不需要、也不应从外部传入。

3.2 完整参数说明 ​

参数类型必填默认值说明
appIdString是—应用 ID
encryptedConfigString?是(可传 null)null加密配置字符串,见下方格式说明
channelString?否''渠道标识,可后续通过 setChannel 更新
uidString?否''用户 ID;未登录可传空;非空时长度须 ≥ 4(由宿主保证,SDK 不额外校验)。登录后通过 setUserIdAndType 更新
deviceIdString?否null已废弃:外部传入会被忽略,device_id 由 SDK 内部生成。服务端事件请通过 getParams / getDeviceCommonFields 获取公共参数后再上报。
appVersionString?否'1.0.0'应用版本号,需符合 x.y.z 格式(如 '2.3.1')。格式不合规时:Debug 模式打印错误日志并忽略传入值;Release 模式静默降级为 '1.0.0'。未传时默认 '1.0.0'
enableDebugBannerbool否false是否显示调试悬浮条(仅在非 release 包生效)

自动采集字段:device(类型)、device_brand、device_model、system_name、system_version、user_agent 均由 SDK 通过 device_info_plus 自动读取,无需传入。device_fingerprint 和 fp_version 也由 SDK 内部生成和维护,业务方外部传入会被忽略或纠正。 内部设备标识:Android 使用 DeviceIdentifier.getClientId();iOS 使用 IDFV;Web、桌面等其它端使用 SDK 本地持久化 UUID。

3.3 encryptedConfig 格式说明 ​

encryptedConfig 是一个 Base64 编码的 AES-GCM 密文,解密后内容为:

json
{
  "domainList": ["https://api1.example.com", "https://api2.example.com"],
  "eventList": [
    "app_page_view",
    "page_click",
    "app_pageitem_exposure",
    "app_pageitem_click",
    "video_event",
    "ad_impression"
  ],
  "eventSignature": {
    "signKey": "your_hmac_sha256_key",
    "version": "2.0.0"
  }
}
  • domainList:上报域名列表,SDK 启动时会并发测速,选择最快的域名上报。
  • eventList:启用的事件类型白名单。不在列表中的事件会被静默丢弃。传空列表或省略此字段时,SDK 会优先加载本地缓存的事件类型配置;没有缓存时允许所有事件上报(向后兼容)。
  • eventSignature:可选。配置 signKey 后,SDK 会为客户端事件自动生成顶层 event_sig 和 event_sig_version;如果事件入队时签名配置尚未就绪,会在真正请求接口前对缺失 event_sig 的事件再次尝试补签。version 不传时默认 2.0.0。

兼容直接在根节点传 signKey / eventSignKey / event_signature_key / event_sig_key,以及 signVersion / eventSigVersion / event_sig_version。

如需在调试包中验证加密串,使用 validateEncryptedConfig。

3.4 初始化时机 ​

建议在 main() 中 runApp() 之前调用:

dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 可选:在 init() 前覆盖默认参数
  SdkConfig.configure(
    uploadInterval: const Duration(seconds: 5),
    autoUploadThreshold: 10,
  );

  await AnalyticsSdk.instance.init(
    appId: 'your_app_id',
    encryptedConfig: await fetchEncryptedConfigFromServer(),
    channel: 'google_play',
    uid: currentUser?.id ?? '',
  );

  runApp(const MyApp());
}

4. 用户管理 ​

4.1 登录 / 切换账号 ​

dart
AnalyticsSdk.setUserIdAndType(
  userId: '12345',
  userTypeEnum: UserTypeEnum.vip,
);
参数类型说明
userIdString用户 ID;未登录可传空,非空时长度须 ≥ 4(由宿主保证)
userTypeEnumUserTypeEnum?用户类型,null 时默认为 normal

4.2 仅更新 uid(不改变用户类型) ​

dart
AnalyticsSdk.setUid('12345');

4.3 更新渠道 ​

dart
AnalyticsSdk.setChannel('huawei_store');

4.4 登出 ​

dart
AnalyticsSdk.logoutUser();

5. 自动埋点 ​

5.1 页面生命周期自动追踪(PageLifecycleObserver) ​

pageObserver 与 sessionObserver 必须注册一个,且只能注册一个。两者都会采集 Session 页面停留,供 app_session_summary.page_stay_list 使用。都不注册时停留明细可能为空;同时注册会告警。SDK 对误双注册做了幂等保护,业务仍应只留一个。

页面曝光方式必须注册是否自动上报 app_page_view
SDK 自动上报AnalyticsSdk.instance.pageObserver是
业务手动上报AnalyticsSdk.instance.sessionObserver否

已经使用 pageObserver 且继续自动上报 app_page_view 的项目不用改。业务自行上报时:原先没注册 Observer 的补上 sessionObserver;原先注册了 pageObserver 的要换成 sessionObserver。

dart
MaterialApp(
  navigatorObservers: [
    // 自动上报 app_page_view
    AnalyticsSdk.instance.pageObserver,
    // 业务自行上报 app_page_view 时改用下面这一行,不要两个一起注册
    // AnalyticsSdk.instance.sessionObserver,
  ],
)

pageObserver 自动追踪页面进入和离开并上报 app_page_view;sessionObserver 只维护页面生命周期与 Session 停留,曝光由业务手动上报。navigation 事件请参见 5.5 导航事件手动上报。

获取当前页面 key:

dart
final currentPage = PageLifecycleObserver.currentPageKey;

响应式监听路由变化 ​

如需在路由切换时响应式执行逻辑(例如更新 UI 状态),使用 currentPageKeyListenable:

dart
PageLifecycleObserver.currentPageKeyListenable.addListener(() {
  final currentPage = PageLifecycleObserver.currentPageKey;
  // 路由变化时执行逻辑
});

currentPageKeyListenable 类型为 ValueListenable<String>(只读),无法从外部写入,防止业务方意外破坏 SDK 内部状态。

自定义路由名解析(GetX / GoRouter) ​

当路由框架使用带参数路由(如 /video/123),而 route.settings.name 返回的是实际路径而非模板路径时,注入解析回调将其映射为稳定的模板路径,再配合 PageNameMapper.addMappings 实现稳定 pageKey 归因:

dart
// 在 AnalyticsSdk.init() 之后调用
AnalyticsSdk.instance.setRealRouteResolver((actualRoute) {
  // 示例:将 /video/123 映射为 /video/:id
  // GetX 场景通常不需要注入,GoRouter 也一般不需要
  // 仅当框架无法在 settings.name 中返回模板路径时使用
  return myRouter.getTemplatePath(actualRoute);
});

传入 null 可清除回调,恢复默认行为(直接使用 settings.name):

dart
AnalyticsSdk.instance.setRealRouteResolver(null);

大多数项目不需要这个配置。PageNameMapper.addMappings 的 :param 动态路由格式已能覆盖绝大多数带参数路由场景,优先使用映射方案。

5.2 页面名称映射(PageNameMapper) ​

AppPageEnum 预定义的 39 个页面已自动携带 name,直接使用即可,无需为它们额外调用 addMappings。

addMappings 只在以下情况需要:业务自定义了 AppPageEnum 未覆盖的页面 key,且希望 SDK 内部(如路由 push 时)也能解析到正确的中文名称。

dart
import 'package:analytics_sdk/manager/page_name_manager.dart';

// 仅为自定义页面补充映射,AppPageEnum 已有的无需重复添加
PageNameMapper.addMappings({
  'live_room':   '直播间',
  'gift_center': '礼物中心',
});

动态路由映射(新格式) ​

当路由名称包含动态参数(如 /video/123、/novel/456)时,使用新格式将路由模式映射为稳定的 pageKey:

dart
PageNameMapper.addMappings({
  // 新格式:值为 {'key': 稳定pageKey, 'name': 页面名称}
  'video/:id':       {'key': 'video_detail', 'name': '视频详情页'},
  'novel/:id':       {'key': 'novel_detail', 'name': '小说详情页'},
  'comic/:id':       {'key': 'comic_detail', 'name': '漫画详情页'},
  'user/:uid/posts': {'key': 'user_posts',   'name': '用户帖子页'},
});

命中后,SDK 会将原始路由名(如 video/123)自动替换为稳定 pageKey(如 video_detail),后续上报的 page_key、referrer_page_key 等字段不再因参数变化而产生多个不同的 key 值。

新旧格式可在同一 addMappings 调用中混用:

dart
PageNameMapper.addMappings({
  'live_room':   '直播间',           // 老格式:精确匹配
  'video/:id':   {'key': 'video_detail', 'name': '视频详情页'},  // 新格式:动态路由
});

注意事项 ​

  • :param 段可匹配任意单个路径节点,不支持跨 / 匹配(如 :rest* 不支持)。
  • 若多个 pattern 可能匹配同一条路由,SDK 会打印警告日志并按注册顺序取第一个命中的 pattern。
  • key 和 name 字段均不可为空,否则该条映射会被忽略并打印警告日志。
  • 路由名前导斜杠(/video/:id 与 video/:id)会自动归一化,两者等价。

5.3 全局点击自动追踪(GlobalClickWrapper) ​

dart
import 'package:analytics_sdk/widget/global_click_wrapper.dart';

MaterialApp(
  builder: (context, child) => GlobalClickWrapper(child: child!),
)

5.4 Tab 切换自动追踪 ​

通过 .withAnalytics(tabs: [...]) 扩展方法包装原生 Widget,Tab 切换时自动上报,无需修改现有结构。

BottomNavigationBar(主导航) ​

dart
BottomNavigationBar(
  currentIndex: _currentIndex,
  onTap: (i) => setState(() => _currentIndex = i),
  items: const [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
    BottomNavigationBarItem(icon: Icon(Icons.explore), label: '发现'),
    BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
  ],
).withAnalytics(tabs: [
  AnalyticsTab(AppPageEnum.home.key,     AppPageEnum.home.name),
  AnalyticsTab(AppPageEnum.discover.key, AppPageEnum.discover.name),
  AnalyticsTab(AppPageEnum.mine.key,     AppPageEnum.mine.name),
])

切换时同时上报:

  • AppPageViewEvent:含来路页面 key,更新 PageLifecycleObserver.currentPageKey
  • NavigationEvent:导航行为记录

TabBar(页内分区 Tab) ​

dart
TabBar(
  controller: _tabController,
  tabs: const [Tab(text: '视频'), Tab(text: '小说')],
).withAnalytics(tabs: [
  AnalyticsTab(AppPageEnum.videoList.key, AppPageEnum.videoList.name),
  AnalyticsTab(AppPageEnum.novelList.key, AppPageEnum.novelList.name),
])

切换时同时上报:

  • AppPageViewEvent:含来路页面 key,更新 PageLifecycleObserver.currentPageKey
  • NavigationEvent:导航行为记录

⚠️ 数据侧注意:若之前已接入 TabBar.withAnalytics(),升级后服务端会新增来自 TabBar 切换的 app_page_view 事件,请提前告知数据侧避免指标异常。

返回值实现了 PreferredSizeWidget,可直接用于 AppBar.bottom:

dart
AppBar(
  bottom: TabBar(...).withAnalytics(tabs: [...]),
)

AnalyticsTab 参数 ​

参数类型必填说明
keyString是Tab 唯一标识,建议使用 AppPageEnum.xxx.key,也可传自定义字符串
nameString?否Tab 展示名称;建议使用 AppPageEnum.xxx.name,省略时由 PageNameMapper.getPageName(key) 解析

注意:tabs 列表顺序须与 items / tabs 保持一致,长度可以小于实际 Tab 数(多余的 Tab 会用 tab_<index> 兜底,不会崩溃)。

5.5 导航事件手动上报(navigation) ​

navigation 事件不再由 SDK 自动上报。仅在用户主动点击导航菜单(底部 Tab、顶部 Tab、侧边抽屉)时,由接入方手动触发。

已使用 .withAnalytics() 扩展的 BottomNavigationBar / TabBar 无需额外调用,扩展内部已处理。

trackNavigation — 上报导航点击 ​

dart
// 用户点击底部 Tab 时调用,应早于页面切换完成
AnalyticsSdk.instance.trackNavigation(
  pageKey: AppPageEnum.video.key,   // 'video'
  pageName: AppPageEnum.video.name, // '影视'
);

示例(手动管理 BottomNavigationBar):

dart
// 预定义 Tab 配置(建议用 AppPageEnum,也可用自定义字符串)
const _navTabs = [
  AppPageEnum.home,
  AppPageEnum.videoList,
  AppPageEnum.mine,
];

BottomNavigationBar(
  onTap: (index) {
    AnalyticsSdk.instance.trackNavigation(
      pageKey: _navTabs[index].key,
      pageName: _navTabs[index].name,
    );
    setState(() => _currentIndex = index);
  },
)

updateCurrentPage — Tab 切换时同步当前页面 ​

适用于 PageView + AutomaticKeepAliveClientMixin 场景:Tab 切换不走路由生命周期,需手动告知 SDK 当前页面,保证后续 page_click 归属正确,且从详情页 pop 返回后能恢复正确的子页面 key。

dart
// 预定义子 Tab(建议用 AppPageEnum,也可自定义字符串)
const _subPages = [
  AppPageEnum.videoList,
  AppPageEnum.novelList,
  AppPageEnum.comicList,
];

PageView(
  onPageChanged: (index) {
    final page = _subPages[index];
    AnalyticsSdk.instance.updateCurrentPage(
      pageKey: page.key,
      pageName: page.name,
    );
    // 同时手动上报 app_page_view(PageView 不走路由观察者)
    AnalyticsSdk.instance.track(AppPageViewEvent(
      pageKey: page.key,
      pageName: page.name,
      referrerPageKey: _previousPageKey,
      referrerPageName: _previousPageName,
    ));
  },
)

6. 手动事件上报 ​

所有手动事件通过 AnalyticsSdk.instance.track(event) 上报。

也可使用统一入口 trackEvent,只需传入事件名和业务 payload,SDK 会自动补齐公共字段并生成 event_id(适合广告 SDK onEvent 直接转发):

dart
AnalyticsSdk.instance.trackEvent(
  eventName: 'ad_impression_sdk',
  payload: {
    'request_id': 'req_001',
    'ad_slot_key': 'slot_home',
    'ad_id': 'ad_001',
    'ad_type': 'banner',
    'trace_id': 'trace_001',
  },
);

6.1 App 安装事件【客户端】 ​

触发时机:首次安装启动后,读取剪贴板获取到 traceId 时上报。

dart
AnalyticsSdk.instance.track(AppInstallEvent(
  traceId: 'clipboard_trace_id',
));
字段类型说明
traceIdString落地页生成并写入剪贴板的唯一追踪 ID

6.2 视频行为事件【客户端】 ​

触发时机:用户播放、暂停、快进、完播等操作时上报。

dart
AnalyticsSdk.instance.track(VideoEvent(
  videoId: 'vid_001',
  videoTitle: '精彩大片',
  videoTypeId: 'action',
  videoTypeName: '动作片',
  videoTagKey: 'hot,new',
  videoTagName: '热门,最新',
  videoDuration: 7200,
  playDuration: 120,
  playProgress: 30,
  videoBehavior: VideoEventEnum.VIDEO_PLAY,
  videoContentType: VideoContentTypeEnum.video,
  playSpeed: 100, // 1 倍速;1.25 倍速传 125
  recommendTraceId: 'trace_abc123',  // 未接推荐引擎传 ''
  mediaId: '',
));

帖子视频示例(post_video 必须带非空 postId):

dart
AnalyticsSdk.instance.track(VideoEvent(
  videoId: 'vid_post_001',
  videoTitle: '帖子视频',
  videoTypeId: 'default',
  videoTypeName: '默认分类',
  videoTagKey: 'default',
  videoTagName: '默认标签',
  videoDuration: 60,
  playDuration: 15,
  playProgress: 25,
  videoBehavior: VideoEventEnum.VIDEO_PLAY,
  videoContentType: VideoContentTypeEnum.postVideo,
  postId: 'post_001',
  playSpeed: 125,
  mediaId: 'media_001',
));
字段类型必填说明
videoIdString是视频 ID
videoTitleString是视频标题
videoTypeIdString是视频分类 ID
videoTypeNameString是视频分类名称
videoTagKeyString是标签 KEY,多个用英文逗号分隔
videoTagNameString是标签名称,多个用英文逗号分隔
videoDurationint是视频总时长(秒)
playDurationint是本次播放时长(秒)
playProgressint是播放进度百分比(0-100)
videoBehaviorVideoEventEnum是视频行为,见 VideoEventEnum
videoContentTypeVideoContentTypeEnum?是video / short_video / post_video;不区分时长类型时可传 null
postIdString条件帖子 ID;仅 post_video 必填且非空,否则事件丢弃;其他类型默认 ''
playSpeedint否实际倍速 × 100,默认 100(1 倍速);1.25 倍速传 125
recommendTraceIdString否推荐引擎 trace ID,未接推荐引擎传 '',默认 ''
mediaIdString否媒体资源 ID,默认 ''

播放失败沿用 video_event,在播放器失败回调里上报,时长和进度填失败当时的实际值。无需新常量或升级 SDK:

dart
videoBehavior: VideoEventEnum('video_failed', '播放失败'),

上报字段为 video_behavior_key: video_failed、video_behavior_name: 播放失败。帖子视频仍须 post_video 且 postId 非空。也可用 trackEvent(eventName: 'video_event', payload: {...}) 直接传这两个字符串。白名单仍是 video_event。服务端需接受该取值。


6.2.1 帖子展示事件【客户端】 ​

触发时机:图文帖 / 视频帖展示时上报。视频帖的播放等操作仍走 VideoEvent(post_video)。

dart
AnalyticsSdk.instance.track(PostEvent(
  mediaId: 'media_001',
  postId: 'post_001',
  postTitle: '视频帖子',
  postCreateTime: 1725686400, // payload 输出 post_create_time(10 位秒级)
  postUserId: 'author_001',
  postUserNick: '作者',
  postContentType: PostContentTypeEnum.videoPost,
  // postTypeId / postTypeName / postTagKey / postTagName 有默认值
  // postBehavior 默认 PostEventEnum.VIEW
));
字段类型必填说明
mediaIdString是老司机媒体资源 ID
postIdString是帖子 ID,空则丢弃
postTitleString是帖子标题
postCreateTimeint是帖子创建时间,10 位秒级时间戳;payload 字段为 post_create_time(不再输出 create_time)。1.5.2 起 Dart 参数由 createTime 改名,旧写法无法编译
postUserIdString是创建人账号 ID
postUserNickString是创建人昵称
postContentTypePostContentTypeEnum是post(图文)/ video_post(视频帖)
postTypeIdString否分类 ID,默认 default
postTypeNameString否分类名称,默认「默认分类」
postTagKeyString否标签 KEY,默认 default
postTagNameString否标签名称,默认「默认标签」
recommendTraceIdString否推荐 trace,默认 ''
postBehaviorPostEventEnum否默认 VIEW(展示)

启用 eventList 白名单时加入 post_event。post_like / post_comment / post_collect / post_unlock 由服务端上报。


6.2.2 剧集播放事件【客户端】 ​

dart
AnalyticsSdk.instance.track(DramaEvent(
  dramaId: 'drama_001',
  dramaTitle: '短剧标题',
  episode: 5,
  totalEpisode: 20,
  mediaId: 'media_005',
  videoId: 'video_005',
  dramaDuration: 300,
  playDuration: 60,
  playProgress: 20,
  playBehavior: DramaPlayBehaviorEnum.EP_PLAY,
  // playSpeed 默认 100;dramaContentType 默认 shortDrama
));
字段类型必填说明
dramaIdString是剧集 ID
dramaTitleString是剧集名称
episodeint是当前第几集(从 1 开始)
totalEpisodeint是总集数
mediaIdString是老司机媒体资源 ID
videoIdString是当前集视频 ID
dramaDurationint是当前集总时长(秒)
playDurationint是本次播放时长(秒)
playProgressint是播放进度(0-100)
playBehaviorDramaPlayBehaviorEnum是展示 / 播放 / 暂停 / 快进 / 快退 / 播放完成 / 分享;播放失败见下
playSpeedint否倍速 × 100,默认 100
dramaContentTypeDramaContentTypeEnum否默认 shortDrama(short_drama)
dramaTypeId 等String否分类 / 标签默认值同帖子事件
recommendTraceIdString否默认 ''

启用白名单时加入 drama_event。

播放失败沿用 drama_event,在播放器失败回调里上报,时长和进度填失败当时的实际值:

dart
playBehavior: DramaPlayBehaviorEnum('ep_failed', '播放失败'),

上报字段为 play_behavior_key: ep_failed、play_behavior_name: 播放失败。无需新常量或升级 SDK。也可用 trackEvent(eventName: 'drama_event', payload: {...}) 直接传这两个字符串。白名单仍是 drama_event。服务端需接受该取值。


6.2.3 剧集状态变更事件【客户端】 ​

dart
AnalyticsSdk.instance.track(DramaStatusChangeEvent(
  dramaId: 'drama_001',
  dramaTitle: '短剧标题',
  episode: 5,
  mediaId: 'media_005',
  videoId: 'video_005',
  preEpisode: 2, // 从第 2 集切到第 5 集
  dramaBehavior: DramaBehaviorEnum.EP_SWITCH,
));
字段类型必填说明
dramaIdString是剧集 ID
dramaTitleString是剧集名称
episodeint是当前第几集
mediaIdString是媒体资源 ID
videoIdString是当前集视频 ID
preEpisodeint是前集序号
dramaBehaviorDramaBehaviorEnum是自动播放 / 上下集 / 切换 / 解锁

启用白名单时加入 drama_status_change。drama_like / drama_comment / drama_collect / drama_purchase 由服务端上报。


6.3 小说阅读事件 ​

dart
AnalyticsSdk.instance.track(NovelEvent(
  novelId: 'novel_001',
  novelTitle: '斗破苍穹',
  novelTypeId: 'fantasy',
  novelTypeName: '玄幻',
  novelTagKey: 'hot,classic',
  novelTagName: '热门,经典',
  readProgress: 45,
  pageNo: 10,
  novelBehavior: ReadBehaviorEnum.PAGE_NEXT,
  chapterId: 'ch_001',       // 无章节时传 ''
  chapterName: '第一章 陨落的天才', // 无章节时传 ''
  mediaId: '',
));
字段类型必填说明
novelIdString是小说 ID
novelTitleString是小说标题
novelTypeIdString是分类 ID
novelTypeNameString是分类名称
novelTagKeyString是标签 KEY,多个用英文逗号分隔
novelTagNameString是标签名称,多个用英文逗号分隔
readProgressint是阅读进度百分比(0-100)
pageNoint是当前阅读第几页。普通小说从 1 开始;仅有声小说业务约定允许 0。注意:当前 SDK 校验器仍会移除 page_no = 0,本版未实现零页码端到端上报
novelBehaviorReadBehaviorEnum是阅读行为,见 ReadBehaviorEnum
chapterIdString否章节 ID,没有章节或未对接推荐引擎时传 '',默认 ''
chapterNameString否章节名称,没有章节或未对接推荐引擎时传 '',默认 ''
recommendTraceIdString否推荐引擎 trace ID,未接推荐引擎传 '',默认 ''
mediaIdString否媒体资源 ID,未使用老司机库资源时传 ''

6.4 漫画阅读事件 ​

dart
AnalyticsSdk.instance.track(ComicEvent(
  comicId: 'comic_001',
  comicTitle: '火影忍者',
  comicTypeId: 'battle',
  comicTypeName: '热血',
  comicTagKey: 'hot,classic',
  comicTagName: '热门,经典',
  readProgress: 60,
  pageNo: 5,
  comicBehavior: ReadBehaviorEnum.PAGE_NEXT,
  chapterId: 'ch_001',    // 无章节时传 ''
  chapterName: '第一话',  // 无章节时传 ''
));

字段与 NovelEvent 对称,将 novel* 前缀替换为 comic*,novelBehavior 替换为 comicBehavior。recommendTraceId、chapterId、chapterName 字段同样支持,默认均为 ''。


6.5 广告展示事件 (带自动去重) ​

SDK 会对 adId 进行页面级去重:同一页面访问内,相同 adId 只上报一次;页面退出后去重状态自动清除,下次进入该页面可重新上报。新 session 开始时所有页面的去重记录同步清空。

dart
AnalyticsSdk.instance.track(AdImpressionEvent(
  pageKey: AppPageEnum.home.key,
  pageName: AppPageEnum.home.name,
  adSlotKey: 'home_banner_1',
  adId: 'ad_001,ad_002,ad_003',
  creativeId: 'cr_001,cr_002,cr_003',
  adType: 'banner',   // 传广告类型字符串,不传默认空字符串
));
字段类型必填说明
pageKeyString是页面标识
pageNameString是页面名称
adSlotKeyString是广告位标识
adIdString是广告 ID,多个用英文逗号分隔
creativeIdString否素材 ID,多个用英文逗号分隔,与 adId 一一对应,默认 ''
adTypeString否广告类型字符串,如 'banner'、'feed'、'splash',默认 ''

去重逻辑:

  • 进入 TabA 展示 20 条广告 → 上报 20 条
  • 切换到 TabB 展示 20 条广告 → 上报 20 条
  • 返回 TabA 上滑新加载 10 条广告 → 上报新的 10 条
  • 再次下滑看到之前已展示的广告 → 全部被过滤

6.6 广告点击事件 ​

dart
AnalyticsSdk.instance.track(AdClickEvent(
  pageKey: AppPageEnum.home.key,
  pageName: AppPageEnum.home.name,
  adSlotKey: 'home_banner_1',
  adId: 'ad_001',
  creativeId: 'cr_001',
  adType: 'banner',
));

字段与 AdImpressionEvent 相同,adId 为单个广告 ID。


6.7 APP 广告行为事件 (内置弹窗/Banner 行为) ​

用于上报 APP 内置广告(弹窗、Banner 等)的展示、点击、关闭行为。

dart
AnalyticsSdk.instance.track(AdvertisingEvent(
  eventType: BannerEventEnum.CLICK.label,
  advertisingKey: 'home_popup',
  advertisingName: '首页弹窗',
  advertisingId: 'adv_001',
));
字段类型说明
eventTypeString行为类型:click/close/show
advertisingKeyString广告标识
advertisingNameString广告名称
advertisingIdString广告 ID

6.7.1 广告 SDK 生命周期事件 ​

广告 SDK 内部建议在对应生命周期自动调用这些事件,接入方不需要重复填写埋点参数。event 始终在外层,业务字段始终在 payload 内。如启用了事件白名单,eventList 需包含:

ad_sdk_init、ad_request、ad_fill、ad_no_fill、ad_load_success、ad_load_fail、ad_impression_sdk、ad_click_sdk、ad_close、ad_skip、ad_error。

dart
AnalyticsSdk.instance.track(AdRequestEvent(
  requestId: 'req_001',
  adSlotKey: 'splash_001',
));

AnalyticsSdk.instance.track(AdImpressionSdkEvent(
  requestId: 'req_001',
  adSlotKey: 'splash_001',
  adId: 'ad_001',
  adType: 'splash',
  materialSize: '1080x1920',
));

AnalyticsSdk.instance.track(AdErrorEvent(
  requestId: 'req_001',
  adSlotKey: 'splash_001',
  adType: 'splash',
  errorCode: 'TIMEOUT',
  errorMsg: '请求超时',
  errorStage: 'request',
));
事件类event说明
AdSdkInitEventad_sdk_init广告 SDK 初始化环境
AdRequestEventad_request发起广告请求
AdFillEventad_fill请求返回广告
AdNoFillEventad_no_fill请求无广告返回
AdLoadSuccessEventad_load_success素材加载成功
AdLoadFailEventad_load_fail素材加载失败
AdImpressionSdkEventad_impression_sdk可见达标曝光
AdClickSdkEventad_click_sdk广告点击
AdCloseEventad_close广告关闭
AdSkipEventad_skip广告跳过(含 trace_id、shown_duration)
AdErrorEventad_error请求、加载或渲染错误

AdSkipEvent 自 v1.4.4 起补齐 traceId / shownDuration,与关闭、曝光、点击等生命周期事件的归因字段对齐。 ad_impression_sdk 自 v1.4.6 起按 trace_id 在同一 Session 内去重:成功入队后才记入去重;新 Session 自动清空。

6.8 关键词搜索事件 ​

dart
AnalyticsSdk.instance.track(KeywordSearchEvent(
  keyword: '火影忍者',
  searchResultCount: 42,
  searchTraceId: '',       // 未接搜索引擎时传空字符串
  searchId: '',            // 可视范围内结果 ID,多个英文逗号分隔;未接搜索引擎时传空字符串
  searchContentType: 'post',  // video | novel | comic | post | short_drama;未接搜索推荐引擎时传 ''
));
字段类型必填说明
keywordString是搜索关键词
searchResultCountint是搜索结果数量
searchTraceIdString否搜索引擎 trace ID,未接搜索引擎时传 '',默认 ''
searchIdString否可视范围内的搜索结果 ID,多个用英文逗号分隔(不是搜索会话 ID);未接搜索引擎时传 '',默认 ''
searchContentTypeString否搜索列表类型:'video' / 'novel' / 'comic' / 'post' / 'short_drama';未接搜索推荐引擎时传 '',默认 ''

6.9 关键词点击事件 ​

dart
AnalyticsSdk.instance.track(KeywordClickEvent(
  keyword: '火影忍者',
  clickItemId: 'post_001',
  clickItemType: ClickItemTypeEnum.POST, // VIDEO | NOVEL | COMIC | POST | SHORT_DRAMA
  clickPosition: 1,
  searchTraceId: '',  // 未接搜索引擎时传空字符串
));
字段类型必填说明
keywordString是搜索关键词
clickItemIdString是点击项目 ID(视频 / 小说 / 漫画 / 帖子 / 短剧 ID)
clickItemTypeClickItemTypeEnum是点击项目类型:VIDEO / NOVEL / COMIC / POST / SHORT_DRAMA
clickPositionint是点击位置(搜索结果中的排序位置,从 1 开始)
searchTraceIdString否搜索引擎 trace ID,未接搜索引擎时传 '',默认 ''

6.10 推荐列表展示事件 ​

dart
AnalyticsSdk.instance.track(RecommendListViewEvent(
  pageKey: AppPageEnum.home.key,
  pageName: AppPageEnum.home.name,
  recommendContentType: RecommendContentTypeEnum.video,
  recommendTraceId: 'rec_trace_abc123',
  recommendId: 'vid_001,vid_002,vid_003',
  recommendTraceInfo: '{"source":"server"}',  // 未接推荐引擎传 ''
  clientVersion: '1.2.3',                     // 未接推荐引擎传 ''
));
字段类型必填说明
pageKeyString是页面标识
pageNameString是页面名称
recommendContentTypeRecommendContentTypeEnum是推荐内容类型,见 RecommendContentTypeEnum
recommendTraceIdString是推荐引擎 trace ID
recommendIdString是可视窗口内的内容 ID,多个用英文逗号分隔
recommendTraceInfoString否推荐引擎 trace info,未接推荐引擎传 '',默认 ''
clientVersionString否客户端版本号,如 '1.2.3',默认 ''

6.11 推荐列表点击事件 ​

dart
AnalyticsSdk.instance.track(RecommendListClickEvent(
  pageKey: AppPageEnum.home.key,
  pageName: AppPageEnum.home.name,
  recommendContentType: RecommendContentTypeEnum.video,
  recommendTraceId: 'rec_trace_abc123',
  recommendId: 'vid_001',
  recommendTraceInfo: '{"source":"server"}',  // 未接推荐引擎传 ''
  clientVersion: '1.2.3',                     // 未接推荐引擎传 ''
));
字段类型必填说明
pageKeyString是页面标识
pageNameString是页面名称
recommendContentTypeRecommendContentTypeEnum是推荐内容类型,见 RecommendContentTypeEnum
recommendTraceIdString是推荐引擎 trace ID
recommendIdString是被点击内容的 ID
recommendTraceInfoString否推荐引擎 trace info,未接推荐引擎传 '',默认 ''
clientVersionString否客户端版本号,如 '1.2.3',默认 ''

6.12 页面内元素曝光事件【客户端】 ​

适用范围:所有项目组可选接入。

dart
AnalyticsSdk.instance.track(AppPageItemExposureEvent(
  itemId: 'item_001',
  itemType: 'recommend_video',
  itemName: '推荐视频',
  parentItemId: 'module_001',
  pageKey: AppPageEnum.home.key,
  pageName: AppPageEnum.home.name,
  referrerPageKey: AppPageEnum.discover.key,
  referrerPageName: AppPageEnum.discover.name,
  recommendTraceId: 'rec_trace_abc123',
  recommendId: 'vid_001',
  recommendTraceInfo: '{"source":"server"}',
));
字段类型必填说明
itemIdString是元素唯一 ID
itemTypeString是元素类型字典值,如推荐、关注、评论、小说、视频、漫画等
itemNameString是元素名称字典值,如推荐、关注、评论、小说、视频、漫画等
parentItemIdString是父元素 ID
pageKeyString是当前页面标识
pageNameString是当前页面名称
referrerPageKeyString是来路页面标识,无来路时传 ''
referrerPageNameString是来路页面名称,无来路时传 ''
recommendTraceIdString否推荐引擎 trace ID,多个推荐列表用英文逗号分隔,未接推荐引擎传 ''
recommendIdString否推荐内容 ID,未接推荐引擎传 ''
recommendTraceInfoString否推荐引擎 trace info,未接推荐引擎传 ''

6.13 页面内元素点击事件【客户端】 ​

适用范围:所有项目组可选接入。

dart
AnalyticsSdk.instance.track(AppPageItemClickEvent(
  itemId: 'item_001',
  itemType: 'comment',
  itemName: '评论',
  parentItemId: 'module_001',
  position: 0,
  pageKey: AppPageEnum.videoDetail.key,
  pageName: AppPageEnum.videoDetail.name,
  recommendTraceId: '',
  recommendId: '',
  recommendTraceInfo: '',
));
字段类型必填说明
itemIdString是元素唯一 ID
itemTypeString是点击元素类型字典值,如推荐、关注、评论、小说、视频、漫画等
itemNameString是点击元素名称字典值,如推荐、关注、评论、小说、视频、漫画等
parentItemIdString是父元素 ID
positionint是在父元素中的顺序索引,按业务约定可从 0 或 1 开始
pageKeyString是当前页面标识
pageNameString是当前页面名称
recommendTraceIdString否推荐引擎 trace ID,多个推荐列表用英文逗号分隔,未接推荐引擎传 ''
recommendIdString否推荐内容 ID,未接推荐引擎传 ''
recommendTraceInfoString否推荐引擎 trace info,未接推荐引擎传 ''

6.14 强制立即上报 ​

dart
await AnalyticsSdk.instance.flush();

6.15 释放资源 ​

dart
await AnalyticsSdk.instance.dispose();

7. 枚举参考 ​

AppPageEnum(页面路由) ​

预定义页面路由常量,所有需要传 page_key / page_name 的地方均可使用,也可直接传自定义字符串。

dart
// 用法示例
AnalyticsSdk.instance.trackNavigation(
  pageKey: AppPageEnum.videoDetail.key,
  pageName: AppPageEnum.videoDetail.name,
);

公共类 ​

枚举值keyname
AppPageEnum.launchlaunch启动页
AppPageEnum.guideguide引导页
AppPageEnum.homehome首页
AppPageEnum.mainPagemain_page主页
AppPageEnum.discoverdiscover发现页
AppPageEnum.searchsearch搜索页
AppPageEnum.searchResultsearch_result搜索结果页
AppPageEnum.appCenterapp_center应用中心
AppPageEnum.communitycommunity社区页
AppPageEnum.squaresquare广场页
AppPageEnum.minemine我的
AppPageEnum.userCenteruser_center个人中心
AppPageEnum.userHomeuser_home用户主页
AppPageEnum.authorHomeauthor_home作者主页
AppPageEnum.loginlogin登录页
AppPageEnum.registerregister注册页
AppPageEnum.messageCentermessage_center消息中心
AppPageEnum.settingsetting设置页
AppPageEnum.rechargerecharge充值页
AppPageEnum.rechargeRecordrecharge_record充值记录页
AppPageEnum.mineCollectmine_collect我的收藏
AppPageEnum.mineHistorymine_history我的历史
AppPageEnum.mineVipmine_vip会员中心页

视频类 ​

枚举值keyname
AppPageEnum.videoListvideo_list视频列表页
AppPageEnum.videoCategoryvideo_category视频分类页
AppPageEnum.videoRankvideo_rank视频榜单页
AppPageEnum.videoSearchvideo_search视频搜索页
AppPageEnum.videoDetailvideo_detail视频详情页
AppPageEnum.videoPlayvideo_play视频播放页

小说类 ​

枚举值keyname
AppPageEnum.novelListnovel_list小说列表页
AppPageEnum.novelCategorynovel_category小说分类页
AppPageEnum.novelRanknovel_rank小说榜单页
AppPageEnum.novelDetailnovel_detail小说详情页
AppPageEnum.novelReadnovel_read小说阅读页
AppPageEnum.novelChapterListnovel_chapter_list小说章节页

漫画类 ​

枚举值keyname
AppPageEnum.comicListcomic_list漫画列表页
AppPageEnum.comicCategorycomic_category漫画分类页
AppPageEnum.comicRankcomic_rank漫画榜单页
AppPageEnum.comicDetailcomic_detail漫画详情页
AppPageEnum.comicReadcomic_read漫画阅读页
AppPageEnum.comicChapterListcomic_chapter_list漫画章节页

不在列表中的页面直接传字符串即可,无需扩展枚举:

dart
AnalyticsSdk.instance.trackNavigation(pageKey: 'live_room', pageName: '直播间');

DeviceEnum ​

枚举值label说明
DeviceEnum.androidAndroidAndroid 设备
DeviceEnum.iOSiOSiOS 设备
DeviceEnum.pcPCPC 设备

UserTypeEnum ​

枚举值label说明
UserTypeEnum.normalnormal普通用户
UserTypeEnum.vipvipVIP 用户

AdTypeEnum ​

已废弃:adType 字段已改为普通 String,直接传字符串即可,如 'banner'、'feed'、'splash'。无需 import 枚举类。

VideoEventEnum ​

枚举值key说明
VideoEventEnum.VIDEO_VIEWvideo_view展示
VideoEventEnum.VIDEO_PLAYvideo_play播放
VideoEventEnum.VIDEO_PAUSEvideo_pause暂停
VideoEventEnum.VIDEO_SHAREvideo_share分享
VideoEventEnum.VIDEO_COMPLETEvideo_complete播放完成
VideoEventEnum.VIDEO_FORWARDvideo_forward快进
VideoEventEnum.VIDEO_REWINDvideo_rewind快退
VideoEventEnum('video_failed', '播放失败')video_failed播放失败(自定义字符串,无预定义常量)

VideoContentTypeEnum ​

枚举值label说明
VideoContentTypeEnum.videovideo长视频
VideoContentTypeEnum.shortVideoshort_video短视频
VideoContentTypeEnum.postVideopost_video帖子视频

PostContentTypeEnum ​

枚举值label说明
PostContentTypeEnum.postpost图文帖
PostContentTypeEnum.videoPostvideo_post视频帖

PostEventEnum ​

枚举值key说明
PostEventEnum.VIEWview展示

DramaContentTypeEnum ​

枚举值label说明
DramaContentTypeEnum.shortDramashort_drama短剧

DramaPlayBehaviorEnum ​

枚举值key说明
DramaPlayBehaviorEnum.EP_VIEWep_view展示
DramaPlayBehaviorEnum.EP_PLAYep_play播放
DramaPlayBehaviorEnum.EP_PAUSEep_pause暂停
DramaPlayBehaviorEnum.EP_FORWARDep_forward快进
DramaPlayBehaviorEnum.EP_REWINDep_rewind快退
DramaPlayBehaviorEnum.EP_COMPLETEep_complete播放完成
DramaPlayBehaviorEnum.EP_SHAREep_share分享
DramaPlayBehaviorEnum('ep_failed', '播放失败')ep_failed播放失败(自定义字符串,无预定义常量)

DramaBehaviorEnum ​

枚举值key说明
DramaBehaviorEnum.EP_AUTO_PLAYep_auto_play自动播放
DramaBehaviorEnum.EP_PREVIOUSep_previous上一集
DramaBehaviorEnum.EP_NEXTep_next下一集
DramaBehaviorEnum.EP_SWITCHep_switch切换剧集
DramaBehaviorEnum.EP_UNLOCKep_unlock解锁

ReadBehaviorEnum ​

枚举值key说明
ReadBehaviorEnum.VIEWview展示
ReadBehaviorEnum.PAGE_NEXTpage_next下一页
ReadBehaviorEnum.PAGE_PREVpage_prev上一页
ReadBehaviorEnum.COMPLETEcomplete读完

ClickItemTypeEnum ​

枚举值label说明
ClickItemTypeEnum.VIDEOvideo视频
ClickItemTypeEnum.NOVELnovel小说
ClickItemTypeEnum.COMICcomic漫画
ClickItemTypeEnum.POSTpost帖子
ClickItemTypeEnum.SHORT_DRAMAshort_drama短剧

RecommendContentTypeEnum ​

枚举值label说明
RecommendContentTypeEnum.videovideo视频
RecommendContentTypeEnum.novelnovel小说
RecommendContentTypeEnum.comiccomic漫画

BannerEventEnum ​

枚举值label说明
BannerEventEnum.CLICKclick点击
BannerEventEnum.CLOSEclose关闭
BannerEventEnum.SHOWshow展示

8. SdkConfig 全局参数 ​

在 AnalyticsSdk.instance.init() 之前调用 SdkConfig.configure(...) 覆盖默认值:

dart
SdkConfig.configure(
  uploadInterval: const Duration(seconds: 5),
  autoUploadThreshold: 10,
  maxBatchSize: 200,
  connectionTimeout: const Duration(seconds: 15),
);
参数默认值说明
maxQueueSize5000内存队列最大容量,超过后新事件丢弃
autoUploadThreshold10队列达到此数量时立即触发上报
maxBatchSize500单次上报最大事件数
uploadIntervalDuration(seconds: 5)定时上报间隔
clickThrottleDurationDuration(milliseconds: 300)点击事件节流时间
connectionTimeoutDuration(seconds: 10)HTTP 连接超时
readTimeoutDuration(seconds: 30)HTTP 读取超时
speedTestTimeoutDuration(seconds: 5)域名测速超时
maxCacheLines2000缓存文件最大行数
maxCacheBytes2 * 1024 * 1024 (2MB)缓存文件最大字节数
maxSingleEventSize300 * 1024 (300KB)单条事件最大字节数,超过丢弃
maxAdImpressionCapacity10000广告去重集合最大容量(FIFO 淘汰)

恢复所有默认值:

dart
SdkConfig.reset();

9. 上报数据格式 ​

9.1 公共字段 ​

每个事件上报时,顶层 JSON 自动携带以下公共字段:

字段名类型说明
eventString事件类型,如 video_event、ad_impression
channelString渠道标识
app_idString应用 ID
uidString用户 ID
sidString会话 ID,由客户端 SDK 自动生成;服务端上报事件时可通过 getDeviceCommonFields() 或 getParams() 获取并回传,以归属同一会话
client_tsint事件创建时间戳(10位,秒)
deviceString设备类型(自动采集)
device_idStringSDK 内部生成的设备标识
device_brandString设备品牌(自动采集)
device_modelString设备型号(自动采集)
user_agentString客户端标识(自动采集)
system_nameString操作系统名称(自动采集)
system_versionString操作系统版本(自动采集)
device_fingerprintString首次生成并持久化的设备指纹,后续初始化读取保存值
fp_versionString指纹规则版本,当前为 4.0.0
sdk_versionStringSDK 版本号(自动写入;当前常量为 1.5.3)
app_versionString应用版本号(由接入方在 init() 中传入,未传默认 '1.0.0')
event_sig_versionString可选。配置事件签名密钥后自动生成,当前默认 2.0.0
event_sigString可选。配置事件签名密钥后自动生成的 HMAC-SHA256 签名
event_idString事件去重 ID(MD5,SDK 自动生成;1100ms 内重复 ID 将被防抖丢弃)
payloadObject事件业务字段

9.2 上报数据格式 ​

json
{
  "event": "video_event",
  "channel": "google_play",
  "app_id": "your_app_id",
  "uid": "12345",
  "sid": "sess_abc123",
  "client_ts": 1704067200,
  "device": "Android",
  "device_id": "xxxx",
  "device_brand": "Samsung",
  "device_model": "SM-G991B",
  "user_agent": "...",
  "system_name": "Android",
  "system_version": "14.0",
  "device_fingerprint": "sha256-hex-xxxx",
  "fp_version": "4.0.0",
  "sdk_version": "1.3.0",
  "app_version": "2.3.1",
  "event_sig_version": "2.0.0",
  "event_sig": "hmac-sha256-hex-signature",
  "event_id": "md5-hash-for-dedup",
  "payload": {
    "video_id": "vid_001",
    ...
  }
}

9.3 字段校验规则 ​

SDK 在事件入队前会自动校验并修正字段。关键字段不合规时整个事件被静默丢弃;可纠正字段超限时自动截断或补全,不影响上报。

关键字段包括:event、event_id、app_id、sid、client_ts、device(提供了无法识别的值时)、device_id、payload(类型错误时),以及 payload 中的核心业务 ID 字段(video_id / novel_id / comic_id / click_item_id)。

通用类型处理规则

  • 所有字符串字段在校验前均会自动 trim()(去除首尾空格)。
  • 非字符串类型(如 int、bool、null 等)传入字符串字段时视为类型错误,等同于未传该字段(关键字段 → 丢弃事件;可选字段 → 移除该字段)。
  • 数字字段(百分比 / 位置 / 数量 / 时间戳类)支持 int、double 以及纯数字字符串三种类型,均可正常解析。

公共字段 ​

字段规则不合规处理
event小写字母/数字/下划线,最大 64 字符丢弃事件
event_id字母/数字,1~32 字符(SDK 自动生成)丢弃事件
app_id非空,最大 64 字符空时丢弃事件;超长截断
channel可选,最大 128 字符超长截断
uid可选;非空时业务约定长度 ≥ 4(宿主保证);最大 128 字符超长截断
sid自动生成;为空时 SDK 自动填充会话 ID生成失败则丢弃事件
client_ts整数,范围 946684800 ~ 4102444800(2000 ~ 2100 年)丢弃事件
device仅 Android / iOS / PC,大小写自动纠正丢弃事件
device_idSDK 内部生成;非空,最大 128 字符空时丢弃事件;超长截断
device_fingerprintSDK 管控字段,不接受外部覆盖改为 SDK 内部生成值;内部无值时清空
fp_versionSDK 管控字段,不接受外部覆盖改为当前规则版本 4.0.0
sdk_version可选,最大 128 字符(SDK 自动写入)超长截断
app_version可选,最大 128 字符;传入值须符合 x.y.z 格式,格式非法时 Debug 打印错误并忽略,Release 降级为 '1.0.0'超长截断
event_sig可选,64 位小写十六进制 HMAC-SHA256;由 SDK 自动生成已存在但格式非法时置空;缺失时不会在校验阶段默认补齐
event_sig_version可选,最大 128 字符;由 SDK 自动生成超长截断

payload 业务字段 ​

类别代表字段规则不合规处理
字段名所有 key^[A-Za-z0-9_]+$移除该字段
关键 IDvideo_id / novel_id / comic_id / click_item_id非空丢弃事件
普通 IDad_id / recommend_id 等非空移除该字段
逗号分隔ad_id / novel_tag_key / video_tag_name 等自动清理各段首尾空格、过滤空段;清洗后仍为空则移除该字段移除该字段
可选字符串creative_id / recommend_trace_id 等可为空空字符串原样保留
名称字段page_name / ad_name / video_name 等最大 128 字符截断
标题字段title / content_title / search_keyword 等最大 256 字符截断
关键词字段keywords / tags / description最大 500 字符截断
百分比字段progress / completion_rate / view_ratio整数,0 ~ 100移除该字段
位置字段position / page_no / click_position整数,position 允许 ≥ 0;page_no / click_position ≥ 1(有声小说业务约定 page_no=0,但当前校验器仍会移除 0)移除该字段
数字字段duration / count / amount 等整数,≥ 0移除该字段
时间戳字段start_ts / end_ts / publish_ts同 client_ts 范围移除该字段
其他字段—字符串最大 500 字符;非字符串原样保留截断

9.4 设备指纹(device_fingerprint / fp_version) ​

SDK 每次 init() 先读取持久化指纹;仅在没有记录时生成并保存。业务方无需、也不应手动传入。device_fingerprint 与 fp_version 都是 SDK 管控字段,外部事件 Map 中传入的值不会生效。

首次生成的采集因子 ​

平台参与计算的因子
Androidos_family、brand、manufacturer、model、abi、screen_width、screen_height、screen_dpi、device_pixel_ratio、cpu_cores、device_memory_gb、build_hardware、build_product、build_fingerprint、has_baseband、id_source、id_value
iOSos_family、ios_device_model_raw、screen_width、screen_height、screen_dpi、device_pixel_ratio、cpu_cores、device_memory_gb、id_source、id_value
Webos_family、browser_kernel、browser_platform、screen_width、screen_height、screen_dpi、device_pixel_ratio、color_depth、max_touch_points、hardware_concurrency、cookie_enabled、local_storage_enabled、session_storage_enabled、indexeddb_enabled、service_worker_enabled、canvas_fp、webgl_fp、audio_fp、fonts_hash、plugins_hash、mimes_hash

Android 按 oaid → aaid → android_id → guid 选第一个非空字段,将字段名和值分别放入 id_source / id_value;全空时使用 none / 空字符串。iOS 使用 idfv,缺失时使用 none / 空字符串。桌面原生端沿用 Android 字段集合,缺失值使用默认值。

language 和 timezone 不参与指纹计算,但仍作为环境字段正常采集和上报。网络、电量、页面、行为也不参与。

生成与持久化规则 ​

  1. 先读取持久化记录;已有非空指纹时直接使用,不因设备参数、语言、时区或系统版本变化而重新计算。
  2. 没有记录时,按上表固定顺序构造 key=value,保留字段名和空值;字符串去掉首尾空格并转小写,数值转整数,无法转换时为 -1。
  3. 使用 | 连接字段,在前面加上 fp_v4|fingerprint_platform=android/ios/web|(平台取其一),对 UTF-8 字节做 SHA-256,输出 64 个小写十六进制字符。
  4. 将生成值持久化,回读确认成功后保存到 SdkContext,供本次运行的事件使用。后续每次 init() 都重新读取持久化记录,包括同一进程内重复初始化。

存储位置:

  • Native:applicationDocumentsDirectory/analytics_device_fingerprint.txt
  • Web:localStorage['analytics_device_fingerprint.txt']

clearCache() 只清除旧兼容入口的内存缓存,不删除已持久化的指纹。应用数据保留期间指纹保持不变;卸载、清除应用数据或浏览器站点存储后需要重新生成。

device_fingerprint 不参与 event_id 的计算。

失败兜底 ​

生成或持久化失败时返回空字符串,事件照常上报。未确认保存成功的新指纹不会用于事件;后续初始化可再次尝试。

fp_version 与升级 ​

当前指纹规则版本为 "4.0.0",由 SDK 内部维护,不支持业务方覆盖。旧 v3 指纹没有独立持久化记录,升级后的首次初始化会生成并保存 v4 指纹,此后持续复用。旧兼容入口的 UUID 文件 / localStorage 记录不作为 v4 指纹使用。

服务端应接受 fp_version = 4.0.0;指纹不再要求与每次启动快照重算结果相同。

9.5 事件签名(event_sig / event_sig_version) ​

当 encryptedConfig 下发 eventSignature.signKey(或兼容字段 signKey / eventSignKey / event_signature_key / event_sig_key)后,SDK 会为客户端事件在顶层追加:

  • event_sig_version:签名规则版本,默认 2.0.0
  • event_sig:HMAC-SHA256 签名,64 位小写十六进制字符串

签名输入固定为:

text
event|event_id|app_id|client_ts|device_fingerprint

device_fingerprint、fp_version、event_sig、event_sig_version 均不参与 event_id 计算。SDK 会优先在事件入队前签名;如果事件入队时签名配置尚未就绪,会在真正请求接口前对缺失 event_sig 的事件再次尝试补签。已有非空 event_sig 的事件不会被覆盖。


10. 调试工具 ​

10.1 AnalyticsDebugBanner(悬浮调试条) ​

在 init() 时传 enableDebugBanner: true,然后将 AnalyticsDebugBanner() 放入 widget 树顶部(通常是页面 Column 的第一个子节点或 Stack 的顶层):

dart
await AnalyticsSdk.instance.init(
  // ...
  enableDebugBanner: true,
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            const AnalyticsDebugBanner(),  // 无需任何参数
            Expanded(child: YourContent()),
          ],
        ),
      ),
    );
  }
}

调试条显示 7 个步骤:①域名配置 ②SDK初始化 ③域名列表 ④域名测速 ⑤上报地址 ⑥待上报队列 ⑦最近上报。release 包自动隐藏,无性能影响。

10.2 getDebugSteps / getDebugInfo ​

dart
final steps = AnalyticsSdk.instance.getDebugSteps();
for (final step in steps) {
  print('${step['name']}: ${step['status']} - ${step['detail']}');
}

final info = AnalyticsSdk.instance.getDebugInfo();
print(info['inited']);
print(info['queueLength']);
print(info['reportUrl']);
print(info['debugReportEventLogPath']); // Native debug 包完整事件 JSONL 文件路径

10.3 validateEncryptedConfig ​

dart
final result = AnalyticsSdk.instance.validateEncryptedConfig(encryptedString);
if (result['success'] == true) {
  print('域名数量: ${result['domainCount']}');
  print('事件类型数量: ${result['eventCount']}');
} else {
  print('验证失败: ${result['error']}');
}

仅 debug 包可用,release 包返回错误。

10.4 完整事件 JSONL 文件 ​

Native debug 包会自动把所有“已通过校验并进入上报队列”的完整事件 JSON 追加到 applicationDocumentsDirectory/data_plus_report_events_debug.jsonl, 每行一个 JSON。被字段校验、事件白名单、单事件大小限制或 event_id 防抖丢弃的事件不会写入该文件。 文件不会因上报成功被删除,适合让业务方直接提供 debug 包运行后的取证文件。

SDK 初始化后会在日志中打印完整路径,也可通过 AnalyticsSdk.instance.getDebugInfo()['debugReportEventLogPath'] 获取。Android debug 包可用包名拉取:

bash
adb exec-out run-as <package_name> cat app_flutter/data_plus_report_events_debug.jsonl > data_plus_report_events_debug.jsonl

10.5 Logger ​

dart
import 'package:analytics_sdk/utils/logger.dart';

Logger.enabled = false;  // 关闭所有日志
Logger.enabled = true;   // 开启日志(debug 包默认已开启)

所有通过字段校验、事件白名单、大小检查和 event_id 防抖,并真正进入上报队列的事件, 都会以 accepted event json: 打印完整事件 JSON;同一批事件也会写入 debug JSONL 文件。 日志会保留一份 dart:developer.log,同时输出到 Flutter 控制台。Android logcat 中可按内容过滤 [AnalyticsSdk],超长 JSON 会自动分片输出, 避免单行截断: init() 和 refreshDomainConfig() 传入 encryptedConfig 时,也会分别打印 init encryptedConfig ... / refreshDomainConfig encryptedConfig ... 的解密明文 (敏感字段脱敏)和解析结果。

bash
adb logcat -v time | grep AnalyticsSdk
adb logcat -v time -s flutter

profile/release 包默认不输出 SDK 日志,如需给业务方排查问题,请在初始化前临时设置 Logger.enabled = true。


11. 高级用法 ​

11.1 延迟传入加密配置 ​

dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await AnalyticsSdk.instance.init(
    appId: 'your_app_id',
    encryptedConfig: null,     // SDK 尝试读取磁盘缓存域名
  );

  runApp(const MyApp());

  // 异步获取,不阻塞 UI 启动
  _fetchAndApplyConfig();
}

Future<void> _fetchAndApplyConfig() async {
  try {
    final config = await yourApiClient.fetchEncryptedConfig();
    await AnalyticsSdk.instance.refreshDomainConfig(encryptedConfig: config);
  } catch (_) {}
}

11.2 获取公共参数(getParams) ​

getParams() 可获取 SDK 当前全部公共参数,适合服务端拼装事件或埋点调试:

dart
// 获取全部参数
final all = AnalyticsSdk.getParams();

// 只取指定字段
final partial = AnalyticsSdk.getParams(['app_id', 'uid', 'device_id', 'device_fingerprint']);

返回 Map<String, dynamic>,包含:app_id、channel、uid、user_type、sid、sdk_version、app_version、device、device_id、device_brand、device_model、user_agent、system_name、system_version、device_fingerprint、fp_version。

sid 说明:sid(会话 ID)由客户端 SDK 自动生成,服务端上报事件时需将其一并回传,以确保服务端事件与客户端事件归属同一会话。可通过 getDeviceCommonFields() 直接获取,也可通过 getParams(keys: ['sid']) 单独获取。

如果 key 不存在,该 key 不会出现在返回 Map 中(静默忽略)。

11.3 服务端复用设备公共字段 ​

dart
final deviceFields = AnalyticsSdk.getDeviceCommonFields();
// 包含:sid, device, device_id, device_brand, device_model,
//       user_agent, system_name, system_version, device_fingerprint, fp_version

服务端事件直接复用时,请将上述 Map 中的 sid 与设备字段一并传回;无需再额外读取 getParams(['sid'])。

11.4 广告 SDK 复用初始化信息 ​

广告 SDK 可直接从埋点 SDK 读取初始化所需快照与当前会话 ID:

dart
// 当前会话 ID(未初始化或异常时返回空字符串)
final sid = AnalyticsSdk.instance.getSessionId();

// 广告 SDK 初始化参数快照(按协议分组)
final adInit = AnalyticsSdk.instance.getAdSdkInitInfo();
// adInit['media_info']            → channel, app_version
// adInit['device_id_info']        → device_id, device_fingerprint
// adInit['device_hardware_info']  → device_type/brand/model、系统、屏幕、方向、user_agent
// adInit['network_info']          → network_type, carrier, language, timezone

分组字段仅供广告 SDK 接入复用,不作为对外通用埋点字段约定;网络类型取值与 sdk_init / network_state_change 一致(不含 cellular / offline)。


12. 完整集成示例 ​

dart
import 'package:flutter/material.dart';
import 'package:analytics_sdk/analytics_sdk.dart';
import 'package:analytics_sdk/config/sdk_config.dart';
import 'package:analytics_sdk/enum/user_type_enum.dart';
import 'package:analytics_sdk/enum/video_content_type_enum.dart';
import 'package:analytics_sdk/enum/video_event_enum.dart';
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/widget/global_click_wrapper.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  SdkConfig.configure(
    uploadInterval: const Duration(seconds: 5),
    autoUploadThreshold: 10,
  );

  PageNameMapper.addMappings({
    'home': '首页',
    'video_detail': '视频详情页',
    'user_center': '个人中心',
    'search': '搜索页',
  });

  await AnalyticsSdk.instance.init(
    appId: 'your_app_id',
    encryptedConfig: 'YOUR_BASE64_ENCRYPTED_CONFIG',
    channel: 'google_play',
    uid: '',
    enableDebugBanner: true,
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GlobalClickWrapper(
      child: MaterialApp(
        navigatorObservers: [
          AnalyticsSdk.instance.pageObserver,
        ],
        initialRoute: '/',
        routes: {
          '/': (context) => const HomePage(),
          '/video_detail': (context) => const VideoDetailPage(),
        },
      ),
    );
  }
}

// ——— 登录场景 ———
void onLoginSuccess(String userId, bool isVip) {
  AnalyticsSdk.setUserIdAndType(
    userId: userId,
    userTypeEnum: isVip ? UserTypeEnum.vip : UserTypeEnum.normal,
  );
}

// ——— 视频播放场景 ———
void onVideoPlay(String videoId, String title) {
  AnalyticsSdk.instance.track(VideoEvent(
    videoId: videoId,
    videoTitle: title,
    videoTypeId: 'action',
    videoTypeName: '动作片',
    videoTagKey: 'hot',
    videoTagName: '热门',
    videoDuration: 7200,
    playDuration: 0,
    playProgress: 0,
    videoBehavior: VideoEventEnum.VIDEO_PLAY,
    videoContentType: VideoContentTypeEnum.video,
  ));
}

// ——— 广告展示场景 ———
void onAdsRendered(List<String> adIds) {
  AnalyticsSdk.instance.track(AdImpressionEvent(
    pageKey: AppPageEnum.home.key,
    pageName: AppPageEnum.home.name,
    adSlotKey: 'home_feed_ad',
    adId: adIds.join(','),
    adType: 'feed',
  ));
}

// ——— 应用退出场景 ———
Future<void> onAppExit() async {
  await AnalyticsSdk.instance.flush();
  await AnalyticsSdk.instance.dispose();
}

13. 桌面平台接入(macOS / Windows) ​

13.1 支持情况 ​

平台Dart 层本地持久化HTTP 上报设备信息采集
macOS✅✅ path_provider✅ 需配置 entitlements✅ device_info_plus
Windows✅✅ path_provider✅✅ device_info_plus

13.2 macOS 必要配置 ​

macOS 沙箱默认禁止出站网络连接。使用 analytics_sdk 的 macOS App 必须在 entitlements 文件中声明出站网络权限,否则所有事件上报请求会被系统静默拦截。

macos/Runner/DebugProfile.entitlements 和 macos/Runner/Release.entitlements 均需添加:

xml
<key>com.apple.security.network.client</key>
<true/>

完整的 Release.entitlements 示例:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
</dict>
</plist>

13.3 Windows 配置 ​

Windows 无需额外配置,正常集成即可。

13.4 device_id 生成 ​

device_id 不信任外部传入值,由 SDK 内部生成:

  • Android:使用 DeviceIdentifier.getClientId()
  • iOS:使用 IDFV
  • Web / macOS / Windows / Linux:首次启动生成 UUID v4,并通过 SDK 本地存储持久化

旧版本接入代码即使仍传入 deviceId,SDK 也会忽略外部值并以上述内部生成值作为公共字段 device_id。


版本更新说明 ​

v1.5.3(2026-09-29) ​

  • 主包与兼容包同步发布;sdk_version = 1.5.3,fp_version = 4.0.0。
  • 修复 Safari 不支持电量 API 时初始化中断、设备指纹为空的问题;可选采集失败 / 超时降级,公共 API 不变。
  • 从 1.5.2 升级无需改调用代码。
  • 文档补充:视频播放失败用 VideoEventEnum('video_failed', '播放失败');剧集播放失败用 DramaPlayBehaviorEnum('ep_failed', '播放失败')。pageObserver 与 sessionObserver 二选一。

完整变更见 更新日志。

v1.5.2(2026-09-19) ​

  • 主包与兼容包同步发布;sdk_version = 1.5.2,fp_version = 4.0.0。
  • PostEvent Dart 参数由 createTime 改为 postCreateTime(源码不兼容,旧调用无法编译)。
  • JSON 仍只输出 / 读取 post_create_time;后台与手写 payload 不用改。

完整变更见 更新日志。

v1.5.1(2026-09-18) ​

  • 主包与兼容包同步发布;sdk_version = 1.5.1,fp_version = 4.0.0。
  • 帖子创建时间 payload 字段改为 post_create_time;Dart 参数 createTime 保持兼容。
  • 约定:非空 uid 长度 ≥ 4;小说 page_no=0 仅有声小说(本版校验器仍会移除 0)。

完整变更见 更新日志。

v1.5.0(2026-09-17) ​

  • 主包与兼容包同步发布;sdk_version = 1.5.0,fp_version = 4.0.0。
  • 基于已交付的 1.4.9 工作版本同步版本与归档,无进一步功能改动;已持久化 v4 指纹继续复用。
  • 继续包含剧集 ep_complete / ep_share,以及关键词搜索 / 点击的 post / short_drama 取值。

完整变更见 更新日志。

v1.4.9(2026-09-17) ​

  • 主包与兼容包同步重新交付(release_revision = 20260917);sdk_version = 1.4.9,fp_version = 4.0.0。
  • 设备指纹首次生成后持久化;去掉 language / timezone 参与计算;重复 init() 从持久化恢复指纹。
  • DramaPlayBehaviorEnum 新增 EP_COMPLETE / EP_SHARE;ClickItemTypeEnum 新增 POST / SHORT_DRAMA;KeywordSearchEvent.searchContentType 支持 post / short_drama。
  • 事件名与字段类型保持兼容;服务端需接受 fp_version = 4.0.0 及上述新增取值。

完整变更见 更新日志。