本文详细介绍用户体验监控 Android SDK 的各功能接口,帮助您更灵活、深度地使用 SDK。
初始化
SDK 通过
TDEM.init(config) 创建实例并自动开始采集,重复调用会返回同一实例;初始化后通过 TDEM.getInstance() 获取单例调用实例 API,未初始化时返回 null。TDEM 上的静态方法共5个:方法 | 用途 |
TDEM.init(config) | 创建并启动 SDK,返回实例 |
TDEM.getInstance() | 获取单例,未初始化返回 null |
TDEM.addInitListener(listener) | 注册启动结算监听,可在 init 之前调用 |
TDEM.removeInitListener(listener) | 注销启动结算监听 |
TDEM.endLaunch() | 业务就绪时手动封口启动测量 |
另有日志级别常量
TDEM.LEVEL_DEBUG / TDEM.LEVEL_INFO / TDEM.LEVEL_WARN / TDEM.LEVEL_ERROR 与版本常量 TDEMCore.SDK_VERSION。val tdem = TDEM.getInstance()
TDEM tdem = TDEM.getInstance(); // 未初始化时返回 null
启动结算可异步感知。
TDEM.addInitListener 既可在 TDEM.init 之前注册,也可在之后注册;远程配置闸门落定后回调一次,若注册时决策已落定则立即回放结果。import com.tencent.tdem.core.TDEMInitListenerval listener = object : TDEMInitListener {override fun onInitSettled(started: Boolean, remoteConfig: RemoteConfigResult?) {// started:是否已进入采集态// remoteConfig:本次决策使用的远程配置;拉取耗尽且无缓存时为 null}}TDEM.addInitListener(listener) // 可在 TDEM.init 之前调用TDEM.removeInitListener(listener) // 注销
import com.tencent.tdem.core.TDEMInitListener;TDEMInitListener listener = (started, remoteConfig) -> {// started:是否已进入采集态// remoteConfig:本次决策使用的远程配置;拉取耗尽且无缓存时为 null};TDEM.addInitListener(listener);TDEM.removeInitListener(listener);
回调覆盖三种结果:成功启动、远端关闭或未抽中、拉取耗尽;若
id / url 为空导致初始化被跳过,同样会以 started=false 回调。实例层另有 registerInitListener / unregisterInitListener,语义相同;接入方应统一使用上述入口层静态方法,避免 JVM 签名冲突。自定义事件
tdem?.track("button_click",tags = mapOf("button_id" to "submit"),properties = mapOf("page" to "/checkout"),)
import java.util.HashMap;import java.util.Map;Map<String, String> tags = new HashMap<>();tags.put("button_id", "submit");Map<String, Object> properties = new HashMap<>();properties.put("page", "/checkout");tdem.track("button_click", tags, properties);
自定义测速
// 直接上报耗时(毫秒)tdem?.measure("api_latency", 320, tags = mapOf("api" to "/user/info"))// 或使用计时器tdem?.startMeasure("render")// ... 执行操作 ...val duration = tdem?.endMeasure("render") // 返回耗时(ms),未找到 start 返回 -1
// 直接上报耗时(毫秒)Map<String, String> tags = new HashMap<>();tags.put("api", "/user/info");tdem.measure("api_latency", 320, tags, new HashMap<String, Object>());// 或使用计时器tdem.startMeasure("render");// ... 执行操作 ...long duration = tdem.endMeasure("render"); // 返回耗时(ms),未找到 start 返回 -1
measure / endMeasure 支持省略末尾可选参数的短重载,因此 Java 侧也可写成 tdem.measure("api_latency", 320) 或 tdem.endMeasure("render")。异常上报
try {riskyOperation()} catch (e: Exception) {tdem?.captureException(e, tags = mapOf("module" to "payment"))}
Map<String, Object> crashTags = new HashMap<>();crashTags.put("module", "payment");try {riskyOperation();} catch (Exception e) {tdem.captureException(e, crashTags);}
captureException 只有
(Throwable, Map<String, Object>) 一种签名,没有 level 参数,也没有可省略参数的重载;Java 侧直接传异常与标签即可。用户标识管理
tdem?.setUser("user-456") // 设置用户tdem?.clearUser() // 清除用户
tdem.setUser("user-456"); // 设置用户tdem.clearUser(); // 清除用户
Compose 组件标记
Compose 场景下的组件标记依赖
com.tencent.tdem:tdem-behavior-compose(版本与 tdem 保持一致)。该库提供三种标记入口,可按需组合使用:本节示例均为 Kotlin。
@Composable 函数只能由 Kotlin 编译器产出,下列示例都写在 Composable 作用域内,SDK 也未提供等价的 Java 入口;Java 项目使用这些能力时,这部分代码同样需要写在 Kotlin 文件中。import com.tencent.tdem.behavior.compose.tdemTagimport com.tencent.tdem.behavior.compose.TDEMTracedimport com.tencent.tdem.behavior.compose.withTDEMPageTracking// 1. 给单个 Composable 打标签Button(modifier = Modifier.tdemTag("buy_button")) { Text("购买") }// 2. 包裹一段 Composable,为整个子树生成一个可定位节点TDEMTraced("CheckoutBody") {CheckoutContent()}// 3. 让 NavHost 的页面流转自动上报,enabled 默认 trueval navController = rememberNavController().withTDEMPageTracking()
三种入口的作用与说明如下:
方法 | 说明 |
Modifier.tdemTag(tag) | 为单个组件写入 TDEMTag 语义属性,该值会作为组件的元素标识进入点击、长按事件 |
TDEMTraced(tag, modifier) { } | 用 Layout 包裹子树并打标签,适用于一组控件共用同一个标识 |
NavHostController.withTDEMPageTracking(enabled) | 追踪 NavHost 的页面流转并上报 page_view;enabled 默认 true,传 false 可临时关闭 |
引入 Kotlin 编译器插件后无需手动调用
tdemTag:插件会在编译期为每个 @Composable 函数自动注入标签,取值为该函数名。androidx、com.tencent.tdem 包下的函数与非 @Composable 函数不注入;excludeComposeModifiers = true 时整块 Compose 标签注入都不生效(标签与 Modifier 都不注入),等价于关闭本能力。需要覆盖自动生成的名称时,可再手动调用 tdemTag 显式指定。注意:
Compose 的页面流转有两条路径:
withTDEMPageTracking() 在代码中显式包裹,tdem-plugin 的 instrumentComposeNavigation 在编译期自动插桩。两者只需启用其一,同时开启不会重复上报,但会多一次无谓的注册。设备标识管理
tdem?.setDeviceId("device-abc") // 设置设备标识,由宿主提供tdem?.clearDeviceId() // 清除设备标识,回落 not_set
tdem.setDeviceId("device-abc"); // 设置设备标识,由宿主提供tdem.clearDeviceId(); // 清除设备标识,回落 not_set
设备标识由业务侧提供,SDK 不会自行读取
ANDROID_ID 等系统标识;未设置或传空白时上报字面量 not_set。也可在 TDEMConfiguration 的 deviceId 中于初始化时一并设置。全局标签管理
tdem?.setTags(mapOf("role" to "admin", "team" to "dev")) // 设置/追加标签tdem?.removeTags(listOf("team")) // 移除指定标签tdem?.clearTags() // 清空所有标签
Map<String, Object> tags = new HashMap<>();tags.put("role", "admin");tags.put("team", "dev");tdem.setTags(tags); // 设置/追加标签tdem.removeTags(Arrays.asList("team")); // 移除指定标签tdem.clearTags(); // 清空所有标签
页面管理
// 手动逻辑页(LIFO,覆盖自动 Activity/Fragment/Compose 页)tdem?.startPage("checkout")tdem?.leavePage() // LIFO pop,栈空时幂等 no-op// 替换当前逻辑页并发 page_view(navigation_type=replace,RN SPA 切页用)tdem?.setPage("/modal/confirm", pageTitle = "确认弹窗")// 三参:额外指定本次页面浏览的 viewId(不传则每次 setPage 自动生成新的 UUID)tdem?.setPage("/modal/confirm", pageTitle = "确认弹窗", viewId = "confirm_dialog_v1")
// 手动逻辑页(LIFO,覆盖自动 Activity/Fragment/Compose 页)tdem.startPage("checkout");tdem.leavePage(); // LIFO pop,栈空时幂等 no-op// 替换当前逻辑页并发 page_view(navigation_type=replace,RN SPA 切页用)tdem.setPage("/modal/confirm", "确认弹窗");// 三参:额外指定本次页面浏览的 viewId(不传则每次 setPage 自动生成新的 UUID)tdem.setPage("/modal/confirm", "确认弹窗", "confirm_dialog_v1");
viewId 是该逻辑页的页面浏览实例标识。SDK 在每次页面切换时生成新的 viewId,并在上报事件的顶层 view_id 字段带上它,用于标识该事件发生在哪一次页面浏览中。setPage 传入 viewId 时直接采用传入值(需自行保证唯一性);不传则由 SDK 自动生成,通常无需干预。上下文与状态查询
除写入接口外,SDK 也提供读取当前运行状态的接口,便于接入自检与问题定位。
tdem?.getTags() // 当前全局标签快照,Map<String, Any>tdem?.currentPageUrl // 当前页面标识tdem?.currentViewId // 当前页面浏览实例 ID,与事件 view_id 同源tdem?.getCurrentSessionId() // 当前会话 ID,会话未就绪返回 ""tdem?.getCurrentReplayId() // 当前回放 ID,未启用回放返回 ""tdem?.userId // 当前用户标识,未设置返回 nulltdem?.deviceId // 当前设备标识,未设置返回 not_settdem?.initialized // SDK 是否已进入采集态
import java.util.Map;Map<String, Object> tags = tdem.getTags();String pageUrl = tdem.getCurrentPageUrl();String viewId = tdem.getCurrentViewId();String sessionId = tdem.getCurrentSessionId();String replayId = tdem.getCurrentReplayId();String userId = tdem.getUserId();String deviceId = tdem.getDeviceId();
getCurrentSessionId() 在会话尚未就绪时返回空串;getCurrentReplayId() 在未启用回放时返回空串;currentPageUrl 的取值为「手动页面栈顶 > 自动识别的 Activity / Fragment / Compose 页 > unknown」。currentViewId 即事件上报中的 view_id,随页面切换刷新;当前无有效页面(如处于后台或尚未识别到页面)时返回空串。resolveReplayLink(timestampMs) 按事件时间戳反查所属回放段,返回回放 ID 与相对段起点的偏移毫秒数,无命中窗口时返回 null。返回对象位于内部包(com.tencent.tdem.internal.v6$a),当前版本的访问器为混淆后的 b()(回放 ID)与 a()(偏移毫秒数),属性名可能随版本变化,取值时建议以方法调用为准:val link = tdem?.resolveReplayLink(eventTimeMs)if (link != null) {println("replay=${link.b()}, offset=${link.a()}ms") // b()=回放 ID,a()=偏移毫秒数}
// 返回类型是 Kotlin 嵌套类(内部包),Java 源码里无法写出它的类名,// 用 var 让编译器推断;Java 8 项目可声明为 Object,但那样拿不到 b() / a()。var link = tdem.resolveReplayLink(eventTimeMs);if (link != null) {System.out.println("replay=" + link.b() + ", offset=" + link.a() + "ms"); // b()=回放 ID,a()=偏移毫秒数}
以下两个接口属进阶用法,一般由插件或桥接层使用:
isPluginEnabled(pluginEnabled, remoteEnabled) 做插件挂载的两级决策(远程配置明确关闭时返回 false,否则取本地配置)。putContextField(key, value) 向 Protocol v2 批量上下文追加自定义字段(key 空白或 value 为空时忽略)。挣扎事件上报
// 业务主动上报自定义挣扎事件(无需理解或填写 owner)tdem?.trackStruggle("payment_declined",severity = TDEMStruggleSeverity.HIGH, // LOW(1) / MEDIUM(3) / HIGH(5),默认 MEDIUMproperties = mapOf("reason" to "risk_rejected", "retry_count" to 2),tags = mapOf("channel" to "checkout"),)
// 业务主动上报自定义挣扎事件(无需理解或填写 owner)Map<String, Object> props = new HashMap<>();props.put("reason", "risk_rejected");props.put("retry_count", 2);Map<String, String> tags = new HashMap<>();tags.put("channel", "checkout");tdem.trackStruggle("payment_declined",TDEMStruggleSeverity.HIGH, // LOW(1) / MEDIUM(3) / HIGH(5),默认 MEDIUMprops,tags);
原始事件上报
// 直接上报 TDEMEvent 格式的事件(高级用法,SDK 会自动补齐 page_url、session_id 与 tags)tdem?.reportTDEMEvent(TDEMEvent(eventType = EventType.CUSTOM,eventCategory = EventCategory.CUSTOM,pageUrl = currentPageUrl,sessionId = tdem.getCurrentSessionId(),data = JSONObject().put("event_name", "my_event"),))
// 直接上报 TDEMEvent 格式的事件(高级用法,SDK 会自动补齐 page_url、session_id 与 tags)// 注意:eventId 在 Kotlin 有默认值(自动生成);Java 主构造器不生成,必须自己传非空值,// 传空串会原样上报成空的 event_id。try {JSONObject data = new JSONObject();data.put("event_name", "my_event");tdem.reportTDEMEvent(new TDEMEvent(UUID.randomUUID().toString(), // eventId:Java 侧必须显式给EventType.CUSTOM,EventCategory.CUSTOM,System.currentTimeMillis(),"", // pageUrl 留空则取当前页面"", // sessionId 留空则取当前会话data,"", // replayId"" // viewId));} catch (JSONException e) {// JSONObject.put 是受检异常}
启动封口
业务就绪时结束启动测量。需先配置
LaunchPluginConfig(manualEndEnabled = true):TDEM.endLaunch()
TDEM.endLaunch();
配置修改与销毁
tdem?.setUser("new-user") // 动态修改用户标识tdem?.destroy() // 销毁实例
tdem.setUser("new-user"); // 动态修改用户标识tdem.destroy(); // 销毁实例
版本与日志
SDK 版本号以
TDEMCore.SDK_VERSION 为准,日志级别在初始化时通过 TDEMConfiguration.Builder.logLevel(...) 设定:import com.tencent.tdem.TDEMimport com.tencent.tdem.TDEMConfigurationimport com.tencent.tdem.core.TDEMCoreTDEMCore.SDK_VERSION // 当前 SDK 版本号,如 "0.1.25"TDEMConfiguration.Builder(this).logLevel(TDEM.LEVEL_WARN) // 日志级别,默认 WARN
import com.tencent.tdem.TDEM;import com.tencent.tdem.TDEMConfiguration;import com.tencent.tdem.core.TDEMCore;String version = TDEMCore.SDK_VERSION; // 当前 SDK 版本号,如 "0.1.25"new TDEMConfiguration.Builder(this).logLevel(TDEM.LEVEL_WARN); // 日志级别,默认 WARN
日志级别常量
TDEM.LEVEL_DEBUG / LEVEL_INFO / LEVEL_WARN / LEVEL_ERROR 可直接用于 Builder.logLevel(...),初始化时下推 native。全模块日志统一使用 TDEM-Android 作为 Logcat tag,模块名只出现在日志正文中;tag 不可配置,SDK 也未提供独立的日志开关接口。环境枚举
用于标识应用部署所属环境,区分不同环境的数据上报、日志采集与监控策略,枚举定义如下:
production:生产环境。
development:开发环境。
gray:灰度环境。
pre:预发布环境。
daily:日发布环境。
local:本地环境。
test:测试环境。
others:其他环境(未知值归一于此)。