版本说明: 本分支(
dev)适用于 Flutter 3.0.5 及以上(Dart >=2.17)。 如使用 Flutter 1.22.5,请切换至compat/flutter-1.22分支。
Analytics SDK
一个 Flutter 埋点 SDK,用于采集 App 内页面、点击、广告、视频、小说、漫画、搜索、推荐等行为,并提供批量上报、本地持久化、域名测速和事件类型动态配置能力。
目录
- 功能特性
- 安装
- 快速开始
- 核心 API
- 域名与事件类型管理
- SDK 配置(可选)
- 更多文档
- 手动上报事件
- 用户类型管理
- 页面名称映射
- 事件数据结构
- 上报机制
- 事件去重
- 完整示例
- 枚举类型参考
- 注意事项
- 调试
- 常见问题 FAQ
- 依赖项
- 环境要求
功能特性
- 多事件类型:页面浏览、页面点击、导航、广告展示/点击、视频/小说/漫画行为、关键词搜索、推荐等事件
- 自动埋点:页面生命周期(曝光/停留)、全局点击(默认开启)
- 批量上报:队列聚合,默认每 5 秒或队列达 10 条时批量上报
- 本地持久化:事件写入本地 JSONL 文件,重启后自动恢复(默认最多 2000 行)
- 智能重试:指数退避重试机制,初始 2s,最大 60s,最多 5 级
- 域名测速:从
encryptedConfig解析域名列表,并发测速选最快可用节点 - 事件类型动态开关:通过
encryptedConfig下发白名单,SDK 侧统一过滤 - 会话管理:按"冷启动 / 前后台 30 分钟 / 行为 30 分钟"规则生成
sid - 用户管理:支持用户 ID 与用户类型动态更新
- 广告展示去重:session 级别按 adId 自动去重,防止重复上报
- 设备信息自动采集:通过
device_info_plus自动获取品牌、型号、系统名称与版本,无需外部传入 - 设备指纹:基于设备因子生成 SHA-256 指纹;因子全空时降级为 UUID v4 持久化兜底
- 公共参数暴露:通过
getParams()获取全部或指定公共埋点参数,供服务端复用
安装
在宿主工程的 pubspec.yaml 中添加依赖(示例):
dependencies:
analytics_sdk:
path: ../analytics_sdk # 本地导入:根据实际目录调整相对路径
然后执行:
flutter pub get
快速开始
1. 初始化 SDK
import 'package:flutter/widgets.dart';
import 'package:analytics_sdk/analytics_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 从你的服务端获取加密配置(由业务方自行实现网络请求)
final encryptedConfig = await yourApiClient.fetchEncryptedConfig();
await AnalyticsSdk.instance.init(
appId: 'your_app_id',
encryptedConfig: encryptedConfig, // 加密配置,解密后包含域名列表和事件白名单
channel: 'official',
uid: 'user_123', // 未登录可传 null 或空字符串
deviceId: 'device_unique_id', // 设备唯一标识
);
runApp(const MyApp());
}
设备品牌、型号、系统名称和版本由 SDK 内部通过
device_info_plus自动采集,无需外部传入。
2. 支持「先 init,后补 encryptedConfig」的场景
如果你需要先初始化 SDK,再从自己配置接口拿到加密串,可以这样用:
// 1)先初始化(此时还没有 encryptedConfig,事件会正常入队和落盘,但无法实际上报)
await AnalyticsSdk.instance.init(
appId: 'your_app_id',
encryptedConfig: null, // 先传 null,SDK 会尝试读取本地缓存域名
deviceId: 'device_unique_id',
);
// 2)等拿到配置后再传入 encryptedConfig
final encryptedConfig = await yourApiClient.fetchEncryptedConfig();
await AnalyticsSdk.instance.refreshDomainConfig(encryptedConfig: encryptedConfig);
refreshDomainConfig() 会:
- 解密
encryptedConfig,解析最新域名列表并并发测速,选出最快可用域名; - 更新内部上报 URL,后续定时上报和
flush()会把之前队列里的事件一并发出; - 更新事件类型白名单配置。
3. 页面生命周期 & 全局点击埋点
import 'package:analytics_sdk/analytics_sdk.dart';
import 'package:analytics_sdk/widget/global_click_wrapper.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [
AnalyticsSdk.instance.pageObserver, // 页面自动埋点
],
builder: (context, child) {
return GlobalClickWrapper( // 全局点击自动抓取
child: child!,
);
},
home: const HomePage(),
);
}
}
核心 API
AnalyticsSdk.init
Future<void> init({
required String appId,
required String? encryptedConfig,
String? channel,
String? uid,
required String deviceId,
bool enableDebugBanner = false,
})
建议在 main() 中、runApp() 之前调用。内部流程:
- 通过
device_info_plus自动采集设备品牌、型号、系统名称、版本等信息; - 配置公共字段(appId / channel / uid / device 等);
- 初始化 SessionManager、AppLifecycleObserver、设备指纹;
- 解密
encryptedConfig,解析域名列表并发测速选最快节点; - 解析事件类型白名单;
- 如
encryptedConfig为 null / 解密失败,尝试读取本地缓存域名和事件类型配置; - 初始化事件队列、本地缓存和定时上报定时器。
enableDebugBanner:仅在非 release 包下生效,配合 AnalyticsDebugBanner 显示调试悬浮条。
AnalyticsSdk.getParams
static Map<String, dynamic> getParams([List<String>? keys])
获取 SDK 当前全部公共参数(或按 key 过滤子集),可用于服务端事件拼装或调试:
// 获取全部公共参数
final all = AnalyticsSdk.getParams();
// { 'app_id': '...', 'channel': '...', 'uid': '...', 'user_type': '...', 'sid': '...', 'device': '...', ... }
// 只取指定参数
final partial = AnalyticsSdk.getParams(['app_id', 'uid', 'device_id']);
返回字段包括:app_id、channel、uid、user_type、sid、device、device_id、device_brand、device_model、user_agent、system_name、system_version、device_fingerprint、fp_version。
AnalyticsSdk.refreshDomainConfig
Future<void> refreshDomainConfig({String? encryptedConfig})
使用场景:
- 先调用
init(),再从服务端获取encryptedConfig; - 运行中需要更新域名列表或事件类型白名单。
// 获取到最新加密配置后刷新
final newConfig = await yourApiClient.fetchEncryptedConfig();
await AnalyticsSdk.instance.refreshDomainConfig(encryptedConfig: newConfig);
传入 null 或空字符串时忽略本次刷新。本方法永远不会抛出异常。
事件上报相关
// 上报内置事件
void track(dynamic event)
// 立即触发上报
Future<void> flush()
// 页面生命周期观察者
NavigatorObserver get pageObserver
// 资源清理(可选)
Future<void> dispose()
track() 支持传入 SDK 内置的事件类(如 VideoEvent、AdImpressionEvent 等)或直接传 Map<String, dynamic>。
SDK 会自动:
- 做广告展示事件去重与过滤;
- 检查事件类型是否在白名单中;
- 校验必填字段;
- 检查单条事件大小(超过 300KB 丢弃);
- 将事件写入队列和本地缓存;
- 在队列长度达到阈值或定时器触发时批量上报。
用户与会话
// 设置用户 ID 和类型(登录 / 切换账号)
static void setUserIdAndType({String userId = "", UserTypeEnum? userTypeEnum})
// 只更新用户 ID(不改变用户类型)
static void setUid(String uid)
// 更新用户类型
void updateUserType(String newType)
// 登出(清空 uid 和 userType)
static void logoutUser()
// 更新渠道标识
static void setChannel(String channel)
sid(会话 ID)由 SessionManager 自动管理,无需手动干预。
获取设备公共字段(服务端复用)
static Map<String, String> getDeviceCommonFields()
返回字段:device、device_id、device_brand、device_model、user_agent、system_name、system_version、device_fingerprint。可将这些字段传递给服务端,由服务端上报事件时附加。
域名与事件类型管理
encryptedConfig 格式
encryptedConfig 是宿主 App 从自己服务端获取的 Base64 编码 AES-GCM 密文,SDK 内部解密后格式为:
{
"domainList": [
"https://api1.example.com",
"https://api2.example.com"
],
"eventList": [
"app_page_view",
"page_click",
"video_event",
"ad_impression"
]
}
domainList:上报域名列表,SDK 启动时会并发测速(GET 请求),选择响应最快的域名上报。eventList:启用的事件类型白名单。不在列表中的事件会被静默丢弃。传空列表时,所有事件类型均允许上报(向后兼容)。
测速与降级策略
- 解密
encryptedConfig成功 → 用domainList并发测速,选最快域名; encryptedConfig为 null 或解密失败 → 尝试从磁盘缓存(data_plus_domains_cache.json)读取上次保存的域名列表;- 缓存也为空 → 上报 URL 保持未配置,事件缓存在本地,等
refreshDomainConfig()成功后自动上报。
验证加密配置(调试包)
final result = AnalyticsSdk.instance.validateEncryptedConfig(encryptedString);
if (result['success'] == true) {
print('条目数量: ${result['count']}');
print('第一条预览: ${result['preview']}');
} else {
print('验证失败: ${result['error']}');
}
此方法仅在 debug 包可用,release 包直接返回
{'success': false}。
SDK 配置(可选)
通过 SdkConfig.configure() 调整 SDK 参数,需在 init() 之前调用:
import 'package:analytics_sdk/config/sdk_config.dart';
SdkConfig.configure(
maxQueueSize: 5000,
uploadInterval: const Duration(seconds: 10),
maxCacheLines: 2000,
connectionTimeout: const Duration(seconds: 15),
);
常用配置:
队列与批量
maxQueueSize:事件队列最大容量(默认5000)autoUploadThreshold:达到阈值时自动触发上报(默认10)maxBatchSize:单次上报最大事件数(默认500)maxJsonSize:单次请求 JSON 最大字节数(默认5MB)maxSingleEventSize:单条事件最大字节数,超过则丢弃(默认300KB)
定时器
uploadInterval:定时上报间隔(默认5秒)clickThrottleDuration:全局点击节流时间(默认300毫秒)speedTestTimeout:域名测速超时(默认5秒)
网络超时
connectionTimeout:HTTP 连接超时(默认10秒)readTimeout:HTTP 读取超时(默认30秒)
缓存相关
cacheFileName:事件缓存文件名(默认data_plus_events_cache.jsonl)maxCacheLines:缓存文件最大行数(默认2000)maxCacheBytes:缓存文件最大字节数(默认2MB)tombstoneCompactionThreshold:tombstone 文件达到此条目数时触发主缓存压缩(默认200)tombstoneFileName:tombstone 文件名(默认data_plus_events_tombstone.txt)
重试
baseRetryDelay:基础重试延迟(默认2秒)maxRetryDelay:最大重试延迟(默认60秒)maxBackoffLevel:最大退避级别(默认5)
广告去重
maxAdImpressionCapacity:广告去重集合最大容量,FIFO 淘汰(默认10000)
恢复所有默认值:
SdkConfig.reset();
更多文档
本仓库根目录提供了更详细的开发文档:
- SDK_USAGE_GUIDE.md:完整使用指南,包含所有内置事件的数据结构与字段说明、会话规则、自动埋点细节、性能与容错机制说明等。
建议接入前先通读 SDK_USAGE_GUIDE.md,本 README 主要用于快速了解和集成。
手动上报事件
void track(dynamic event)
上报任意事件,传入 SDK 内置事件类即可。
1. App 安装事件(客户端)
首次安装启动时,读取剪贴板获取到
traceId后上报。
AnalyticsSdk.instance.track(AppInstallEvent(
traceId: 'landing_page_trace_id_xxx', // 落地页点击生成的唯一 ID
));
2. 视频行为事件(客户端)
import 'package:analytics_sdk/enum/video_event_enum.dart';
AnalyticsSdk.instance.track(VideoEvent(
videoId: 'video_123',
videoTitle: '示例视频',
videoTypeId: 'type_1',
videoTypeName: '娱乐',
videoTagKey: 'tag_1,tag_2',
videoTagName: '热门,最新',
videoDuration: 300,
playDuration: 60,
playProgress: 20,
videoBehavior: VideoEventEnum.VIDEO_PLAY,
mediaId: '',
));
3. 小说阅读事件(客户端)
import 'package:analytics_sdk/enum/read_behavior_enum.dart';
AnalyticsSdk.instance.track(NovelEvent(
novelId: 'novel_123',
novelTitle: '示例小说',
novelTypeId: 'type_1',
novelTypeName: '玄幻',
novelTagKey: 'tag_1,tag_2',
novelTagName: '玄幻,穿越',
readProgress: 50,
pageNo: 10,
novelBehavior: ReadBehaviorEnum.PAGE_NEXT,
));
4. 漫画阅读事件(客户端)
AnalyticsSdk.instance.track(ComicEvent(
comicId: 'comic_123',
comicTitle: '示例漫画',
comicTypeId: 'type_1',
comicTypeName: '热血',
comicTagKey: 'tag_1,tag_2',
comicTagName: '热血,冒险',
readProgress: 30,
pageNo: 5,
comicBehavior: ReadBehaviorEnum.VIEW,
));
5. 广告展示事件(客户端,带自动去重)
SDK 会对
adId进行 session 级别去重:同一个adId在当前 session 内只会上报一次。
import 'package:analytics_sdk/enum/ad_type_enum.dart';
AnalyticsSdk.instance.track(AdImpressionEvent(
pageKey: PageLifecycleObserver.currentPageKey,
pageName: PageNameMapper.getPageName(PageLifecycleObserver.currentPageKey),
adSlotKey: 'home_banner_1',
adSlotName: '首页顶部Banner',
adId: 'ad_123,ad_456,ad_789', // 多个 adId 英文逗号分隔
creativeId: 'cr_001,cr_002,cr_003', // 与 adId 一一对应,可传 null
adType: AdTypeEnum.banner,
));
6. 广告点击事件(客户端)
AnalyticsSdk.instance.track(AdClickEvent(
pageKey: PageLifecycleObserver.currentPageKey,
pageName: PageNameMapper.getPageName(PageLifecycleObserver.currentPageKey),
adSlotKey: 'home_banner_1',
adSlotName: '首页顶部Banner',
adId: 'ad_123',
adType: AdTypeEnum.banner,
));
7. APP 广告行为事件(客户端)
用于上报 APP 内置活动弹窗、运营 Banner 等的展示、点击、关闭行为。
import 'package:analytics_sdk/enum/banner_event_enum.dart';
AnalyticsSdk.instance.track(AdvertisingEvent(
eventType: BannerEventEnum.CLICK.label, // 'click'
advertisingKey: 'home_banner',
advertisingName: '首页Banner',
advertisingId: 'ad_001',
));
8. 关键词搜索事件(客户端)
AnalyticsSdk.instance.track(KeywordSearchEvent(
keyword: '示例关键词',
searchResultCount: 100,
));
9. 关键词搜索结果点击事件(客户端)
import 'package:analytics_sdk/enum/click_item_type_enum.dart';
AnalyticsSdk.instance.track(KeywordClickEvent(
keyword: '示例关键词',
clickItemId: 'video_123',
clickItemType: ClickItemTypeEnum.VIDEO,
clickPosition: 1,
));
10. 推荐列表展示事件(客户端)
AnalyticsSdk.instance.track(RecommendListViewEvent(
pageKey: 'home',
pageName: '首页',
recommendContentType: RecommendContentTypeEnum.video,
recommendTraceId: 'rec_trace_abc123',
recommendId: 'vid_001,vid_002,vid_003',
));
11. 推荐列表点击事件(客户端)
AnalyticsSdk.instance.track(RecommendListClickEvent(
pageKey: 'home',
pageName: '首页',
recommendTraceId: 'rec_trace_abc123',
recommendId: 'vid_001',
));
强制立即上报
await AnalyticsSdk.instance.flush();
资源清理
建议在应用退出前调用,SDK 会先尝试上报剩余事件,再释放所有资源:
await AnalyticsSdk.instance.dispose();
用户类型管理
import 'package:analytics_sdk/enum/user_type_enum.dart';
// 用户登录时设置用户 ID 和类型
AnalyticsSdk.setUserIdAndType(
userId: 'user_123',
userTypeEnum: UserTypeEnum.vip,
);
// 只更新用户类型(升级会员等)
AnalyticsSdk.instance.updateUserType(UserTypeEnum.vip.label);
// 用户登出
AnalyticsSdk.logoutUser();
页面名称映射
SDK 使用路由 key(RouteSettings.name)作为 pageKey,建议在应用启动时统一注册映射:
import 'package:analytics_sdk/manager/page_name_manager.dart';
// 批量添加
PageNameMapper.addMappings({
'/': '首页',
'/video_detail': '视频详情页',
'/user_center': '个人中心',
'/search': '搜索页',
});
// 单条添加
PageNameMapper.addMapping('/settings', '设置页');
// 获取
String pageName = PageNameMapper.getPageName('/video_detail'); // '视频详情页'
动态获取当前页面信息
import 'package:analytics_sdk/observer/page_lifecycle_observer.dart';
import 'package:analytics_sdk/manager/page_name_manager.dart';
AnalyticsSdk.instance.track(AdImpressionEvent(
pageKey: PageLifecycleObserver.currentPageKey,
pageName: PageNameMapper.getPageName(PageLifecycleObserver.currentPageKey),
adSlotKey: 'home_banner_1',
adSlotName: '首页顶部Banner',
adId: 'ad_123',
adType: AdTypeEnum.banner,
));
事件数据结构
所有事件上报时都包含以下公共字段(顶层):
| 字段名 | 类型 | 说明 |
|---|---|---|
event |
String | 事件类型,如 video_event |
channel |
String | 渠道标识 |
app_id |
String | 应用 ID |
uid |
String | 用户 ID |
sid |
String | 会话 ID(冷启动或后台超 30 分钟时重新生成) |
client_ts |
int | 事件对象创建时的时间戳(10位秒级) |
device |
String | 设备类型(SDK 自动检测) |
device_id |
String | 设备唯一标识(由 init(deviceId) 传入) |
device_brand |
String | 设备品牌(SDK 自动检测) |
device_model |
String | 设备型号(SDK 自动检测) |
user_agent |
String | 客户端 UA(SDK 自动检测) |
system_name |
String | 操作系统名称(SDK 自动检测) |
system_version |
String | 操作系统版本(SDK 自动检测) |
device_fingerprint |
String | 设备指纹(SHA-256;因子全空时降级为 UUID v4) |
fp_version |
String | 指纹生成规则版本号(当前为 1) |
event_id |
String | 事件去重 ID(双重 MD5) |
payload |
Object | 事件业务字段 |
device_fingerprint和fp_version均不参与event_id的 MD5 计算。
上报机制
批量上报地址
POST {fastestDomain}/api/eventTracking/batchReport.json?appId={appId}
Content-Type: application/json
Body: [ {...事件1}, {...事件2}, ... ]
触发条件
- 队列达到 10 条(
SdkConfig.autoUploadThreshold) - 定时器每 5 秒触发(
SdkConfig.uploadInterval) - 手动调用
flush()
本地持久化
- 事件自动保存到
data_plus_events_cache.jsonl(应用文档目录) - 应用重启后自动从缓存恢复并上报;恢复时会过滤掉已通过 tombstone 标记为已上报的事件,以及当前事件类型白名单中未启用的事件类型
- 上报成功后,已上报的
event_id追加写入 tombstone 文件(data_plus_events_tombstone.txt),避免每次上报都重写整个缓存文件;tombstone 条目达到 200 条(SdkConfig.tombstoneCompactionThreshold)时触发一次主缓存压缩合并
重试机制
失败后指数退避重试:2s → 4s → 8s → 16s → 32s,超过 60s 上限后不再翻倍,最多重试 5 级。
事件去重
通用事件去重
每个事件都有唯一的 event_id(基于所有字段的双重 MD5 计算),服务端可以使用 event_id 去重。
广告展示事件去重
AdImpressionEvent 在客户端进行额外去重:
- 基于
adId去重,同一adId在当前 session 只上报一次 - 支持多个
adId逗号分隔,只上报未曾上报的 ID - 去重记录在内存中,应用重启后清空
- 容量上限默认 10000 条,超出时 FIFO 淘汰最旧记录
去重管理 API:
import 'package:analytics_sdk/manager/ad_impression_manager.dart';
// 检查广告 ID 是否已上报
bool isReported = AdImpressionManager.instance.isReported('ad_123');
// 获取多个 ID 中哪些未上报
List<String> unreported = AdImpressionManager.instance.getUnreportedAdIds('ad_123,ad_456');
// 清除已上报记录(测试用)
AdImpressionManager.instance.clear();
完整示例
import 'package:analytics_sdk/analytics_sdk.dart';
import 'package:analytics_sdk/config/sdk_config.dart';
import 'package:analytics_sdk/entity/video_event.dart';
import 'package:analytics_sdk/entity/ad_impression_event.dart';
import 'package:analytics_sdk/entity/advertising_event.dart';
import 'package:analytics_sdk/enum/user_type_enum.dart';
import 'package:analytics_sdk/enum/video_event_enum.dart';
import 'package:analytics_sdk/enum/ad_type_enum.dart';
import 'package:analytics_sdk/enum/banner_event_enum.dart';
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/observer/page_lifecycle_observer.dart';
import 'package:analytics_sdk/widget/global_click_wrapper.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 可选:调整 SDK 默认参数
SdkConfig.configure(
uploadInterval: const Duration(seconds: 10),
autoUploadThreshold: 20,
);
// 配置页面名称映射
PageNameMapper.addMappings({
'/': '首页',
'/video_detail': '视频详情页',
'/user_center': '个人中心',
});
// 从自己的服务端获取加密配置
final encryptedConfig = await yourApiClient.fetchEncryptedConfig();
// 初始化 SDK(设备信息自动采集,无需外部传入)
await AnalyticsSdk.instance.init(
appId: 'your_app_id',
encryptedConfig: encryptedConfig,
channel: 'official',
uid: '', // 未登录时传空
deviceId: 'device_unique_id',
enableDebugBanner: true, // 仅 debug 包生效
);
// 设置登录用户信息(已登录时)
AnalyticsSdk.setUserIdAndType(
userId: 'user_123',
userTypeEnum: UserTypeEnum.vip,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return AnalyticsDebugBanner( // 仅 debug 包显示调试悬浮条
child: MaterialApp(
navigatorObservers: [
AnalyticsSdk.instance.pageObserver, // 页面生命周期自动埋点
],
builder: (context, child) {
return GlobalClickWrapper( // 全局点击自动抓取
child: child!,
);
},
home: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('首页')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
AnalyticsSdk.instance.track(VideoEvent(
videoId: 'video_123',
videoTitle: '示例视频',
videoTypeId: 'type_1',
videoTypeName: '娱乐',
videoTagKey: 'hot',
videoTagName: '热门',
videoDuration: 300,
playDuration: 60,
playProgress: 20,
videoBehavior: VideoEventEnum.VIDEO_PLAY,
));
},
child: const Text('上报视频播放'),
),
ElevatedButton(
onPressed: () {
AnalyticsSdk.instance.track(AdImpressionEvent(
pageKey: PageLifecycleObserver.currentPageKey,
pageName: PageNameMapper.getPageName(PageLifecycleObserver.currentPageKey),
adSlotKey: 'home_banner_1',
adSlotName: '首页顶部Banner',
adId: 'ad_123,ad_456',
adType: AdTypeEnum.banner,
));
},
child: const Text('上报广告展示'),
),
ElevatedButton(
onPressed: () {
AnalyticsSdk.instance.track(AdvertisingEvent(
eventType: BannerEventEnum.CLICK.label,
advertisingKey: 'home_popup',
advertisingName: '首页弹窗',
advertisingId: 'adv_001',
));
},
child: const Text('上报弹窗点击'),
),
ElevatedButton(
onPressed: () => AnalyticsSdk.instance.flush(),
child: const Text('立即上报'),
),
],
),
),
);
}
}
// 应用退出时清理资源
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
AnalyticsSdk.instance.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.detached) {
AnalyticsSdk.instance.dispose();
}
}
}
枚举类型参考
UserTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
UserTypeEnum.normal |
normal |
普通用户 |
UserTypeEnum.vip |
vip |
VIP 用户 |
DeviceEnum
| 枚举值 | label | 说明 |
|---|---|---|
DeviceEnum.android |
Android |
Android 设备 |
DeviceEnum.iOS |
iOS |
iOS 设备 |
DeviceEnum.pc |
PC |
PC 设备 |
VideoEventEnum
| 枚举值 | key | 说明 |
|---|---|---|
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 |
读完 |
AdTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
AdTypeEnum.icon |
icon |
图标广告 |
AdTypeEnum.banner |
banner |
Banner |
AdTypeEnum.feed |
feed |
信息流 |
AdTypeEnum.player |
player |
播放器广告 |
AdTypeEnum.splash |
splash |
开屏广告 |
AdTypeEnum.text |
text |
文字广告 |
BannerEventEnum
| 枚举值 | label | 说明 |
|---|---|---|
BannerEventEnum.CLICK |
click |
点击 |
BannerEventEnum.CLOSE |
close |
关闭 |
BannerEventEnum.SHOW |
show |
展示 |
AuthTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
AuthTypeEnum.phone |
phone |
手机号 |
AuthTypeEnum.deviceid |
deviceid |
设备 ID |
AuthTypeEnum.email |
email |
邮箱 |
AuthTypeEnum.username |
username |
账号 |
ClickItemTypeEnum
| 枚举值 | key | 说明 |
|---|---|---|
ClickItemTypeEnum.VIDEO |
video |
视频 |
ClickItemTypeEnum.NOVEL |
novel |
小说 |
ClickItemTypeEnum.COMIC |
comic |
漫画 |
RecommendContentTypeEnum
| 枚举值 | label | 说明 |
|---|---|---|
RecommendContentTypeEnum.video |
video |
视频 |
RecommendContentTypeEnum.novel |
novel |
小说 |
RecommendContentTypeEnum.comic |
comic |
漫画 |
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 |
内容购买 |
注意事项
-
初始化时机:建议在
main()函数中、runApp()之前初始化 SDK。 -
encryptedConfig 由宿主 App 负责获取:SDK 不自主发起配置接口请求,宿主 App 需自行从服务端获取加密配置串,再传入
init()或refreshDomainConfig()。 -
设备信息自动采集:品牌、型号、系统名称和版本由 SDK 通过
device_info_plus自动读取,无需外部传入。deviceId仍由宿主 App 提供(Android IMEI/OAID、iOS IDFV 等)。 -
页面路由命名:确保所有页面路由都有名称,SDK 才能正确追踪页面生命周期:
MaterialApp( routes: { '/': (context) => HomePage(), // ✅ 有路由名称 '/detail': (context) => DetailPage(), }, ) -
用户信息更新:用户登录、登出、升级会员等操作时,记得调用相应的用户管理方法。
-
网络权限:
- Android:在
AndroidManifest.xml中添加INTERNET权限 - iOS:确保
Info.plist中NSAppTransportSecurity配置正确
- Android:在
-
资源清理:应用退出前建议调用
dispose()清理资源。 -
事件过滤:未在
encryptedConfig.eventList中启用的事件类型会被静默丢弃,可通过日志确认。eventList为空时默认允许所有事件上报。 -
AdImpressionEvent 的 adType 需传枚举:不能传字符串,需使用
AdTypeEnum.banner等枚举值。
调试
AnalyticsDebugBanner
在 init() 时设置 enableDebugBanner: true,并在 Widget 树根部加入 AnalyticsDebugBanner(仅 debug 包显示):
import 'package:analytics_sdk/analytics_sdk.dart';
AnalyticsDebugBanner(
child: MaterialApp(...),
)
悬浮条显示 7 个埋点流程步骤:①域名配置 → ②SDK初始化 → ③域名列表 → ④域名测速 → ⑤上报地址 → ⑥待上报队列 → ⑦最近上报结果。
通过代码获取调试信息
// 获取摘要
final info = AnalyticsSdk.instance.getDebugInfo();
print(info['inited']); // SDK 是否初始化
print(info['queueLength']); // 当前队列长度
print(info['reportUrl']); // 上报 URL
// 获取详细步骤
final steps = AnalyticsSdk.instance.getDebugSteps();
for (final step in steps) {
print('${step['name']}: ${step['status']} - ${step['detail']}');
}
控制日志输出
import 'package:analytics_sdk/utils/logger.dart';
Logger.enabled = false; // 关闭所有日志
Logger.enabled = true; // 开启日志(debug 包默认已开启)
常见问题 FAQ
Q: 事件没有上报成功怎么办?
A:
- 检查网络连接和权限
- 确认
encryptedConfig是否传入且格式正确(可用validateEncryptedConfig()验证) - 查看控制台日志了解具体错误
- 检查是否调用了
flush()或在应用退出前调用了dispose()
Q: 如何验证 encryptedConfig 是否正确?
A: 在 debug 包中调用:
final result = AnalyticsSdk.instance.validateEncryptedConfig(encryptedString);
print(result);
Q: 事件上报延迟多久?
A: 正常情况下:
- 队列达到 10 条时立即上报
- 每 5 秒定时上报一次
- 调用
flush()立即上报
Q: 为什么某些事件没有上报?
A: 可能原因:
- 事件类型不在
encryptedConfig.eventList白名单中 - 广告展示事件被 session 级别去重过滤
- 事件单条超过 300KB 被丢弃
- 队列满(默认上限 5000 条)
- 上报 URL 未就绪(域名测速未完成或 encryptedConfig 未传入)
Q: 如何动态开启/关闭某些事件的上报?
A: 通过服务端更新 encryptedConfig 中的 eventList,然后调用:
await AnalyticsSdk.instance.refreshDomainConfig(encryptedConfig: newEncryptedConfig);
Q: 如何自定义 SDK 配置参数?
A:
import 'package:analytics_sdk/config/sdk_config.dart';
SdkConfig.configure(
maxQueueSize: 10000,
uploadInterval: const Duration(seconds: 30),
connectionTimeout: const Duration(seconds: 15),
);
await AnalyticsSdk.instance.init(...);
Q: 如何控制日志输出?
A:
import 'package:analytics_sdk/utils/logger.dart';
Logger.enabled = false;
支持的事件类型
| 事件类型字符串 | 说明 | 上报方式 |
|---|---|---|
app_install |
App 安装 | 客户端 |
navigation |
导航事件 | 自动 |
app_page_view |
应用页面展示 | 自动 |
page_click |
应用页面点击 | 自动 |
page_lifecycle |
页面生命周期 | 自动 |
advertising |
APP 广告行为(弹窗/运营 Banner) | 客户端 |
ad_click |
广告点击 | 客户端 |
ad_impression |
广告展示 | 客户端 |
video_event |
视频行为 | 客户端 |
novel_event |
小说阅读 | 客户端 |
comic_event |
漫画阅读 | 客户端 |
keyword_search |
关键词搜索 | 客户端 |
keyword_click |
关键词搜索结果点击 | 客户端 |
recommend_list_view |
推荐列表展示 | 客户端 |
recommend_list_click |
推荐列表点击 | 客户端 |
依赖项
flutter: Flutter SDK (>=3.3.0)uuid: UUID 生成(设备指纹因子全空时的降级兜底)path_provider: ^2.1.4: 本地文件路径获取synchronized: ^3.1.0: 并发控制锁device_info_plus: ^10.0.0: 自动采集设备品牌、型号、系统名称与版本pointycastle: 3.7.3: AES-GCM 解密crypto: ^3.0.2: SHA-256 / MD5 哈希计算
环境要求
- Dart SDK:
>=3.4.4 <4.0.0 - Flutter:
>=3.3.0