Appearance
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. 概述
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_version4.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 完整参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
appId | String | 是 | — | 应用 ID |
encryptedConfig | String? | 是(可传 null) | null | 加密配置字符串,见下方格式说明 |
channel | String? | 否 | '' | 渠道标识,可后续通过 setChannel 更新 |
uid | String? | 否 | '' | 用户 ID;未登录可传空;非空时长度须 ≥ 4(由宿主保证,SDK 不额外校验)。登录后通过 setUserIdAndType 更新 |
deviceId | String? | 否 | null | 已废弃:外部传入会被忽略,device_id 由 SDK 内部生成。服务端事件请通过 getParams / getDeviceCommonFields 获取公共参数后再上报。 |
appVersion | String? | 否 | '1.0.0' | 应用版本号,需符合 x.y.z 格式(如 '2.3.1')。格式不合规时:Debug 模式打印错误日志并忽略传入值;Release 模式静默降级为 '1.0.0'。未传时默认 '1.0.0' |
enableDebugBanner | bool | 否 | 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,
);| 参数 | 类型 | 说明 |
|---|---|---|
userId | String | 用户 ID;未登录可传空,非空时长度须 ≥ 4(由宿主保证) |
userTypeEnum | UserTypeEnum? | 用户类型,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.currentPageKeyNavigationEvent:导航行为记录
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.currentPageKeyNavigationEvent:导航行为记录
⚠️ 数据侧注意:若之前已接入
TabBar.withAnalytics(),升级后服务端会新增来自 TabBar 切换的app_page_view事件,请提前告知数据侧避免指标异常。
返回值实现了 PreferredSizeWidget,可直接用于 AppBar.bottom:
dart
AppBar(
bottom: TabBar(...).withAnalytics(tabs: [...]),
)AnalyticsTab 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
key | String | 是 | Tab 唯一标识,建议使用 AppPageEnum.xxx.key,也可传自定义字符串 |
name | String? | 否 | 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',
));| 字段 | 类型 | 说明 |
|---|---|---|
traceId | String | 落地页生成并写入剪贴板的唯一追踪 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',
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
videoId | String | 是 | 视频 ID |
videoTitle | String | 是 | 视频标题 |
videoTypeId | String | 是 | 视频分类 ID |
videoTypeName | String | 是 | 视频分类名称 |
videoTagKey | String | 是 | 标签 KEY,多个用英文逗号分隔 |
videoTagName | String | 是 | 标签名称,多个用英文逗号分隔 |
videoDuration | int | 是 | 视频总时长(秒) |
playDuration | int | 是 | 本次播放时长(秒) |
playProgress | int | 是 | 播放进度百分比(0-100) |
videoBehavior | VideoEventEnum | 是 | 视频行为,见 VideoEventEnum |
videoContentType | VideoContentTypeEnum? | 是 | video / short_video / post_video;不区分时长类型时可传 null |
postId | String | 条件 | 帖子 ID;仅 post_video 必填且非空,否则事件丢弃;其他类型默认 '' |
playSpeed | int | 否 | 实际倍速 × 100,默认 100(1 倍速);1.25 倍速传 125 |
recommendTraceId | String | 否 | 推荐引擎 trace ID,未接推荐引擎传 '',默认 '' |
mediaId | String | 否 | 媒体资源 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
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
mediaId | String | 是 | 老司机媒体资源 ID |
postId | String | 是 | 帖子 ID,空则丢弃 |
postTitle | String | 是 | 帖子标题 |
postCreateTime | int | 是 | 帖子创建时间,10 位秒级时间戳;payload 字段为 post_create_time(不再输出 create_time)。1.5.2 起 Dart 参数由 createTime 改名,旧写法无法编译 |
postUserId | String | 是 | 创建人账号 ID |
postUserNick | String | 是 | 创建人昵称 |
postContentType | PostContentTypeEnum | 是 | post(图文)/ video_post(视频帖) |
postTypeId | String | 否 | 分类 ID,默认 default |
postTypeName | String | 否 | 分类名称,默认「默认分类」 |
postTagKey | String | 否 | 标签 KEY,默认 default |
postTagName | String | 否 | 标签名称,默认「默认标签」 |
recommendTraceId | String | 否 | 推荐 trace,默认 '' |
postBehavior | PostEventEnum | 否 | 默认 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
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
dramaId | String | 是 | 剧集 ID |
dramaTitle | String | 是 | 剧集名称 |
episode | int | 是 | 当前第几集(从 1 开始) |
totalEpisode | int | 是 | 总集数 |
mediaId | String | 是 | 老司机媒体资源 ID |
videoId | String | 是 | 当前集视频 ID |
dramaDuration | int | 是 | 当前集总时长(秒) |
playDuration | int | 是 | 本次播放时长(秒) |
playProgress | int | 是 | 播放进度(0-100) |
playBehavior | DramaPlayBehaviorEnum | 是 | 展示 / 播放 / 暂停 / 快进 / 快退 / 播放完成 / 分享;播放失败见下 |
playSpeed | int | 否 | 倍速 × 100,默认 100 |
dramaContentType | DramaContentTypeEnum | 否 | 默认 shortDrama(short_drama) |
dramaTypeId 等 | String | 否 | 分类 / 标签默认值同帖子事件 |
recommendTraceId | String | 否 | 默认 '' |
启用白名单时加入 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,
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
dramaId | String | 是 | 剧集 ID |
dramaTitle | String | 是 | 剧集名称 |
episode | int | 是 | 当前第几集 |
mediaId | String | 是 | 媒体资源 ID |
videoId | String | 是 | 当前集视频 ID |
preEpisode | int | 是 | 前集序号 |
dramaBehavior | DramaBehaviorEnum | 是 | 自动播放 / 上下集 / 切换 / 解锁 |
启用白名单时加入 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: '',
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
novelId | String | 是 | 小说 ID |
novelTitle | String | 是 | 小说标题 |
novelTypeId | String | 是 | 分类 ID |
novelTypeName | String | 是 | 分类名称 |
novelTagKey | String | 是 | 标签 KEY,多个用英文逗号分隔 |
novelTagName | String | 是 | 标签名称,多个用英文逗号分隔 |
readProgress | int | 是 | 阅读进度百分比(0-100) |
pageNo | int | 是 | 当前阅读第几页。普通小说从 1 开始;仅有声小说业务约定允许 0。注意:当前 SDK 校验器仍会移除 page_no = 0,本版未实现零页码端到端上报 |
novelBehavior | ReadBehaviorEnum | 是 | 阅读行为,见 ReadBehaviorEnum |
chapterId | String | 否 | 章节 ID,没有章节或未对接推荐引擎时传 '',默认 '' |
chapterName | String | 否 | 章节名称,没有章节或未对接推荐引擎时传 '',默认 '' |
recommendTraceId | String | 否 | 推荐引擎 trace ID,未接推荐引擎传 '',默认 '' |
mediaId | String | 否 | 媒体资源 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', // 传广告类型字符串,不传默认空字符串
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
pageKey | String | 是 | 页面标识 |
pageName | String | 是 | 页面名称 |
adSlotKey | String | 是 | 广告位标识 |
adId | String | 是 | 广告 ID,多个用英文逗号分隔 |
creativeId | String | 否 | 素材 ID,多个用英文逗号分隔,与 adId 一一对应,默认 '' |
adType | String | 否 | 广告类型字符串,如 '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',
));| 字段 | 类型 | 说明 |
|---|---|---|
eventType | String | 行为类型:click/close/show |
advertisingKey | String | 广告标识 |
advertisingName | String | 广告名称 |
advertisingId | String | 广告 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 | 说明 |
|---|---|---|
AdSdkInitEvent | ad_sdk_init | 广告 SDK 初始化环境 |
AdRequestEvent | ad_request | 发起广告请求 |
AdFillEvent | ad_fill | 请求返回广告 |
AdNoFillEvent | ad_no_fill | 请求无广告返回 |
AdLoadSuccessEvent | ad_load_success | 素材加载成功 |
AdLoadFailEvent | ad_load_fail | 素材加载失败 |
AdImpressionSdkEvent | ad_impression_sdk | 可见达标曝光 |
AdClickSdkEvent | ad_click_sdk | 广告点击 |
AdCloseEvent | ad_close | 广告关闭 |
AdSkipEvent | ad_skip | 广告跳过(含 trace_id、shown_duration) |
AdErrorEvent | ad_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;未接搜索推荐引擎时传 ''
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
keyword | String | 是 | 搜索关键词 |
searchResultCount | int | 是 | 搜索结果数量 |
searchTraceId | String | 否 | 搜索引擎 trace ID,未接搜索引擎时传 '',默认 '' |
searchId | String | 否 | 可视范围内的搜索结果 ID,多个用英文逗号分隔(不是搜索会话 ID);未接搜索引擎时传 '',默认 '' |
searchContentType | String | 否 | 搜索列表类型:'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: '', // 未接搜索引擎时传空字符串
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
keyword | String | 是 | 搜索关键词 |
clickItemId | String | 是 | 点击项目 ID(视频 / 小说 / 漫画 / 帖子 / 短剧 ID) |
clickItemType | ClickItemTypeEnum | 是 | 点击项目类型:VIDEO / NOVEL / COMIC / POST / SHORT_DRAMA |
clickPosition | int | 是 | 点击位置(搜索结果中的排序位置,从 1 开始) |
searchTraceId | String | 否 | 搜索引擎 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', // 未接推荐引擎传 ''
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
pageKey | String | 是 | 页面标识 |
pageName | String | 是 | 页面名称 |
recommendContentType | RecommendContentTypeEnum | 是 | 推荐内容类型,见 RecommendContentTypeEnum |
recommendTraceId | String | 是 | 推荐引擎 trace ID |
recommendId | String | 是 | 可视窗口内的内容 ID,多个用英文逗号分隔 |
recommendTraceInfo | String | 否 | 推荐引擎 trace info,未接推荐引擎传 '',默认 '' |
clientVersion | String | 否 | 客户端版本号,如 '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', // 未接推荐引擎传 ''
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
pageKey | String | 是 | 页面标识 |
pageName | String | 是 | 页面名称 |
recommendContentType | RecommendContentTypeEnum | 是 | 推荐内容类型,见 RecommendContentTypeEnum |
recommendTraceId | String | 是 | 推荐引擎 trace ID |
recommendId | String | 是 | 被点击内容的 ID |
recommendTraceInfo | String | 否 | 推荐引擎 trace info,未接推荐引擎传 '',默认 '' |
clientVersion | String | 否 | 客户端版本号,如 '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"}',
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
itemId | String | 是 | 元素唯一 ID |
itemType | String | 是 | 元素类型字典值,如推荐、关注、评论、小说、视频、漫画等 |
itemName | String | 是 | 元素名称字典值,如推荐、关注、评论、小说、视频、漫画等 |
parentItemId | String | 是 | 父元素 ID |
pageKey | String | 是 | 当前页面标识 |
pageName | String | 是 | 当前页面名称 |
referrerPageKey | String | 是 | 来路页面标识,无来路时传 '' |
referrerPageName | String | 是 | 来路页面名称,无来路时传 '' |
recommendTraceId | String | 否 | 推荐引擎 trace ID,多个推荐列表用英文逗号分隔,未接推荐引擎传 '' |
recommendId | String | 否 | 推荐内容 ID,未接推荐引擎传 '' |
recommendTraceInfo | String | 否 | 推荐引擎 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: '',
));| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
itemId | String | 是 | 元素唯一 ID |
itemType | String | 是 | 点击元素类型字典值,如推荐、关注、评论、小说、视频、漫画等 |
itemName | String | 是 | 点击元素名称字典值,如推荐、关注、评论、小说、视频、漫画等 |
parentItemId | String | 是 | 父元素 ID |
position | int | 是 | 在父元素中的顺序索引,按业务约定可从 0 或 1 开始 |
pageKey | String | 是 | 当前页面标识 |
pageName | String | 是 | 当前页面名称 |
recommendTraceId | String | 否 | 推荐引擎 trace ID,多个推荐列表用英文逗号分隔,未接推荐引擎传 '' |
recommendId | String | 否 | 推荐内容 ID,未接推荐引擎传 '' |
recommendTraceInfo | String | 否 | 推荐引擎 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,
);公共类
| 枚举值 | key | name |
|---|---|---|
AppPageEnum.launch | launch | 启动页 |
AppPageEnum.guide | guide | 引导页 |
AppPageEnum.home | home | 首页 |
AppPageEnum.mainPage | main_page | 主页 |
AppPageEnum.discover | discover | 发现页 |
AppPageEnum.search | search | 搜索页 |
AppPageEnum.searchResult | search_result | 搜索结果页 |
AppPageEnum.appCenter | app_center | 应用中心 |
AppPageEnum.community | community | 社区页 |
AppPageEnum.square | square | 广场页 |
AppPageEnum.mine | mine | 我的 |
AppPageEnum.userCenter | user_center | 个人中心 |
AppPageEnum.userHome | user_home | 用户主页 |
AppPageEnum.authorHome | author_home | 作者主页 |
AppPageEnum.login | login | 登录页 |
AppPageEnum.register | register | 注册页 |
AppPageEnum.messageCenter | message_center | 消息中心 |
AppPageEnum.setting | setting | 设置页 |
AppPageEnum.recharge | recharge | 充值页 |
AppPageEnum.rechargeRecord | recharge_record | 充值记录页 |
AppPageEnum.mineCollect | mine_collect | 我的收藏 |
AppPageEnum.mineHistory | mine_history | 我的历史 |
AppPageEnum.mineVip | mine_vip | 会员中心页 |
视频类
| 枚举值 | key | name |
|---|---|---|
AppPageEnum.videoList | video_list | 视频列表页 |
AppPageEnum.videoCategory | video_category | 视频分类页 |
AppPageEnum.videoRank | video_rank | 视频榜单页 |
AppPageEnum.videoSearch | video_search | 视频搜索页 |
AppPageEnum.videoDetail | video_detail | 视频详情页 |
AppPageEnum.videoPlay | video_play | 视频播放页 |
小说类
| 枚举值 | key | name |
|---|---|---|
AppPageEnum.novelList | novel_list | 小说列表页 |
AppPageEnum.novelCategory | novel_category | 小说分类页 |
AppPageEnum.novelRank | novel_rank | 小说榜单页 |
AppPageEnum.novelDetail | novel_detail | 小说详情页 |
AppPageEnum.novelRead | novel_read | 小说阅读页 |
AppPageEnum.novelChapterList | novel_chapter_list | 小说章节页 |
漫画类
| 枚举值 | key | name |
|---|---|---|
AppPageEnum.comicList | comic_list | 漫画列表页 |
AppPageEnum.comicCategory | comic_category | 漫画分类页 |
AppPageEnum.comicRank | comic_rank | 漫画榜单页 |
AppPageEnum.comicDetail | comic_detail | 漫画详情页 |
AppPageEnum.comicRead | comic_read | 漫画阅读页 |
AppPageEnum.comicChapterList | comic_chapter_list | 漫画章节页 |
不在列表中的页面直接传字符串即可,无需扩展枚举:
dartAnalyticsSdk.instance.trackNavigation(pageKey: 'live_room', pageName: '直播间');
DeviceEnum
| 枚举值 | label | 说明 |
|---|---|---|
DeviceEnum.android | Android | Android 设备 |
DeviceEnum.iOS | iOS | iOS 设备 |
DeviceEnum.pc | PC | PC 设备 |
UserTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
UserTypeEnum.normal | normal | 普通用户 |
UserTypeEnum.vip | vip | VIP 用户 |
AdTypeEnum
已废弃:
adType字段已改为普通String,直接传字符串即可,如'banner'、'feed'、'splash'。无需 import 枚举类。
VideoEventEnum
| 枚举值 | key | 说明 |
|---|---|---|
VideoEventEnum.VIDEO_VIEW | video_view | 展示 |
VideoEventEnum.VIDEO_PLAY | video_play | 播放 |
VideoEventEnum.VIDEO_PAUSE | video_pause | 暂停 |
VideoEventEnum.VIDEO_SHARE | video_share | 分享 |
VideoEventEnum.VIDEO_COMPLETE | video_complete | 播放完成 |
VideoEventEnum.VIDEO_FORWARD | video_forward | 快进 |
VideoEventEnum.VIDEO_REWIND | video_rewind | 快退 |
VideoEventEnum('video_failed', '播放失败') | video_failed | 播放失败(自定义字符串,无预定义常量) |
VideoContentTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
VideoContentTypeEnum.video | video | 长视频 |
VideoContentTypeEnum.shortVideo | short_video | 短视频 |
VideoContentTypeEnum.postVideo | post_video | 帖子视频 |
PostContentTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
PostContentTypeEnum.post | post | 图文帖 |
PostContentTypeEnum.videoPost | video_post | 视频帖 |
PostEventEnum
| 枚举值 | key | 说明 |
|---|---|---|
PostEventEnum.VIEW | view | 展示 |
DramaContentTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
DramaContentTypeEnum.shortDrama | short_drama | 短剧 |
DramaPlayBehaviorEnum
| 枚举值 | key | 说明 |
|---|---|---|
DramaPlayBehaviorEnum.EP_VIEW | ep_view | 展示 |
DramaPlayBehaviorEnum.EP_PLAY | ep_play | 播放 |
DramaPlayBehaviorEnum.EP_PAUSE | ep_pause | 暂停 |
DramaPlayBehaviorEnum.EP_FORWARD | ep_forward | 快进 |
DramaPlayBehaviorEnum.EP_REWIND | ep_rewind | 快退 |
DramaPlayBehaviorEnum.EP_COMPLETE | ep_complete | 播放完成 |
DramaPlayBehaviorEnum.EP_SHARE | ep_share | 分享 |
DramaPlayBehaviorEnum('ep_failed', '播放失败') | ep_failed | 播放失败(自定义字符串,无预定义常量) |
DramaBehaviorEnum
| 枚举值 | key | 说明 |
|---|---|---|
DramaBehaviorEnum.EP_AUTO_PLAY | ep_auto_play | 自动播放 |
DramaBehaviorEnum.EP_PREVIOUS | ep_previous | 上一集 |
DramaBehaviorEnum.EP_NEXT | ep_next | 下一集 |
DramaBehaviorEnum.EP_SWITCH | ep_switch | 切换剧集 |
DramaBehaviorEnum.EP_UNLOCK | ep_unlock | 解锁 |
ReadBehaviorEnum
| 枚举值 | key | 说明 |
|---|---|---|
ReadBehaviorEnum.VIEW | view | 展示 |
ReadBehaviorEnum.PAGE_NEXT | page_next | 下一页 |
ReadBehaviorEnum.PAGE_PREV | page_prev | 上一页 |
ReadBehaviorEnum.COMPLETE | complete | 读完 |
ClickItemTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
ClickItemTypeEnum.VIDEO | video | 视频 |
ClickItemTypeEnum.NOVEL | novel | 小说 |
ClickItemTypeEnum.COMIC | comic | 漫画 |
ClickItemTypeEnum.POST | post | 帖子 |
ClickItemTypeEnum.SHORT_DRAMA | short_drama | 短剧 |
RecommendContentTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
RecommendContentTypeEnum.video | video | 视频 |
RecommendContentTypeEnum.novel | novel | 小说 |
RecommendContentTypeEnum.comic | comic | 漫画 |
BannerEventEnum
| 枚举值 | label | 说明 |
|---|---|---|
BannerEventEnum.CLICK | click | 点击 |
BannerEventEnum.CLOSE | close | 关闭 |
BannerEventEnum.SHOW | show | 展示 |
8. SdkConfig 全局参数
在 AnalyticsSdk.instance.init() 之前调用 SdkConfig.configure(...) 覆盖默认值:
dart
SdkConfig.configure(
uploadInterval: const Duration(seconds: 5),
autoUploadThreshold: 10,
maxBatchSize: 200,
connectionTimeout: const Duration(seconds: 15),
);| 参数 | 默认值 | 说明 |
|---|---|---|
maxQueueSize | 5000 | 内存队列最大容量,超过后新事件丢弃 |
autoUploadThreshold | 10 | 队列达到此数量时立即触发上报 |
maxBatchSize | 500 | 单次上报最大事件数 |
uploadInterval | Duration(seconds: 5) | 定时上报间隔 |
clickThrottleDuration | Duration(milliseconds: 300) | 点击事件节流时间 |
connectionTimeout | Duration(seconds: 10) | HTTP 连接超时 |
readTimeout | Duration(seconds: 30) | HTTP 读取超时 |
speedTestTimeout | Duration(seconds: 5) | 域名测速超时 |
maxCacheLines | 2000 | 缓存文件最大行数 |
maxCacheBytes | 2 * 1024 * 1024 (2MB) | 缓存文件最大字节数 |
maxSingleEventSize | 300 * 1024 (300KB) | 单条事件最大字节数,超过丢弃 |
maxAdImpressionCapacity | 10000 | 广告去重集合最大容量(FIFO 淘汰) |
恢复所有默认值:
dart
SdkConfig.reset();9. 上报数据格式
9.1 公共字段
每个事件上报时,顶层 JSON 自动携带以下公共字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
event | String | 事件类型,如 video_event、ad_impression |
channel | String | 渠道标识 |
app_id | String | 应用 ID |
uid | String | 用户 ID |
sid | String | 会话 ID,由客户端 SDK 自动生成;服务端上报事件时可通过 getDeviceCommonFields() 或 getParams() 获取并回传,以归属同一会话 |
client_ts | int | 事件创建时间戳(10位,秒) |
device | String | 设备类型(自动采集) |
device_id | String | SDK 内部生成的设备标识 |
device_brand | String | 设备品牌(自动采集) |
device_model | String | 设备型号(自动采集) |
user_agent | String | 客户端标识(自动采集) |
system_name | String | 操作系统名称(自动采集) |
system_version | String | 操作系统版本(自动采集) |
device_fingerprint | String | 首次生成并持久化的设备指纹,后续初始化读取保存值 |
fp_version | String | 指纹规则版本,当前为 4.0.0 |
sdk_version | String | SDK 版本号(自动写入;当前常量为 1.5.3) |
app_version | String | 应用版本号(由接入方在 init() 中传入,未传默认 '1.0.0') |
event_sig_version | String | 可选。配置事件签名密钥后自动生成,当前默认 2.0.0 |
event_sig | String | 可选。配置事件签名密钥后自动生成的 HMAC-SHA256 签名 |
event_id | String | 事件去重 ID(MD5,SDK 自动生成;1100ms 内重复 ID 将被防抖丢弃) |
payload | Object | 事件业务字段 |
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_id | SDK 内部生成;非空,最大 128 字符 | 空时丢弃事件;超长截断 |
device_fingerprint | SDK 管控字段,不接受外部覆盖 | 改为 SDK 内部生成值;内部无值时清空 |
fp_version | SDK 管控字段,不接受外部覆盖 | 改为当前规则版本 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_]+$ | 移除该字段 |
| 关键 ID | video_id / novel_id / comic_id / click_item_id | 非空 | 丢弃事件 |
| 普通 ID | ad_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 中传入的值不会生效。
首次生成的采集因子
| 平台 | 参与计算的因子 |
|---|---|
| Android | os_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 |
| iOS | os_family、ios_device_model_raw、screen_width、screen_height、screen_dpi、device_pixel_ratio、cpu_cores、device_memory_gb、id_source、id_value |
| Web | os_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 不参与指纹计算,但仍作为环境字段正常采集和上报。网络、电量、页面、行为也不参与。
生成与持久化规则
- 先读取持久化记录;已有非空指纹时直接使用,不因设备参数、语言、时区或系统版本变化而重新计算。
- 没有记录时,按上表固定顺序构造
key=value,保留字段名和空值;字符串去掉首尾空格并转小写,数值转整数,无法转换时为-1。 - 使用
|连接字段,在前面加上fp_v4|fingerprint_platform=android/ios/web|(平台取其一),对 UTF-8 字节做 SHA-256,输出 64 个小写十六进制字符。 - 将生成值持久化,回读确认成功后保存到
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.0event_sig:HMAC-SHA256 签名,64 位小写十六进制字符串
签名输入固定为:
text
event|event_id|app_id|client_ts|device_fingerprintdevice_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.jsonl10.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 flutterprofile/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。 PostEventDart 参数由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及上述新增取值。
完整变更见 更新日志。