帮你快速理解、总结文档立即下载

数据上报验证

最近更新时间:2026-10-30 21:23:42
本文档已由 AI 辅助审校
我的收藏
本文介绍接入用户体验监控 Android SDK 后,如何验证各监控能力的数据上报是否成功。

前提条件

崩溃(crashPluginConfig)、ANR(anrPluginConfig)、卡顿(lagPluginConfig)、启动(launchPluginConfig)、网络(httpPluginConfig)、挣扎检测(strugglePluginConfig)、设备信息(devicePluginConfig)默认开启。
用户行为(behaviorPluginConfig)、Session Replay(replayPluginConfig)默认关闭,需显式开启。
验证期间建议设置 .logLevel(TDEM.LEVEL_DEBUG),方便观察上报请求与运行日志。日志输出开关由 SDK 内部固定开启,logLevel 只控制输出级别下限,默认 WARN。

步骤1:检查上报请求

初始化后,SDK 会发出下列请求(Session Replay 默认关闭,开启后才有回放上报)。HTTP 2xx / 204 均视为成功:
端点
内容
POST {url}/api/v1/config
远程配置,仅在返回启用时才挂载各监控插件
POST {url}/api/v1/collect/events
标准事件(崩溃 / ANR / 卡顿 / 启动 / 网络 / 行为 / 挣扎 / 自定义事件等)
POST {url}/api/v1/collect/replay
Session Replay 录屏数据
url 填站点根地址即可,SDK 会自行拼接路径,无需手动补 /api/v1。

步骤2:验证各监控能力

崩溃监控

初始化后,可在页面中主动触发 Java 异常来验证:
// 触发未捕获的 Java 异常
throw RuntimeException("tdem verify crash")
Native 崩溃(SIGSEGV / SIGABRT)通过 Signal Handler 捕获,可在 demo 中触发验证。

ANR 监控

主线程 sleep 或死循环可触发 SIGQUIT ANR 检测:
Thread.sleep(10_000) // 主线程阻塞触发 ANR

卡顿监控

主线程执行长时间任务,超过 lagThresholdMs(默认1000ms)即可触发卡顿上报。

启动监控

启动监控默认开启,通过 ASM 编译期插桩实现,早于 SDK 初始化执行,不依赖 TDEM.init 的调用时机。
启动类型共6种:
first_start:首次安装。
upgrade_start:版本升级后首次启动。
cold_start:进程被杀后重新启动。
warm_start:从后台切回且停留超过阈值。
preheat_start:从后台切回且停留不超过阈值,即热启动。
prepare_start:进程由 Service / BroadcastReceiver / ContentProvider 触发。
后台停留阈值由 warmStartThresholdMs 控制,默认 180000ms。
默认在首个 Activity resume 后自动封口并上报事件 app_launch。若配置 .launchPluginConfig(LaunchPluginConfig(manualEndEnabled = true)),则改为等待业务调用 TDEM.endLaunch() 后才封口。
首次安装后的第一次启动不采集:启动监控的开关通过 SharedPreferences 持久化,第一次启动时开关尚未写入,SDK 初始化成功写入后,第二次及后续启动才正式开启。验证启动数据需要至少冷启动两次。
验证方式:冷启动 App 后观察 Logcat 中的启动日志。

网络监控

网络监控默认开启,SDK 自动监听 OkHttp3 / HttpURLConnection / SSE 请求并上报耗时与错误。可通过配置 slowThresholdMs 验证慢请求上报。

大模型 SSE 体验监控

大模型应用普遍用 SSE(Server-Sent Events)流式返回内容,普通 HTTP 请求指标看不出「首字慢」、「长时间不吐字」、「流中断」这类问题。SDK 在网络监控之上对 SSE 流单独采集,上报 sse_call 事件,控制台据此计算 token 吞吐率、首次 token 耗时、错误率、卡死率、流畅度等指标。该能力默认开启,不需要额外接入。
接入前提:业务侧使用 OkHttp 的 EventSource(okhttp-sse)发起 SSE 请求,并传入 EventSourceListener。SDK 在编译期对 okhttp3.internal.sse.RealEventSource 的构造过程插桩,同时把业务中继承 okhttp3.sse.EventSourceListener 的监听器挂到 TDEMEventSourceListener 下。用其他方式自行拼接、手工解析流式响应的写法不采集。
开关:是否采集由编译期 tdem { instrumentSSE = true } 决定,默认 true;关闭后不再插桩,也就不会产生 sse_call。运行期 httpPluginConfig.sseEnabled 默认 true,当前版本仅用于插件初始化时的状态日志,不参与采集判定。
停滞阈值:httpPluginConfig.sseStallThresholdMs,默认60000ms。超过该时长没有新消息,本次连接按停滞处理并上报 stalled。
模型标识:TDEMConfiguration.Builder.llmVersion(...) 设置的值随事件上报为 model_version;未设置时上报 unknown。
resource_type 为 sse 的 sse_call 事件,字段分三组:
连接:http_method、http_status、outcome(success / http_error / transport_error / cancelled / stalled)、server_ip、model_version。
网络耗时:duration、dns_time、tcp_time、ssl_time、request_time、response_time、ttfb。
流式特征:message_count(消息条数)、time_to_first_message(首条消息耗时)、max_message_gap(最大消息间隔,消息数不少于 2 时才有)、last_message_offset(最后一条消息相对请求发起的时间偏移),以及 6 档间隔计数 gap_lt_500_count / gap_500_999_count / gap_1000_2999_count / gap_3000_9999_count / gap_10000_59999_count / gap_ge_60000_count。
验证方式:在控制台 大模型体验 查看 token 吞吐率、首次 token 耗时、错误率、卡死率、流畅度,维度分布中的模型版本与 llmVersion 对应;或直接确认设备已产生 sse_call 上报。

