32 KiB
Analytics SDK 使用文档
目录
1. 概述
Analytics SDK 是一个 Flutter 插件,提供:
- 自动埋点:页面浏览、页面生命周期、全局点击、导航事件(均默认开启)
- 手动埋点:视频/小说/漫画行为、广告、推荐等客户端事件
- 本地持久化缓存(JSONL 格式),断网重传
- 域名并发测速,自动选择最快上报节点
- AES-GCM 加密配置,防止配置明文泄露
- 事件类型白名单控制(服务端下发)
- 广告展示去重(session 级别)
- 设备信息自动采集(通过
device_info_plus,无需外部传入) - 设备指纹自动生成
getParams()方法暴露全部或指定公共参数,供服务端复用
所有公开方法均不会向外抛出异常,SDK 内部失败不影响宿主应用运行。
2. 安装
在 pubspec.yaml 中添加依赖:
dependencies:
analytics_sdk:
path: ../analytics_sdk # 或 git/pub 地址
运行:
flutter pub get
3. SDK 初始化
3.1 最简初始化
import 'package:analytics_sdk/analytics_sdk.dart';
await AnalyticsSdk.instance.init(
appId: 'your_app_id',
encryptedConfig: 'Base64加密字符串', // 服务端下发
deviceId: 'your_device_id', // Android IMEI/OAID、iOS IDFV 等
);
设备品牌、型号、系统名称、版本等信息由 SDK 内部通过 device_info_plus 自动采集。
3.2 完整参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
appId |
String |
是 | — | 应用 ID |
encryptedConfig |
String? |
是(可传 null) | null | 加密配置字符串,见下方格式说明 |
channel |
String? |
否 | '' | 渠道标识,可后续通过 setChannel 更新 |
uid |
String? |
否 | '' | 用户 ID,未登录可传空,登录后通过 setUserIdAndType 更新 |
deviceId |
String |
是 | — | 设备唯一标识(Android IMEI/OAID、iOS IDFV 等) |
enableDebugBanner |
bool |
否 | false |
是否显示调试悬浮条(仅在非 release 包生效) |
自动采集字段:
device(类型)、device_brand、device_model、system_name、system_version、user_agent均由 SDK 通过device_info_plus自动读取,无需传入。
3.3 encryptedConfig 格式说明
encryptedConfig 是一个 Base64 编码的 AES-GCM 密文,解密后内容为:
{
"domainList": [
"https://api1.example.com",
"https://api2.example.com"
],
"eventList": [
"app_page_view",
"page_click",
"video_event",
"ad_impression"
]
}
domainList:上报域名列表,SDK 启动时会并发测速,选择最快的域名上报。eventList:启用的事件类型白名单。不在列表中的事件会被静默丢弃。传空列表或省略此字段时,所有事件类型均允许上报。
如需在调试包中验证加密串,使用 validateEncryptedConfig。
3.4 初始化时机
建议在 main() 中 runApp() 之前调用:
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(),
deviceId: await getDeviceId(),
channel: 'google_play',
uid: currentUser?.id ?? '',
);
runApp(const MyApp());
}
4. 用户管理
4.1 登录 / 切换账号
AnalyticsSdk.setUserIdAndType(
userId: '12345',
userTypeEnum: UserTypeEnum.vip,
);
| 参数 | 类型 | 说明 |
|---|---|---|
userId |
String |
用户 ID,默认空字符串(未登录状态) |
userTypeEnum |
UserTypeEnum? |
用户类型,null 时默认为 normal |
4.2 仅更新 uid(不改变用户类型)
AnalyticsSdk.setUid('12345');
4.3 更新渠道
AnalyticsSdk.setChannel('huawei_store');
4.4 登出
AnalyticsSdk.logoutUser();
5. 自动埋点
5.1 页面生命周期自动追踪(PageLifecycleObserver)
MaterialApp(
navigatorObservers: [
AnalyticsSdk.instance.pageObserver,
],
)
注册后 SDK 会自动追踪页面进入、离开和导航事件,无需手动上报。
获取当前页面 key:
final currentPage = PageLifecycleObserver.currentPageKey;
5.2 页面名称映射(PageNameMapper)
import 'package:analytics_sdk/manager/page_name_manager.dart';
PageNameMapper.addMappings({
'home': '首页',
'video_detail': '视频详情页',
'user_center': '个人中心',
'search': '搜索页',
});
final name = PageNameMapper.getPageName('video_detail'); // '视频详情页'
注意:
pageKey和映射 key 均会自动去除前导/,传'/video_detail'与'video_detail'效果相同。
5.3 全局点击自动追踪(GlobalClickWrapper)
import 'package:analytics_sdk/widget/global_click_wrapper.dart';
MaterialApp(
builder: (context, child) => GlobalClickWrapper(child: child!),
)
自动上报 PageClickEvent,含归一化坐标(300ms 节流)。
6. 手动事件上报
所有手动事件通过 AnalyticsSdk.instance.track(event) 上报。
6.1 App 安装事件【客户端】
触发时机:首次安装启动后,读取剪贴板获取到 traceId 时上报。
AnalyticsSdk.instance.track(AppInstallEvent(
traceId: 'clipboard_trace_id',
));
| 字段 | 类型 | 说明 |
|---|---|---|
traceId |
String |
落地页生成并写入剪贴板的唯一追踪 ID |
6.2 视频行为事件【客户端】
触发时机:用户播放、暂停、快进、完播等操作时上报。
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,
recommendTraceId: 'trace_abc123', // 未接推荐引擎传 ''
mediaId: '',
));
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
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(短视频),未传时上报空字符串 |
recommendTraceId |
String |
否 | 推荐引擎 trace ID,未接推荐引擎传 '',默认 '' |
mediaId |
String |
否 | 媒体资源 ID,默认 '' |
6.3 小说阅读事件【客户端】
AnalyticsSdk.instance.track(NovelEvent(
novelId: 'novel_001',
novelTitle: '斗破苍穹',
novelTypeId: 'fantasy',
novelTypeName: '玄幻',
novelTagKey: 'hot,classic',
novelTagName: '热门,经典',
readProgress: 45,
pageNo: 10,
novelBehavior: ReadBehaviorEnum.PAGE_NEXT,
mediaId: '',
));
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
novelId |
String |
是 | 小说 ID |
novelTitle |
String |
是 | 小说标题 |
novelTypeId |
String |
是 | 分类 ID |
novelTypeName |
String |
是 | 分类名称 |
novelTagKey |
String |
是 | 标签 KEY,多个用英文逗号分隔 |
novelTagName |
String |
是 | 标签名称,多个用英文逗号分隔 |
readProgress |
int |
是 | 阅读进度百分比(0-100) |
pageNo |
int |
是 | 当前阅读第几页(从 1 开始) |
novelBehavior |
ReadBehaviorEnum |
是 | 阅读行为,见 ReadBehaviorEnum |
recommendTraceId |
String |
否 | 推荐引擎 trace ID,未接推荐引擎传 '',默认 '' |
mediaId |
String |
否 | 媒体资源 ID,未使用老司机库资源时传 '' |
6.4 漫画阅读事件【客户端】
AnalyticsSdk.instance.track(ComicEvent(
comicId: 'comic_001',
comicTitle: '火影忍者',
comicTypeId: 'battle',
comicTypeName: '热血',
comicTagKey: 'hot,classic',
comicTagName: '热门,经典',
readProgress: 60,
pageNo: 5,
comicBehavior: ReadBehaviorEnum.PAGE_NEXT,
));
字段与 NovelEvent 对称,将 novel* 前缀替换为 comic*,novelBehavior 替换为 comicBehavior。recommendTraceId 字段同样支持,默认 ''。
6.5 广告展示事件【客户端】(带自动去重)
SDK 会对
adId进行 session 级别去重:同一个adId在当前 session 内只会上报一次。
AnalyticsSdk.instance.track(AdImpressionEvent(
pageKey: 'home',
pageName: '首页',
adSlotKey: 'home_banner_1',
adSlotName: '首页顶部Banner',
adId: 'ad_001,ad_002,ad_003',
creativeId: 'cr_001,cr_002,cr_003',
adType: 'banner', // 传广告类型字符串,不传默认空字符串
));
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
pageKey |
String |
是 | 页面标识 |
pageName |
String |
是 | 页面名称 |
adSlotKey |
String |
是 | 广告位标识 |
adSlotName |
String |
是 | 广告位名称 |
adId |
String |
是 | 广告 ID,多个用英文逗号分隔 |
creativeId |
String |
否 | 素材 ID,多个用英文逗号分隔,与 adId 一一对应,默认 '' |
adType |
String |
否 | 广告类型字符串,如 'banner'、'feed'、'splash',默认 '' |
去重逻辑:
- 进入 TabA 展示 20 条广告 → 上报 20 条
- 切换到 TabB 展示 20 条广告 → 上报 20 条
- 返回 TabA 上滑新加载 10 条广告 → 上报新的 10 条
- 再次下滑看到之前已展示的广告 → 全部被过滤
6.6 广告点击事件【客户端】
AnalyticsSdk.instance.track(AdClickEvent(
pageKey: 'home',
pageName: '首页',
adSlotKey: 'home_banner_1',
adSlotName: '首页顶部Banner',
adId: 'ad_001',
creativeId: 'cr_001',
adType: 'banner',
));
字段与 AdImpressionEvent 相同,adId 为单个广告 ID。
6.7 APP 广告行为事件【客户端】(弹窗/Banner 行为)
用于上报 APP 内置广告(弹窗、Banner 等)的展示、点击、关闭行为。
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.8 关键词搜索事件【客户端】
AnalyticsSdk.instance.track(KeywordSearchEvent(
keyword: '火影忍者',
searchResultCount: 42,
searchTraceId: '', // 未接搜索引擎时传空字符串
searchId: '', // 未接搜索引擎时传空字符串
));
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
keyword |
String |
是 | 搜索关键词 |
searchResultCount |
int |
是 | 搜索结果数量 |
searchTraceId |
String |
否 | 搜索引擎 trace ID,未接搜索引擎时传 '' |
searchId |
String |
否 | 搜索引擎 search ID,未接搜索引擎时传 '' |
6.9 关键词点击事件【客户端】
AnalyticsSdk.instance.track(KeywordClickEvent(
keyword: '火影忍者',
clickItemId: 'vid_001',
clickItemType: ClickItemTypeEnum.VIDEO,
clickPosition: 1,
searchTraceId: '', // 未接搜索引擎时传空字符串
));
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
keyword |
String |
是 | 搜索关键词 |
clickItemId |
String |
是 | 点击项目 ID(视频ID / 小说ID 等) |
clickItemType |
ClickItemTypeEnum |
是 | 点击项目类型:VIDEO / NOVEL / COMIC |
clickPosition |
int |
是 | 点击位置(搜索结果中的排序位置,从 1 开始) |
searchTraceId |
String |
否 | 搜索引擎 trace ID,未接搜索引擎时传 '' |
6.10 推荐列表展示事件【客户端】
AnalyticsSdk.instance.track(RecommendListViewEvent(
pageKey: 'home',
pageName: '首页',
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 推荐列表点击事件【客户端】
AnalyticsSdk.instance.track(RecommendListClickEvent(
pageKey: 'home',
pageName: '首页',
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 强制立即上报
await AnalyticsSdk.instance.flush();
6.13 释放资源
await AnalyticsSdk.instance.dispose();
7. 枚举参考
DeviceEnum
| 枚举值 | label | 说明 |
|---|---|---|
DeviceEnum.android |
Android |
Android 设备 |
DeviceEnum.iOS |
iOS |
iOS 设备 |
DeviceEnum.pc |
PC |
PC 设备 |
UserTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
UserTypeEnum.normal |
normal |
普通用户 |
UserTypeEnum.vip |
vip |
VIP 用户 |
AuthTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
AuthTypeEnum.phone |
phone |
手机号 |
AuthTypeEnum.deviceid |
deviceid |
设备 ID |
AuthTypeEnum.email |
email |
邮箱 |
AuthTypeEnum.username |
username |
账号 |
AdTypeEnum
已废弃:
adType字段已改为普通String,直接传字符串即可,如'banner'、'feed'、'splash'。无需 import 枚举类。
OrderTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
OrderTypeEnum.coinPurchase |
coin_purchase |
金币购买 |
OrderTypeEnum.vipSubscription |
vip_subscription |
VIP 订阅 |
CurrencyEnum
| 枚举值 | label | 说明 |
|---|---|---|
CurrencyEnum.cny |
CNY |
人民币 |
CurrencyEnum.usd |
USD |
美元 |
PayTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
PayTypeEnum.wechat |
wechat |
微信支付 |
PayTypeEnum.alipay |
alipay |
支付宝 |
PayTypeEnum.bankCard |
bank_card |
银行卡 |
PayTypeEnum.applePay |
apple_pay |
Apple Pay |
ConsumeReasonEnum
| 枚举值 | label | 说明 |
|---|---|---|
ConsumeReasonEnum.videoUnlock |
video_unlock |
视频解锁 |
ConsumeReasonEnum.giftSend |
gift_send |
礼物赠送 |
ConsumeReasonEnum.contentPurchase |
content_purchase |
内容购买 |
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 |
快退 |
VideoContentTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
VideoContentTypeEnum.video |
video |
长视频 |
VideoContentTypeEnum.shortVideo |
short_video |
短视频 |
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 |
漫画 |
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(...) 覆盖默认值:
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 淘汰) |
恢复所有默认值:
SdkConfig.reset();
9. 上报数据格式
9.1 公共字段
每个事件上报时,顶层 JSON 自动携带以下公共字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
event |
String |
事件类型,如 video_event、ad_impression |
channel |
String |
渠道标识 |
app_id |
String |
应用 ID |
uid |
String |
用户 ID |
sid |
String |
会话 ID,自动管理 |
client_ts |
int |
事件创建时间戳(10位,秒) |
device |
String |
设备类型(自动采集) |
device_id |
String |
设备唯一标识 |
device_brand |
String |
设备品牌(自动采集) |
device_model |
String |
设备型号(自动采集) |
user_agent |
String |
客户端标识(自动采集) |
system_name |
String |
操作系统名称(自动采集) |
system_version |
String |
操作系统版本(自动采集) |
device_fingerprint |
String |
设备指纹(自动生成) |
fp_version |
String |
指纹版本号 |
event_id |
String |
事件去重 ID |
payload |
Object |
事件业务字段 |
9.2 上报数据格式
{
"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": "1",
"event_id": "md5-hash-for-dedup",
"payload": {
"event": "video_event",
"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 |
可选,最大 128 字符 | 超长截断 |
sid |
自动生成;为空时 SDK 自动填充会话 ID | 生成失败则丢弃事件 |
client_ts |
整数,范围 946684800 ~ 4102444800(2000 ~ 2100 年) | 丢弃事件 |
device |
仅 Android / iOS / PC,大小写自动纠正 |
丢弃事件 |
device_id |
非空,最大 128 字符 | 空时丢弃事件;超长截断 |
device_fingerprint |
可选,最大 128 字符 | 超长截断 |
fp_version |
若指纹非空而版本为空,自动填充默认版本 | — |
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 / rank / slot / index |
整数,≥ 1 | 移除该字段 |
| 数字字段 | duration / count / amount 等 |
整数,≥ 0 | 移除该字段 |
| 时间戳字段 | start_ts / end_ts / publish_ts |
同 client_ts 范围 |
移除该字段 |
| 其他字段 | — | 字符串最大 500 字符;非字符串原样保留 | 截断 |
9.4 设备指纹(device_fingerprint / fp_version)
设备指纹由 SDK 在 init() 时自动生成,业务方无需手动传入。
采集因子
| 平台 | 参与计算的因子 |
|---|---|
| Native(Android / iOS / 桌面) | device_id、device(类型)、device_brand、device_model、system_name、system_version |
| Web | device、device_brand、device_model、system_name、system_version、user_agent、屏幕分辨率(WxH)、浏览器语言、时区名称 |
生成规则
- 过滤所有空因子,将剩余因子用
|拼接后做 SHA-256,取 64 位十六进制字符串作为指纹。 - 若所有因子均为空(降级场景),改为生成随机 UUID v4 并持久化至本地(见缓存规则),后续从本地读取保证稳定性。
device_fingerprint不参与event_id的计算。
缓存规则
- 内存缓存:首次生成后缓存在内存中,同一应用生命周期内直接返回,不重复计算。
- 持久化缓存(仅降级 UUID 路径):
- Native:写入
applicationDocumentsDirectory/analytics_fingerprint.txt; - Web:写入
localStorage['analytics_fp']。
- Native:写入
- 重新
init()后若设备因子发生变化,可通过内部clearCache()重新计算(一般无需手动调用)。
失败兜底
任何环节发生异常,均静默返回空字符串,事件照常上报;device_fingerprint 为空时,fp_version 字段亦不写入(无指纹则无需版本号)。
fp_version 说明
fp_version 标识当前指纹生成规则的版本(当前为 "1"),供服务端区分不同规则生成的指纹,支持跨版本数据对比分析。生成规则(因子组合或哈希算法)变更时版本号递增。
10. 调试工具
10.1 AnalyticsDebugBanner(悬浮调试条)
在 init() 时传 enableDebugBanner: true,然后将 AnalyticsDebugBanner() 放入 widget 树顶部(通常是页面 Column 的第一个子节点或 Stack 的顶层):
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
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']);
10.3 validateEncryptedConfig
final result = AnalyticsSdk.instance.validateEncryptedConfig(encryptedString);
if (result['success'] == true) {
print('域名数量: ${result['domainCount']}');
print('事件类型数量: ${result['eventCount']}');
} else {
print('验证失败: ${result['error']}');
}
仅 debug 包可用,release 包返回错误。
10.4 Logger
import 'package:analytics_sdk/utils/logger.dart';
Logger.enabled = false; // 关闭所有日志
11. 高级用法
11.1 延迟传入加密配置
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AnalyticsSdk.instance.init(
appId: 'your_app_id',
encryptedConfig: null, // SDK 尝试读取磁盘缓存域名
deviceId: await getDeviceId(),
);
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 当前全部公共参数,适合服务端拼装事件或埋点调试:
// 获取全部参数
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、device、device_id、device_brand、device_model、user_agent、system_name、system_version、device_fingerprint、fp_version。
如果 key 不存在,该 key 不会出现在返回 Map 中(静默忽略)。
11.3 服务端复用设备公共字段
final deviceFields = AnalyticsSdk.getDeviceCommonFields();
// 包含:device, device_id, device_brand, device_model,
// user_agent, system_name, system_version, device_fingerprint
12. 完整集成示例
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/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',
deviceId: 'device_unique_id',
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,
));
}
// ——— 广告展示场景 ———
void onAdsRendered(List<String> adIds) {
AnalyticsSdk.instance.track(AdImpressionEvent(
pageKey: 'home',
pageName: '首页',
adSlotKey: 'home_feed_ad',
adSlotName: '首页信息流广告',
adId: adIds.join(','),
adType: 'feed',
));
}
// ——— 应用退出场景 ———
Future<void> onAppExit() async {
await AnalyticsSdk.instance.flush();
await AnalyticsSdk.instance.dispose();
}