本文详细介绍用户体验监控 Android 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 与版本常量 TDEM.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")。日志与异常上报
tdem?.captureMessage("用户完成注册", level = "info", tags = mapOf("step" to "register"))try {riskyOperation()} catch (e: Exception) {tdem?.captureException(e, tags = mapOf("module" to "payment"))}
Map<String, Object> tags = new HashMap<>();tags.put("step", "register");tdem.captureMessage("用户完成注册", "info", tags);Map<String, Object> crashTags = new HashMap<>();crashTags.put("module", "payment");try {riskyOperation();} catch (Exception e) {tdem.captureException(e, crashTags);}
captureMessage / captureException 未提供短重载,Java 侧需显式传出全部参数(level 传 null 等同于不写该字段)。用户标识管理
tdem?.setUser("user-456") // 设置用户tdem?.clearUser() // 清除用户
隐私标记
通过
TDEMPrivacyMetadata 主动声明某个控件为敏感,用于自动识别覆盖不到的场景(例如订单金额、收货地址这类字符串本身无语义特征的文案)。import com.tencent.tdem.common.privacy.TDEMPrivacyMetadataimport com.tencent.tdem.common.privacy.TDEMReplayMasking// 标记为敏感:行为监控不采集该控件文本,Session Replay 遮罩该控件及其子树TDEMPrivacyMetadata.setSensitive(orderAmountText, true)TDEMPrivacyMetadata.setSensitive(orderAmountText, false) // 取消标记// 只调 Session Replay 遮罩,不影响文本采集TDEMPrivacyMetadata.setReplayMasking(qrCodeView, TDEMReplayMasking.MASKED)TDEMPrivacyMetadata.setReplayMasking(qrCodeView, TDEMReplayMasking.UNMASKED)TDEMPrivacyMetadata.setReplayMasking(qrCodeView, TDEMReplayMasking.INHERIT) // 恢复默认判定
TDEMReplayMasking 三个取值:INHERIT(默认,按下面的遮罩判定链走)、MASKED(强制遮罩)、UNMASKED(显式不遮罩)。两条通道作用范围与继承语义不同:
通道 | 影响 | 对子控件是否继承 |
setSensitive | 行为监控文本采集 + Session Replay 遮罩 | 行为监控不继承,Session Replay 继承 |
setReplayMasking | 仅 Session Replay 遮罩 | 不继承 |
标记父容器时:Session Replay 会把标记向下传递,等价于遮罩整棵子树;但行为监控只判定被标记的那个控件本身,子控件文本仍会被采集,需逐个标记。
Session Replay 的遮罩判定按下列次序,命中即返回:
1. 业务敏感标记(
setSensitive,含父容器继承):最高优先级,不可被任何标记撤销。2.
sentry-unmask Tag → 不遮罩3.
sentry-mask Tag → 遮罩4.
setReplayMasking(MASKED) → 遮罩5.
setReplayMasking(UNMASKED) → 不遮罩(次序在显式遮罩之后,但仍受第 1 条压制)。6.
unmaskViewClasses 匹配 → 不遮罩7.
maskViewClasses 匹配 → 遮罩因此对已标记敏感的控件调用
setReplayMasking(UNMASKED) 不会放开遮罩。TDEMPrivacyMetadata.isSensitive(view) / replayMasking(view) 可查询当前取值,后者未设置时返回 INHERIT。行为元数据
BehaviorViewMetadata 用于在自动识别拿不到稳定语义时,为单个控件补充行为侧元数据。点击或长按事件仍会正常上报,这些标记只影响事件属性与下游判定。import com.tencent.tdem.behavior.BehaviorViewMetadata// 该控件的点击仍会上报,但不计入「无响应点击」判定BehaviorViewMetadata.setDeadClickIgnored(buyButton, true)BehaviorViewMetadata.isDeadClickIgnored(buyButton) // 查询,未设置返回 false// 把行为事件与业务反馈键关联BehaviorViewMetadata.setStruggleFeedbackKey(buyButton, "coupon_refresh")BehaviorViewMetadata.struggleFeedbackKey(buyButton) // 查询,未设置返回 null
import com.tencent.tdem.behavior.BehaviorViewMetadata;BehaviorViewMetadata.setDeadClickIgnored(buyButton, true);BehaviorViewMetadata.isDeadClickIgnored(buyButton);BehaviorViewMetadata.setStruggleFeedbackKey(buyButton, "coupon_refresh");BehaviorViewMetadata.struggleFeedbackKey(buyButton);
两个标记各自写入行为事件的
event_properties:方法 | 写入的事件属性 | 说明 |
setDeadClickIgnored(view, true) | dead_click_ignored=1 | 该控件的点击不计入「无响应点击」判定;传 false 撤销 |
setStruggleFeedbackKey(view, key) | struggle_feedback_key | 把行为事件与业务反馈键关联; key 为空白或 null 时撤销 |
标记挂在
View 实例上,内部使用弱引用,控件被回收后自动释放,不需要手动清理。注意:
页面 ID 与控件 ID 由 SDK 自动解析(优先取
resource-id,取不到时回落控件层级路径),Android 没有手动指定 page ID / element ID 的接口。若只是想在检测阶段跳过一批控件、不写事件属性,应改用 StrugglePluginConfig 的 deadClickIgnoredViewIds / ignoreDeadClickSelectors 配置项。设备标识管理
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() // 清空所有标签
页面管理
// 手动逻辑页(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 = "确认弹窗")
上下文与状态查询
除写入接口外,SDK 也提供读取当前运行状态的接口,便于接入自检与问题定位。
tdem?.getTags() // 当前全局标签快照,Map<String, Any>tdem?.currentPageUrl // 当前页面标识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 sessionId = tdem.getCurrentSessionId();String replayId = tdem.getCurrentReplayId();String userId = tdem.getUserId();String deviceId = tdem.getDeviceId();
getCurrentSessionId() 在会话尚未就绪时返回空串;getCurrentReplayId() 在未启用回放时返回空串;currentPageUrl 的取值为「手动页面栈顶 > 自动识别的 Activity / Fragment / Compose 页 > unknown」。resolveReplayLink(timestampMs) 按事件时间戳反查所属回放段,返回 replayId 与相对段起点的 offsetMs,无命中窗口时返回 null:val link = tdem?.resolveReplayLink(eventTimeMs)if (link != null) {println("replay=${link.replayId}, offset=${link.offsetMs}ms")}
import com.tencent.tdem.core.replay.ReplayAssociationStore;ReplayAssociationStore.Link link = tdem.resolveReplayLink(eventTimeMs);if (link != null) {System.out.println("replay=" + link.getReplayId() + ", offset=" + link.getOffsetMs() + "ms");}
以下两个接口属进阶用法,一般由插件或桥接层使用:
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"),)
原始事件上报
// 直接上报 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"),))
启动封口
业务就绪时结束启动测量。需先配置
LaunchPluginConfig(manualEndEnabled = true):TDEM.endLaunch()
配置修改与销毁
tdem?.setUser("new-user") // 动态修改用户标识tdem?.destroy() // 销毁实例
版本与日志
通过
TDEM.VERSION 可获取 SDK 版本号字符串。日志由全局单例 TDEMLogger 控制:import com.tencent.tdem.common.log.TDEMLoggerTDEMLogger.enabled = true // 全局日志开关,修改时同步下推 nativeTDEMLogger.tag = "TDEM-Android" // Logcat tag,默认 TDEM-AndroidTDEMLogger.level = TDEM.LEVEL_DEBUG // 级别:DEBUG / INFO / WARN / ERROR,默认 DEBUG
import com.tencent.tdem.common.log.TDEMLogger;TDEMLogger.INSTANCE.setEnabled(true);TDEMLogger.INSTANCE.setTag("TDEM-Android");TDEMLogger.INSTANCE.setLevel(TDEM.LEVEL_DEBUG);
TDEMLogger 是 Kotlin object,Java 侧通过 TDEMLogger.INSTANCE 访问,属性读写为 getEnabled() / setEnabled(...)、getTag() / setTag(...)、getLevel() / setLevel(...)(enabled 为普通声明式属性,Java 侧不是 isEnabled())。日志级别常量 TDEM.LEVEL_* 也可直接用于 TDEMConfiguration.Builder.logLevel(...)。SDK 初始化时会按配置同步一次日志开关与级别,通常无需手动设置。环境枚举
用于标识应用部署所属环境,区分不同环境的数据上报、日志采集与监控策略,枚举定义如下:
production:生产环境。
development:开发环境。
gray:灰度环境。
pre:预发布环境。
daily:日发布环境。
local:本地环境。
test:测试环境。
others:其他环境(未知值归一于此)。