移动拨测

移动拨测从真实设备侧发起网络探测,用于定位域名或端口在特定网络环境下的可达性、丢包与路由问题。SDK 已内置该能力并默认开启,不需要额外接入或配置,探测任务由控制台下发、客户端拉取后执行,结果以 dial_probe(连通性探测)和 dial_mtr(路由追踪)事件上报。
1. 登录用户体验监控控制台,在 客户端总览 中选择目标应用,进入 应用详情。
2. 在左侧菜单栏中选择 移动拨测。
3. 单击 新建任务,填写任务名称、类型(TCP / ICMP)、执行策略与探测地址后保存。
任务保存后不会立即生效,客户端每 10 分钟左右拉取一次任务,最迟一个拉取周期后按新定义执行。同一设备同时最多执行 3 个不同拨测目标,超出部分会被跳过。
验证方式:在 移动拨测 页查看任务状态与探测结果,或确认设备已产生 dial_probe / dial_mtr 上报。

行为监控

开启行为监控后,SDK 自动采集以下行为事件:页面(page_view)、触摸(click / double_tap / long_press / scroll / pinch_zoom)、按键与系统(key_press / screen_toggle / screen_rotation)、输入与表单(text_input / form_focus / form_blur / form_change)。
Kotlin
Java
val config = TDEMConfiguration.Builder(this)
.id("your-project-key")
.url("https://dem.rumt-zh.com")
.behaviorPluginConfig(BehaviorPluginConfig(enabled = true))
.build()
TDEM.init(config)
import android.app.Application;
import com.tencent.tdem.TDEM;
import com.tencent.tdem.TDEMConfiguration;
import com.tencent.tdem.behavior.BehaviorPluginConfig;

TDEMConfiguration config = new TDEMConfiguration.Builder(this)
.id("your-project-key")
.url("https://dem.rumt-zh.com")
.behaviorPluginConfig(new BehaviorPluginConfig(true))
.build();
TDEM.init(config);
手动逻辑页可通过 startPage / leavePage(LIFO)声明:
Kotlin
Java
TDEM.getInstance()?.startPage("checkout")
// ... 页面逻辑 ...
TDEM.getInstance()?.leavePage()
import com.tencent.tdem.TDEM;

TDEM.getInstance().startPage("checkout");
// ... 页面逻辑 ...
TDEM.getInstance().leavePage();

Compose 监控

Compose 的两类能力都要先引入 com.tencent.tdem:tdem-behavior-compose 这个必需依赖(版本与 tdem 一致),再按需开启,且都依赖行为监控已打开(behaviorPluginConfig(enabled = true))。
本节示例均为 Kotlin。@Composable 函数只能由 Kotlin 编译器产出,下列调用点都位于 Composable 作用域内,SDK 也未提供等价的 Java 入口;Java 项目使用本能力时,这部分代码同样需要写在 Kotlin 文件中。
页面流转。两条路径任选其一,效果等价;同时启用也不会重复上报。
路径一:代码中显式包裹。适合不改动构建配置的项目,在创建 NavHostController 时调用 withTDEMPageTracking() 即可:
import androidx.compose.runtime.Composable
import androidx.navigation.compose.rememberNavController
import com.tencent.tdem.behavior.compose.withTDEMPageTracking

@Composable
fun AppNavHost() {
val navController = rememberNavController().withTDEMPageTracking()
// NavHost(navController = navController, ...)
}
路径二:编译期自动插桩。在模块级 build.gradle 中开启,插件会在每个 rememberNavController() 调用之后自动插入同一句 withTDEMPageTracking(),业务代码无需改动:
// 模块级 build.gradle
tdem {
instrumentComposeNavigation = true
}
切换 Navigation destination 后,SDK 会把 destination 的 route 作为当前自动页面并上报一条 page_view,page_source 为 compose;destination 没有 route 时回退取资源的 entry name。
组件标签。引入 Kotlin 编译器插件后,@Composable 函数会自动获得以函数名为值的标签;需要覆盖时手动指定。
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import com.tencent.tdem.behavior.compose.tdemTag

