Usagestatsmanager(queryEvents) API屏幕使用时长数据错误的修复咨询
修复Android UsageStatsManager获取应用使用时长的错误数据问题
你遇到的问题主要来自时间范围计算偏差、系统重复上报事件、未过滤屏幕休眠时段,以及事件配对逻辑不严谨这几个方面。以下是具体修复方案:
1. 修正目标时间范围
你的代码当前是取「当前时间往前推24小时」,但需求是昨日11点至今日11点,和当前时间无关。正确的时间范围计算应该固定锚定11点:
val calendar = Calendar.getInstance().apply { // 设置为今日11点作为结束时间 set(Calendar.HOUR_OF_DAY, 11) set(Calendar.MINUTE, 0) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) } val endTime = calendar.timeInMillis // 往前推24小时得到昨日11点作为开始时间 calendar.add(Calendar.DAY_OF_YEAR, -1) val startTime = calendar.timeInMillis
2. 过滤屏幕未激活时段
夜间睡眠时屏幕通常处于熄灭状态,这部分事件属于无效数据。可以通过PowerManager结合系统屏幕事件来标记有效时段:
val powerManager = applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager var isScreenOn = powerManager.isInteractive // 屏幕是否处于交互状态(亮屏) // 遍历事件时先更新并判断屏幕状态 while (usageEvents.hasNextEvent()) { val event = UsageEvents.Event() usageEvents.getNextEvent(event) // 更新屏幕状态 when (event.eventType) { UsageEvents.Event.SCREEN_ON -> isScreenOn = true UsageEvents.Event.SCREEN_OFF -> isScreenOn = false } if (!isScreenOn) continue // 屏幕熄灭时跳过应用事件计算 }
3. 严谨配对RESUMED/PAUSED事件
系统可能重复上报事件,或出现应用被强杀导致PAUSED事件丢失的情况。需要维护当前活跃应用的状态映射,确保每个RESUMED都对应正确的PAUSED:
// 保存当前活跃应用的最后RESUMED时间 val activeApps = mutableMapOf<String, Long>() // 存储每个应用的原始使用时段 val appRawIntervals = mutableMapOf<String, MutableList<Pair<Long, Long>>>() while (usageEvents.hasNextEvent()) { val event = UsageEvents.Event() usageEvents.getNextEvent(event) if (!isScreenOn) continue // 跳过熄屏时段事件 when (event.eventType) { UsageEvents.Event.ACTIVITY_RESUMED -> { val packageName = event.packageName // 如果该应用已有未配对的RESUMED,先补一个结束时间(处理应用被强杀的情况) activeApps[packageName]?.let { prevResumeTime -> appRawIntervals.getOrPut(packageName) { mutableListOf() }.add(prevResumeTime to event.timeStamp) } // 更新当前RESUMED时间 activeApps[packageName] = event.timeStamp } UsageEvents.Event.ACTIVITY_PAUSED -> { val packageName = event.packageName activeApps.remove(packageName)?.let { resumeTime -> // 只统计合理的正向时长(避免事件顺序颠倒的异常) if (event.timeStamp > resumeTime) { appRawIntervals.getOrPut(packageName) { mutableListOf() }.add(resumeTime to event.timeStamp) } } } } } // 处理事件结束后仍处于活跃状态的应用(比如当前正在使用的应用) activeApps.forEach { (packageName, resumeTime) -> if (resumeTime < endTime) { // 确保在目标时间范围内 appRawIntervals.getOrPut(packageName) { mutableListOf() }.add(resumeTime to endTime) } }
4. 合并重叠/重复的时段
最后对每个应用的时段列表进行去重合并,解决重复统计的问题:
fun mergeOverlappingIntervals(intervals: MutableList<Pair<Long, Long>>): List<Pair<Long, Long>> { if (intervals.size <= 1) return intervals // 按开始时间排序 intervals.sortBy { it.first } val merged = mutableListOf<Pair<Long, Long>>() var currentStart = intervals[0].first var currentEnd = intervals[0].second for (i in 1 until intervals.size) { val (start, end) = intervals[i] if (start <= currentEnd) { // 重叠或相邻,合并时段 currentEnd = maxOf(currentEnd, end) } else { // 无重叠,保存当前时段 merged.add(currentStart to currentEnd) currentStart = start currentEnd = end } } // 添加最后一个时段 merged.add(currentStart to currentEnd) return merged } // 生成最终的应用使用时长数据(单位:毫秒) val finalUsageData = appRawIntervals.mapValues { (_, intervals) -> mergeOverlappingIntervals(intervals).sumOf { it.second - it.first } }
额外注意事项
- 确保已申请
android.permission.PACKAGE_USAGE_STATS权限,并且用户在系统设置中授予了该权限。 - 部分定制ROM可能修改了UsageStats的事件上报逻辑,建议在不同品牌设备上测试验证。
内容的提问来源于stack exchange,提问作者Yash Khasgiwala
相关产品推荐
相关产品推荐

