屏幕时间
本指南介绍如何集成屏幕时间:报告玩家正在使用您的产品、读取判定结果、处理七个屏幕时间 Webhook 事件,以及让玩家可以向父母申请更多时间。
关于屏幕时间是什么、以及 k-ID 与您的产品之间的职责边界在哪里,请先阅读屏幕时间概念。
开始之前
- 确认您的组织已可使用屏幕时间。 如需为您的组织启用此功能,请联系 k-ID。
- 为您的产品启用该功能。 屏幕时间默认关闭,需要在 Compliance Studio 中按以下顺序打开两个开关:先打开 Screentime,再打开 Screentime Controls,然后发布更改。Controls 依赖于 Screentime,在 Screentime 打开之前无法操作。在某个端点所需的开关打开之前,该端点会以
FEATURE_DISABLED失败,而两组端点所需的开关并不相同。请参阅功能开关。 - 配置您的 Webhook 端点。 全部七个屏幕时间信号都以 Webhook 形式到达。请在产品的 Developer Settings 中设置 URL 和密钥,并按照 Webhooks 概述的说明验证签名。
- 订阅您所处理的事件。 端点只会接收它已订阅的事件类型,其余的会被 k-ID 丢弃,既不尝试投递也不报错。请在 Compliance Studio 中打开产品的 Developer Settings 页面,为您的端点选择
Screentime.BreakReminder、Screentime.LimitWarning、Screentime.LimitReached、Screentime.ScheduleChanged、Screentime.QuietHoursWarning、Screentime.QuietHoursReached和Screentime.OverrideResult。这些项目只有在您的组织启用屏幕时间之后才会列出。 - 需要有一个拥有
kuid的玩家会话。 每个屏幕时间调用都面向一个 k-IDsessionId,但其背后的安排是保存在玩家的kuid上,而不是保存在会话上。没有kuid的会话永远不可能拥有安排,因此无论父母如何配置,GET /screentime/get-state对它都会返回{ "enabled": false }。玩家在受信任的成年人完成同意之后才会获得kuid,所以请先走完那个流程,并在后续会话中重复使用已保存的kuid。请参阅会话与权限和挑战。
父母未配置安排的玩家不是错误情形。GET /screentime/get-state 会返回 { "enabled": false },不会有任何判定进行拦截,也不会发出事件。请按这种情况来构建:您的大多数玩家都处于这种状态。
报告使用时间
报告使用时间有两种方式,而这个选择并非只是形式上的差别。它决定了您的产品是否能收到实时信号。
进行中的会话:start 与 end | 事后报告:push | |
|---|---|---|
| 端点 | /screentime/start、/screentime/end | /screentime/push |
| 计入每日总量 | 是 | 是 |
Screentime.BreakReminder | 会发出 | 不会发出 |
Screentime.LimitWarning | 会发出 | 不会发出 |
Screentime.LimitReached | 会发出 | 不会发出 |
| 可报告的范围 | 实时,距事件发生 5 分钟内 | 过去 7 天内的任意时点 |
| 适用场景 | 您的产品知道玩家何时开始和停止 | 您的产品只能在会话结束后才知道使用时间 |
start 与 end这是屏幕时间中无法仅凭端点列表推断出来的关键点。通过 /screentime/push 报告的使用时间会计入每日总量,也会出现在父母的 Family Connect 图表中,但它不会发出休息提醒、限制警告或限制到达事件。
原因在于时机。k-ID 在您调用 /screentime/start 时安排提醒和警告,因此它们会在玩家仍处于会话中时发出。一个 90 分钟的会话若在事后报告,60 分钟处的休息提醒只能迟 30 分钟发出,那已经不是休息提醒了。如果您的产品负有休息提醒义务,/screentime/push 无法满足它。
安静时段事件和 Screentime.ScheduleChanged 由父母的日历驱动,而不是由您的会话上报方式驱动,因此两种方式下都会发出。
会话状态机
每个 sessionId 同时只能有一个进行中的屏幕时间会话,以下规则由此而来:
id由您生成。 调用/screentime/start时发送一个新的 UUID,调用/screentime/end时发送相同的id。如果end的id与进行中的会话不匹配,将返回NOT_FOUND。- 重试是安全的。 用相同的
id重复调用start会返回accepted且不会重复计入。对已结束的会话重复调用end同样返回accepted。 - 使用不同
id的第二次start会返回status: "replaced"。 k-ID 会结束上一个会话、计入其时长,并把新会话设为进行中。当应用被强制关闭或设备重启导致end从未送达时,这就是您的恢复路径:直接再次调用start并检查是否为replaced。同一位玩家在第二台设备上开始游玩时也会发生同样的事,而那种情况会产生父母能够看到的后果:第一台设备停止统计,也不再收到提醒。请参阅使用时间如何统计。 - k-ID 会在 4 小时后自动结束停滞的会话。 截至该时点的时长会被计入,待发的提醒会被取消。这限制了完全停止发送
end的产品所造成的影响。它是兜底机制,而不是调用end的替代方案。 - 时间戳必须是实时的。
/screentime/start会拒绝早于 5 分钟的timestamp以及任何未来的时间。/screentime/end要求时间戳不早于对应 start 的时间戳,并容忍约一分钟的向前时钟偏差。
start 和 end 的响应中都会返回计数器,因此如果只是想在会话开始或结束时向玩家显示剩余时间,您不需要额外调用 GET /screentime/get-state。
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"id": "3f8c1d24-6b5e-4a07-9c2f-8d1e7a4b6c90",
"status": "accepted",
"screentime": {
"counter": {
"timeUsedTodayMinutes": 45,
"timeLimitTodayMinutes": 120,
"timeRemainingTodayMinutes": 75,
"dayResetsAt": "2026-06-25T00:00:00Z"
}
}
}
事后报告
/screentime/push 一次调用最多接收 100 个已完成的片段。每个片段都带有用于幂等的 id、这段使用开始时的 timestamp 以及 durationSeconds。
片段是独立处理的,因此请读取每个事件的状态,而不要假设整批都已成功:
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"accepted": 1,
"rejected": 1,
"events": [
{ "id": "9d4e2f81-7a3b-4c56-8e90-1f2a3b4c5d6e", "status": "accepted" },
{ "id": "c1b2a3d4-5e6f-4708-9a1b-2c3d4e5f6071", "status": "rejected", "reason": "invalid_input" }
]
}
reason 为 invalid_input 表示该片段违反了校验规则,原样重试仍会失败:片段必须已经结束,其 timestamp 必须在过去 7 天内,且 durationSeconds 必须在 1 秒到 24 小时之间。reason 为 internal_error 属于临时性问题,可以安全重试。重新提交已被接受的 id 会再次记为 accepted 且不会重复计入。
读取状态
GET /screentime/get-state 是规则与当前判定结果的唯一可信来源。请在会话开始时调用它,并在任何改变局面的事件之后再次调用。
{
"enabled": true,
"access": {
"allowed": false,
"details": {
"reason": "quiet_hours",
"resumesAt": "2026-06-25T07:00:00+08:00"
}
},
"state": {
"timeUsedTodayMinutes": 75,
"continuousUsageMinutes": 30,
"timeLimitTodayMinutes": 120,
"timeRemainingTodayMinutes": 45,
"dayResetsAt": "2026-06-25T00:00:00Z"
},
"schedule": {
"timezone": "Asia/Shanghai",
"breakReminderIntervalMinutes": 45,
"dailyLimits": [{ "day": "mon", "limitMinutes": 120 }],
"quietHours": [
{
"name": "Bedtime",
"days": ["mon", "tue", "wed", "thu", "fri"],
"start": "21:00",
"end": "07:00"
}
]
}
}
请按以下顺序读取:
enabled。为false时响应中没有其他内容,原因有三种:父母没有配置屏幕时间;父母配置了但关闭了限制;或者会话没有kuid,因此不存在可以归属安排的玩家。在测试中您最可能遇到的是第三种。这三种情况下都不要施加任何限制。access.allowed。这就是判定结果,其中已经综合了每日限制和所有安静时段。请不要自行从schedule重新计算。access.details。仅在allowed为false时出现。reason为quiet_hours或limit_reached,而resumesAt是限制解除的时刻,这正是值得告知玩家的信息。当两种限制同时适用时,响应会返回结束更晚的那一个。state。供您界面使用的当天数值。timeUsedTodayMinutes包含进行中会话的时长,而continuousUsageMinutes仅是该会话自身的长度。当天所属星期未设置限制时,timeLimitTodayMinutes和timeRemainingTodayMinutes不会出现。schedule。用于展示的父母规则。时刻为schedule.timezone时区下的HH:MM。
处理事件
只有当产品的 Webhook 订阅了相应的事件类型时,这些事件才会到达。在排查一个从未执行的处理逻辑之前,请先核对开始之前。
| 事件 | 含义 | 应做的处理 |
|---|---|---|
Screentime.BreakReminder | 玩家在一个会话中连续使用达到了设定的间隔 | 提示玩家休息 |
Screentime.LimitWarning | 当天还剩 15 分钟或 5 分钟 | 告知玩家,以便其到达存档点 |
Screentime.LimitReached | 当天的限制已生效 | 施加您的限制,并提供向父母申请的入口 |
Screentime.QuietHoursWarning | 时段将在 15 分钟后开始 | 告知玩家是哪个时段以及何时开始 |
Screentime.QuietHoursReached | 时段已经开始 | 在 endsAt 之前施加您的限制 |
Screentime.ScheduleChanged | 父母更改了规则 | 重新获取 get-state 并替换您保存的副本 |
Screentime.OverrideResult | 父母答复了豁免请求 | 应用负载中的 state |
投递方面有三项特性值得在设计时考虑:
- 事件面向
sessionId投递。 请按data.sessionId分发,这七个事件都包含该字段。 - 投递保证为至少一次。 同一事件可能到达两次。请按 Webhooks 概述的说明让处理逻辑保持幂等。
- 安静时段事件不需要有进行中的会话。 对于在您产品中拥有有效 k-ID 会话的玩家,无论其当时是否在游玩,事件都会按父母的日历发出。请不要假设存在可以介入的界面。
向父母申请更多时间
当 access.allowed 为 false 时,请为玩家提供申请的途径,而不只是一堵墙。
- 携带
sessionId调用/screentime/request-override。响应中包含id和expiresAt。请保存该id。 - k-ID 通知父母,父母在 Family Connect 中批准或拒绝。
- 结果会作为
Screentime.OverrideResult到达,通过data.id关联,其status为granted、denied或expired。 - 读取
data.state,它是决定生效后计算出的完整get-state结构。无需再次请求。
该端点对每位玩家是幂等的:在请求仍处于待处理状态时再次调用,会原样返回该待处理请求而不会创建第二个,并且两种情况下响应结构完全相同,因此您无需为此单独写一条代码路径。无人答复的请求会在 24 小时后到期,并以 status: "expired" 发出 Webhook。玩家随后可以再次申请。
获批的豁免会提高当天的限制。它不会停用安排,也不会延续到第二天。
时区与日期边界
每份安排都带有时区,屏幕时间的每项规则都按该时区计算。请以带偏移量的 RFC 3339 发送时间戳,把转换交给 k-ID。
- 每日限制在安排所用时区的当地午夜重置。状态响应中的
dayResetsAt就是该时刻。 - 跨过当地午夜的会话会在
/screentime/end时被拆分,因此每一天的总量都反映当天实际发生的使用时间。跨过午夜的 push 片段同样如此。
功能开关
屏幕时间分为两个层级,产品可以只启用第一个层级:
| 开关 | 启用的能力 | 所控制的端点 |
|---|---|---|
| Screentime | 被动的使用时间上报,无需父母进行任何设置 | /screentime/push |
| Screentime Controls | 父母配置的限制、安排、安静时段、休息提醒和豁免 | /screentime/start、/screentime/end、/screentime/get-state、/screentime/request-override |
仅启用 Screentime 时,产品只能上报使用时间:调用 /screentime/push 提交已完成的片段,它们会计入孩子的每日总量。这与活动接入是同一种形态的集成,父母无需进行任何设置。
Screentime Controls 是第二个层级:父母配置的安排以及据此运行的一切。它控制会话状态机(/screentime/start、/screentime/end)、当前判定(/screentime/get-state)和请求父母的流程(/screentime/request-override),实时事件由该会话状态机产生。
所属层级未启用的端点会返回 FEATURE_DISABLED。仅启用 Screentime 的产品可以 push 使用时间,但无法开始会话或读取状态;两者都未启用的产品无法调用其中任何一个。
上线前检查清单
- 在每个会话开始时调用
GET /screentime/get-state,并在enabled: false时不对玩家施加任何限制。 - 您的产品读取
access.allowed,而不是从schedule重新计算判定结果。 - 每次
/screentime/start都与携带相同id的/screentime/end配对,并且把status为replaced的情况作为正常情况处理而非错误。 - 每一对预警中的两个事件都被处理,而不只是第二个。
- 产品的 Webhook 已订阅您所处理的全部七个
Screentime.*事件类型。Webhook 未选中的事件类型不会被投递。 - Webhook 处理逻辑是幂等的,并且验证签名。
- 收到
Screentime.ScheduleChanged后会重新获取get-state。 - 对达到限制或进入安静时段的玩家提供
/screentime/request-override,并处理包括expired在内的全部三种豁免状态。 - 您的产品所施加的限制会使用
resumesAt或endsAt告知玩家限制何时解除。