Button(modifier = Modifier.tdemTag("buy_button")) {
Text("购买")
}
点击该 Button 后,上报的 click 事件中 element_id / target_element 即为 buy_button。
注意:
开启了 instrumentComposeNavigation 却没引入 tdem-behavior-compose 时,插桩后的字节码在运行期找不到目标类,会抛 NoClassDefFoundError(构建期不报错)。
只引入 tdem-behavior-compose、不引入 Kotlin 编译器插件时,自动标签不生效;此时只能手动调用 tdemTag。
TDEMTag 与 Compose 原生的 testTag 同级读取,若已有 UI 测试标签,可直接复用 Modifier.testTag(),效果一致。

挣扎检测

挣扎检测默认开启,产出5类子事件:
rage_click:同一元素1秒内连续点击5次。
dead_click:点击后3秒内既无页面跳转也无接口调用,判定为无响应。
error_click:点击后3秒内出现接口错误或 JS / Promise 错误。
long_focus_time:表单控件持续聚焦超过20秒。
back_forward:10秒内连续返回3次。
规则阈值内部写死,接入方不可修改。可通过 clickRulesEnabled / formRulesEnabled / navigationRulesEnabled 分别关闭点击类、表单类与返回类规则。
需要注意的是,默认配置下通常只有 back_forward 能被触发。rage_click / dead_click / error_click 依赖点击事件,long_focus_time 依赖表单聚焦事件,而这些事件全部由用户行为监控(behaviorPluginConfig)产生,行为监控默认关闭,因此这4类子事件会静默失效。要完整验证挣扎检测,需要同时开启行为监控:
Kotlin
Java
val config = TDEMConfiguration.Builder(this)
.id("your-project-key")
.url("https://dem.rumt-zh.com")
.behaviorPluginConfig(BehaviorPluginConfig(enabled = true)) // 必需:提供点击 / 表单事件源
.strugglePluginConfig(StrugglePluginConfig(enabled = true))
.build()
TDEM.init(config)
import android.app.Application;
import com.tencent.tdem.TDEM;
import com.tencent.tdem.TDEMConfiguration;
import com.tencent.tdem.behavior.BehaviorPluginConfig;
import com.tencent.tdem.struggle.StrugglePluginConfig;

TDEMConfiguration config = new TDEMConfiguration.Builder(this)
.id("your-project-key")
.url("https://dem.rumt-zh.com")
.behaviorPluginConfig(new BehaviorPluginConfig(true)) // 必需:提供点击 / 表单事件源
.strugglePluginConfig(new StrugglePluginConfig())
.build();
TDEM.init(config);
back_forward 消费的是页面事件,而页面事件有不受行为监控开关影响的来源(startPage / setPage / leavePage),因此它单独可用。
验证方式:连点同一按钮5次以上触发 rage_click,或在10秒内连续返回3次触发 back_forward,随后观察 Logcat 中的挣扎日志。

会话回放

Session Replay 默认关闭,需在初始化时显式开启。Kotlin 侧可直接用命名参数(ReplayConfig(enabled = true));Java 侧需传满 9 个参数,且 quality / screenshotStrategy 两个枚举类型位于内部包、类名被混淆,跨版本可能变化,不便直接依赖时可在 Kotlin 文件中构造后再传入:
Kotlin
Java
val config = TDEMConfiguration.Builder(this)
.id("your-project-key")
.url("https://dem.rumt-zh.com")
.replayPluginConfig(ReplayConfig(enabled = true))
.build()
TDEM.init(config)
import android.app.Application;
import com.tencent.tdem.TDEM;
import com.tencent.tdem.TDEMConfiguration;
import com.tencent.tdem.replay.ReplayConfig;
import com.tencent.tdem.internal.z7; // quality 枚举(当前版本位于内部包、类名被混淆)
import com.tencent.tdem.internal.f9; // screenshotStrategy 枚举(同上)
import java.util.Collections;

