认识屏幕使用时间 API
Meet the Screen Time API
2021年6月9日
一句话判断
Screen Time API 终于开放给第三方开发者 —— 家庭教育类 app 可以读取孩子的使用时长、设置 App 限额和内容过滤,但权限管控非常严格,你的 app 必须明确声明用途且通过审核才能使用。
这场 Session 讲了什么
iOS 15 首次向开发者开放了 Screen Time API,允许授权的第三方 app 读取和管理屏幕使用时间数据。API 分为三个核心模块:
- FamilyActivitySelection(家庭活动选择):让用户选择哪些 app 和网页类别纳入管理。系统提供一个标准的 picker 界面,用户选择后返回一个 token。
- DeviceActivityMonitor(设备活动监控):监听设备使用事件,如在某个时间段内使用了特定类别的 app。可以设置阈值触发回调。
- ManagedSettings(受管设置):根据 FamilyActivitySelection 的选择,应用限制:屏蔽特定 app、限制网页内容、阻止更改系统设置等。
整个 API 围绕”家长控制”场景设计,但也适用于个人效率管理 app。权限模型基于 Family Controls entitlement,需要在 Apple Developer 网站申请。
值得深挖的点
权限模型的三层保护。你的 app 要使用 Screen Time API,需要:(1) 在 Apple Developer 后台申请 Family Controls entitlement;(2) 在 Info.plist 中声明 FamilyControls 用途描述;(3) 用户在运行时通过系统弹窗显式授权。第三步的弹窗是系统级 UI(和 ATT 类似),用户可以随时在设置中撤销。这意味着你的 app 不能偷偷读取使用数据。
DeviceActivityMonitor 的后台执行。DeviceActivityMonitor 的回调(如 intervalDidStart、intervalDidEnd、eventDidReachThreshold)在后台执行,不是在 app 进程内。系统会唤醒你的 app extension(DeviceActivityMonitorExtension)来处理这些事件。这个设计确保了即使 app 被杀掉,监控仍然有效。但 extension 的执行时间有限(通常几秒),不能做耗时操作。
代码片段
请求 Family Controls 权限:
import FamilyControls
class ScreenTimeManager: ObservableObject {
@Published var isAuthorized = false
func requestAuthorization() async {
do {
// 请求 Family Controls 权限
try await AuthorizationCenter.shared.requestAuthorization(for: .child)
isAuthorized = true
} catch {
print("Family Controls 授权失败: \(error)")
isAuthorized = false
}
}
// 在 app 启动时检查当前授权状态
func checkAuthorization() {
switch AuthorizationCenter.shared.authorizationStatus {
case .approved:
isAuthorized = true
case .denied:
isAuthorized = false
case .notDetermined:
// 还未请求过权限
break
@unknown default:
break
}
}
}
展示 App 选择器:
import SwiftUI
import FamilyControls
struct AppPickerView: View {
@State private var selection = FamilyActivitySelection()
@State private var showPicker = false
var body: some View {
VStack {
Text("选择要限制的 App 和网页类别")
.font(.headline)
Button("选择 App") {
showPicker = true
}
.familyActivityPicker(isPresented: $showPicker,
selection: $selection)
// 显示已选择的类别数量
if !selection.applicationTokens.isEmpty {
Text("已选择 \(selection.applicationTokens.count) 个 App")
}
if !selection.categoryTokens.isEmpty {
Text("已选择 \(selection.categoryTokens.count) 个类别")
}
Button("应用限制") {
applyRestrictions(selection)
}
.disabled(selection.applicationTokens.isEmpty && selection.categoryTokens.isEmpty)
}
}
}
应用使用限制:
import FamilyControls
import ManagedSettings
class RestrictionManager {
let store = ManagedSettingsStore()
func applyRestrictions(_ selection: FamilyActivitySelection) {
// 屏蔽选中的 app
store.shield.applications = selection.applicationTokens
// 屏蔽选中的网页类别
store.shield.webContent = selection.categoryTokens
// 可选:阻止 App Store 安装新 app
store.appStore.requiresPassword = true
// 可选:阻止更改系统设置
store.account.lockAccounts = true
print("限制已应用")
}
func removeRestrictions() {
// 清除所有限制
store.clearAllSettings()
print("限制已移除")
}
}
设备活动监控 Extension:
import DeviceActivity
// 在 DeviceActivityMonitorExtension 中实现回调
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
// 用户开始使用被监控的 app
print("监控周期开始: \(activity)")
}
override func intervalDidEnd(for activity: DeviceActivityName) {
super.intervalDidEnd(for: activity)
// 监控周期结束(如"学习时间"结束)
print("监控周期结束: \(activity)")
}
override func eventDidReachThreshold(
_ event: DeviceActivityEvent,
activity: DeviceActivityName
) {
super.eventDidReachThreshold(event, activity: activity)
// 使用时长达到阈值(如使用社交媒体 30 分钟)
print("达到阈值: \(event)")
// 发送本地通知提醒用户
sendUsageReminder()
}
}
最佳实践
- 权限请求要在功能上下文中发起。不要在 app 启动时立即弹出 Family Controls 授权。在用户点击”开始管理屏幕时间”按钮后再请求,通过率会高很多。
- 选择器 UI 使用系统提供的
familyActivityPicker。不要自己实现 app 列表,系统 picker 会自动展示已安装 app 的图标和名称,且保证隐私安全。 - 限制策略要有”宽限期”。不要一次性严格限制所有社交 app。逐步收紧比一刀切更容易被用户(尤其是孩子)接受。
还有什么值得关注
- Screen Time API 对 app 类别的 token 是不透明的(
ApplicationToken),你不能通过它知道具体是哪个 app,只能用于设置限制。这是隐私保护设计。 ManagedSettingsStore支持多个独立 store,适合”学习时间”、“睡眠时间”等不同场景的差异化配置。- 这个 API 在 MDM(移动设备管理)环境中也有用途,企业可以用它限制员工设备上的非工作 app。