You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:42:40