我同意你在评论中提到的不是一个值得信赖的来源所说的话。我已经玩了一段时间,它根据一天中的时间返回不一致的结果。我发现使用s并手动计算必要的信息会更加可信(至少对于每日统计数据而言),因为它们是时间点,并且没有任何奇怪的计算错误,这些错误会根据一天中的时间为相同的输入产生不同的输出。queryUsageStats
UsageStatsManager
UsageEvent
我用@Vishal提出的解决方案想出了自己的解决方案:
/**
* Returns the stats for the [date] (defaults to today)
*/
fun getDailyStats(date: LocalDate = LocalDate.now()): List<Stat> {
// The timezones we'll need
val utc = ZoneId.of("UTC")
val defaultZone = ZoneId.systemDefault()
// Set the starting and ending times to be midnight in UTC time
val startDate = date.atStartOfDay(defaultZone).withZoneSameInstant(utc)
val start = startDate.toInstant().toEpochMilli()
val end = startDate.plusDays(1).toInstant().toEpochMilli()
// This will keep a map of all of the events per package name
val sortedEvents = mutableMapOf<String, MutableList<UsageEvents.Event>>()
// Query the list of events that has happened within that time frame
val systemEvents = usageManager.queryEvents(start, end)
while (systemEvents.hasNextEvent()) {
val event = UsageEvents.Event()
systemEvents.getNextEvent(event)
// Get the list of events for the package name, create one if it doesn't exist
val packageEvents = sortedEvents[event.packageName] ?: mutableListOf()
packageEvents.add(event)
sortedEvents[event.packageName] = packageEvents
}
// This will keep a list of our final stats
val stats = mutableListOf<Stat>()
// Go through the events by package name
sortedEvents.forEach { packageName, events ->
// Keep track of the current start and end times
var startTime = 0L
var endTime = 0L
// Keep track of the total usage time for this app
var totalTime = 0L
// Keep track of the start times for this app
val startTimes = mutableListOf<ZonedDateTime>()
events.forEach {
if (it.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
// App was moved to the foreground: set the start time
startTime = it.timeStamp
// Add the start time within this timezone to the list
startTimes.add(Instant.ofEpochMilli(startTime).atZone(utc)
.withZoneSameInstant(defaultZone))
} else if (it.eventType == UsageEvents.Event.MOVE_TO_BACKGROUND) {
// App was moved to background: set the end time
endTime = it.timeStamp
}
// If there's an end time with no start time, this might mean that
// The app was started on the previous day, so take midnight
// As the start time
if (startTime == 0L && endTime != 0L) {
startTime = start
}
// If both start and end are defined, we have a session
if (startTime != 0L && endTime != 0L) {
// Add the session time to the total time
totalTime += endTime - startTime
// Reset the start/end times to 0
startTime = 0L
endTime = 0L
}
}
// If there is a start time without an end time, this might mean that
// the app was used past midnight, so take (midnight - 1 second)
// as the end time
if (startTime != 0L && endTime == 0L) {
totalTime += end - 1000 - startTime
}
stats.add(Stat(packageName, totalTime, startTimes))
}
return stats
}
// Helper class to keep track of all of the stats
class Stat(val packageName: String, val totalTime: Long, val startTimes: List<ZonedDateTime>)
几个观察结果:
- s 具有的时间戳采用 UTC 格式,这就是我将开始/结束查询时间从默认时区转换为 UTC 的原因,以及为什么我将每个事件的开始时间转换回的原因。这个让我有一段时间了...
Event
- 这考虑了以下边缘情况:应用在一天开始之前处于前台(即用户在午夜之前打开了应用)或在说完之后转到后台(即用户在当天晚上 11:59 之后,用户仍然在前台有一个应用)。免责声明:我还没有真正测试这些边缘情况。
- 如果用户在午夜后使用应用,我选择使用晚上 11:59:59 作为结束时间。显然,您可以将其更改为午夜的1毫秒,或者只是午夜,具体取决于您选择如何计算。只需删除并替换为您想要的任何内容即可。
- 1000
- 在我的用例中,我需要总前台时间+开始时间,这就是我收集该信息的原因。但是,您可以调整类和代码以捕获所需的任何信息。例如,您可以跟踪结束时间,或者如果需要,可以跟踪一天内启动应用的次数。
Stat
- 我在这里使用Java 8时间库,因为它更容易处理日期。为了在Android中使用它,我使用了ThreeTenABP库。
我希望这有帮助!