Android:UsageStatsManager没有返回正确的每日结果

时间:2016-03-26 17:30:37

标签: java android usage-statistics activity-manager usagestatsmanager

我正在尝试从UsageStats查询UsageStatsManager,目的是返回每天使用的所有应用包以及持续多长时间。

守则:

public static List<UsageStats> getUsageStatsList(Context context){
    UsageStatsManager usm = getUsageStatsManager(context);
    Calendar calendar = Calendar.getInstance();
    long endTime = calendar.getTimeInMillis();
    calendar.add(Calendar.DAY_OF_YEAR, -1);
    long startTime = calendar.getTimeInMillis();

    List<UsageStats> usageStatsList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,startTime, endTime);
    return usageStatsList;
}

我有一个警报,每天在午夜之前触发并查询使用状态,然后存储返回的数据。起初一切似乎工作正常,我得到包结果和他们的活动时间,但是我添加了一个功能,每小时检查一次结果,这是我发现一个奇怪的发现。

来自UsageStatsManager的结果似乎是在不同的时间重置,而不是在午夜,这是我在考虑使用INTERVAL_DAILY作为搜索参数时的预期。

根据我保存的数据包“时间”结果似乎正在重置(粗略时间):

  • 上午03点
  • 午间
  • 下午3时
  • 午夜

我意识到包装时间重置之间存在相关性,但这是否意味着发生?

我已经看过以下帖子了,这是我收集了大量信息的地方: How to use UsageStatsManager?

因此: Android UsageStatsManager producing wrong output? 在评论中提到从queryUsageStats返回的数据不可信并且返回随机结果。

我是否遗漏了一些简单或UsageStatsManager无法正常运作的内容?

4 个答案:

答案 0 :(得分:6)

我也注意到了API 21中的这种行为,在API 21中,UsageStats数据的维护时间不长。它在API 22中运行良好。如果您签入android /data/system/usagestats,您会在API 21中找到有限的条目,所以在API 21中使用它是不可靠的。

对于API 21+, 根据{{​​1}} API查询usagestats时,您将获得全天INTERVAL_DAILY。 如果你想在一天中的几小时内查询,你应该使用UsageStatsManager并按照你自己的逻辑迭代它们。

我尝试了以下方式...

这是捕获每个应用程序数据的模态类:

queryEvents

private class AppUsageInfo { Drawable appIcon; String appName, packageName; long timeInForeground; int launchCount; AppUsageInfo(String pName) { this.packageName=pName; } }

这里是方法,它很容易,顺其自然:

List<AppUsageInfo> smallInfoList; //global var

答案 1 :(得分:3)

我想我发现那里发生了什么。首先我写了下面的代码,

 public String getDaily(String appPackageName, long startTime, long endTime)
 {
    List<UsageStats> usageStatsList = usageStatsManager.queryUsageStats(
                     UsageStatsManager.INTERVAL_DAILY, startTime,endTime);

    String x="";
    for(int i=0; i<usageStatsList.size(); i++) {

        UsageStats stat = usageStatsList.get(i);
        if(stat.getPackageName().equals(appPackageName))
            x=x+i+"-"+stat.getPackageName()+"-"
            +converLongToTimeChar(stat.getTotalTimeInForeground())+"\n";
    }

    return x;
}
public String converLongToTimeChar(long usedTime) {
    String hour="", min="", sec="";

    int h=(int)(usedTime/1000/60/60);
    if (h!=0)
        hour = h+"h ";

    int m=(int)((usedTime/1000/60) % 60);
    if (m!=0)
        min = m+"m ";

    int s=(int)((usedTime/1000) % 60);
    if (s==0 && (h!=0 || m!=0))
        sec="";
    else
        sec = s+"s";

    return hour+min+sec;
}

(今天的日期是03.08.2017 00:25:14) 当我发送时(“包名”,02.08.2017 00.00.00,03.08.2017 00.00.00);方法,(我用日历发送这个日期,你可以在谷歌搜索,如何设置这样的日期) 我得到了这个输入;

  46-'apppackagename'-9m 31s
  154-'apppackagename'-22m 38s

然后我发送了(“包名”,03.08.2017 00.00.00,04.08.2017 00.00.00);方法, 我得到了这个输入;

  25-'apppackagename'-22m 38s

我使用了方法发送的app大约1分钟。我发送的方法输出再次是:

02:08:2017-03.08.2017

  46-'apppackagename'-9m 31s
  154-'apppackagename'-23m 32s

03:08:2017-04.08.2017

  25-'apppackagename'-23m 32s

如你所见,他们两者都增加了。我看到我等到凌晨03.00, 我使用app大约5分钟,我得到了这些输出。

02:08:2017-03.08.2017

  46-'apppackagename'-9m 31s
  154-'apppackagename'-23m 32s

03:08:2017-04.08.2017

  25-'apppackagename'-23m 32s
  50-'apppackagename'-4m 48s

总而言之,你应该在白天及其最后一个前台运行时间之前控制。如果它与当天的第一个前景时间相同。你应该消除那个时间并返回其他人的总和。 (即使我不知道那个奇怪的系统。)新的一天的计数器在03:00之后开始。

我希望它会对你有所帮助。

答案 2 :(得分:3)

我同意您提到的关于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>)

有几点意见:

  • Event所拥有的时间戳以UTC为单位,这就是我将我的开始/结束查询时间从我的默认时区转换为UTC的原因,以及为什么我将开始时间转换回每个事件。这个让我有一段时间......
  • 这考虑了应用程序在一天开始之前处于前台的边缘情况(即用户在午夜之前打开应用程序)或者在说出结束后进入后台(即用户仍然有当天晚上11:59 PM的应用程序)。免责声明:我还没有真正测试过这些边缘情况。
  • 如果用户在午夜过后使用应用,我选择使用晚上11:59:59作为结束时间。显然,您可以将其更改为午夜1毫秒,或者只是午夜,具体取决于您选择如何计算。只需删除- 1000并替换为您想要的任何内容。
  • 在我的用例中,我需要总前景时间+开始时间,这就是我收集该信息的原因。但是,您可以调整Stat类和代码来捕获您需要的任何信息。例如,如果需要,您可以跟踪结束时间或应用程序在一天内启动的次数。
  • 我在这里使用Java 8时间库,因为它更容易处理日期。要在Android中使用此功能,我使用ThreeTenABP库。

我希望这有帮助!

答案 3 :(得分:1)

我也遇到了同样的问题,并且为此也向Google开了一个问题。如果https://issuetracker.google.com/issues/118564471符合您的描述,请看看。