// Java 没有 Kotlin 默认参数,必须按顺序传满 9 个参数
TDEMConfiguration config = new TDEMConfiguration.Builder(this)
.id("your-project-key")
.url("https://dem.rumt-zh.com")
.replayPluginConfig(new ReplayConfig(
true, // enabled
0.1, // sessionSampleRate,默认 0.1
1.0, // onErrorSampleRate,默认 1.0
z7.d, // quality = LOW(默认值)
false, // maskAllText,默认 false
false, // maskAllImages,默认 false
f9.a, // screenshotStrategy = PIXEL_COPY(默认值)
ReplayConfig.k, // maskViewClasses,默认遮罩集
Collections.<String>emptySet())) // unmaskViewClasses
.build();
TDEM.init(config);
采样是双通道:sessionSampleRate(默认0.1)决定常规会话是否被录制;未命中时若 onErrorSampleRate(默认1.0)大于0,SDK 会走滚动缓冲策略,在发生错误时冲刷并上报。
控制台下发的远程配置 replay_sample_rate(0 - 100)会覆盖本地 sessionSampleRate。因此“开启了回放却没看到数据”不一定是接入失败,需要依次确认:本地 enabled 已开启、控制台远程配置已启用回放、且会话被采样命中。
验证方式:开启后操作 App 若干秒,观察 Logcat 中的会话回放日志。

设备信息

设备信息采集默认开启,SDK 会在每条事件的上下文中附带下列设备字段:os / device_type / device_model / app_version / sdk_version / network_type / screen_resolution / language / carrier / storage_free_mb / memory_free_mb
验证方式:在控制台打开任一事件的详情,确认事件上下文中的上述字段已填充。

步骤3:主动上报(可选)

除自动采集外,SDK 提供下列主动上报接口,均通过 TDEM.getInstance() 获取实例后调用。
Kotlin
Java
val tdem = TDEM.getInstance()

// 自定义事件
tdem?.track(
"order_submit",
tags = mapOf("channel" to "app"),
properties = mapOf("amount" to 99),
)

// 自定义测速:可直接传耗时,或 start / end 成对使用
tdem?.measure("api_latency", 320, tags = mapOf("api" to "/user/info"))
tdem?.startMeasure("render")
val duration = tdem?.endMeasure("render") // 返回耗时(ms);未调用 startMeasure 时返回 -1

// 日志与异常:用于上报业务已捕获、未导致崩溃的异常
tdem?.captureMessage("用户完成注册", level = "info")
try {
// 业务代码
} catch (e: Exception) {
tdem?.captureException(e, tags = mapOf("module" to "payment"))
}
import com.tencent.tdem.TDEM;
import java.util.HashMap;
import java.util.Map;

TDEM tdem = TDEM.getInstance();

// 自定义事件
Map<String, String> tags = new HashMap<>();
tags.put("channel", "app");
Map<String, Object> properties = new HashMap<>();
properties.put("amount", 99);
tdem.track("order_submit", tags, properties);

// 自定义测速:可直接传耗时,或 start / end 成对使用
Map<String, String> measureTags = new HashMap<>();
measureTags.put("api", "/user/info");
tdem.measure("api_latency", 320, measureTags, new HashMap<String, Object>());
tdem.startMeasure("render");
long duration = tdem.endMeasure("render"); // 返回耗时(ms);未调用 startMeasure 时返回 -1

// 日志与异常:用于上报业务已捕获、未导致崩溃的异常
tdem.captureMessage("用户完成注册", "info", null);
try {
// 业务代码
} catch (Exception e) {
Map<String, Object> crashTags = new HashMap<>();
crashTags.put("module", "payment");
tdem.captureException(e, crashTags);
}
用户标识、设备标识与全局标签:
Kotlin
Java
tdem?.setUser("user-456") // 设置用户标识,用于用户维度聚合
tdem?.clearUser() // 清除用户标识
tdem?.setDeviceId("device-abc") // 设置设备标识,由宿主提供
tdem?.clearDeviceId() // 清除设备标识,回落 not_set
tdem?.setTags(mapOf("role" to "admin", "team" to "dev")) // 设置 / 追加标签
tdem?.removeTags(listOf("team")) // 移除指定标签
tdem?.clearTags() // 清空所有标签
import com.tencent.tdem.TDEM;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

TDEM tdem = TDEM.getInstance();

tdem.setUser("user-456"); // 设置用户标识,用于用户维度聚合
tdem.clearUser(); // 清除用户标识
tdem.setDeviceId("device-abc"); // 设置设备标识,由宿主提供
tdem.clearDeviceId(); // 清除设备标识,回落 not_set

Map<String, Object> tags = new HashMap<>();
tags.put("role", "admin");
tags.put("team", "dev");
tdem.setTags(tags); // 设置 / 追加标签
tdem.removeTags(Arrays.asList("team")); // 移除指定标签
tdem.clearTags(); // 清空所有标签
验证方式:调用后在控制台对应的自定义事件、日志或用户页确认数据已入库。完整签名与参数说明见 API 说明。

步骤4:检查数据上报

在 Logcat 中观察 SDK 日志。全模块日志统一使用 TDEM-Android 作为 tag,模块名只出现在日志正文中:
adb logcat -s TDEM-Android:D

# 只看某个模块(如网络)的日志
adb logcat -s TDEM-Android:D | grep HttpPlugin