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

Rya*_*Dev 8 java android usage-statistics activity-manager usagestatsmanager

我试图查询UsageStatsUsageStatsManager,与返回的日常使用所有应用程序包的出发点和多长时间.

代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

结果UsageStatsManager似乎是在不同的时间重置,而不是在午夜,这是我所期望的,考虑到我INTERVAL_DAILY用作搜索参数.

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

  • 凌晨3点
  • 正午
  • 下午3点
  • 午夜

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

我已经看过以下线程了,我从中得到了很多信息: 如何使用UsageStatsManager?

因此: Android UsageStatsManager产生错误的输出? 在评论中提到,返回的数据queryUsageStats不可信,并且返回随机结果.

我错过了一些简单的东西还是UsageStatsManager运作不正常?

小智 8

我也注意到了API 21中的这种行为,在API 21中,UsageStats数据的维护时间不长.它可以在API 22中正常工作.如果您签入android /data/system/usagestats,您将在API 21中找到有限的条目,因此在API中使用它是不可靠的21.

对于API 21+,您将根据API usagestats查询一整天.如果您想在一天中的几小时内查询,您应该使用并按照您自己的逻辑迭代它们.INTERVAL_DAILYUsageStatsManagerqueryEvents

我试着用以下方式......

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

private class AppUsageInfo {
        Drawable appIcon;
        String appName, packageName;
        long timeInForeground;
        int launchCount;

        AppUsageInfo(String pName) {
            this.packageName=pName;
        }
}
Run Code Online (Sandbox Code Playgroud)

List<AppUsageInfo> smallInfoList; //global var

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

void getUsageStatistics() {

UsageEvents.Event currentEvent;
List<UsageEvents.Event> allEvents = new ArrayList<>();
HashMap<String, AppUsageInfo> map = new HashMap <String, AppUsageInfo> ();

long currTime = System.currentTimeMillis();
long startTime currTime - 1000*3600*3; //querying past three hours

UsageStatsManager mUsageStatsManager =  (UsageStatsManager)
                    mContext.getSystemService(Context.USAGE_STATS_SERVICE);

        assert mUsageStatsManager != null;
UsageEvents usageEvents = mUsageStatsManager.queryEvents(usageQueryTodayBeginTime, currTime);

//capturing all events in a array to compare with next element

         while (usageEvents.hasNextEvent()) {
            currentEvent = new UsageEvents.Event();
            usageEvents.getNextEvent(currentEvent);
            if (currentEvent.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND ||
                    currentEvent.getEventType() == UsageEvents.Event.MOVE_TO_BACKGROUND) {
                allEvents.add(currentEvent);
                String key = currentEvent.getPackageName();
// taking it into a collection to access by package name
                if (map.get(key)==null)
                    map.put(key,new AppUsageInfo(key));
            }
        }

//iterating through the arraylist 
         for (int i=0;i<allEvents.size()-1;i++){
            UsageEvents.Event E0=allEvents.get(i);
            UsageEvents.Event E1=allEvents.get(i+1);

//for launchCount of apps in time range
             if (!E0.getPackageName().equals(E1.getPackageName()) && E1.getEventType()==1){
// if true, E1 (launch event of an app) app launched
                 map.get(E1.getPackageName()).launchCount++;
             }

//for UsageTime of apps in time range
            if (E0.getEventType()==1 && E1.getEventType()==2
                    && E0.getClassName().equals(E1.getClassName())){
                long diff = E1.getTimeStamp()-E0.getTimeStamp();
                phoneUsageToday+=diff; //gloabl Long var for total usagetime in the timerange
                map.get(E0.getPackageName()).timeInForeground+= diff;
            }
        }
//transferred final data into modal class object
        smallInfoList = new ArrayList<>(map.values());

}
Run Code Online (Sandbox Code Playgroud)


jgu*_*net 7

I agree with what is said in that comment you mentioned about queryUsageStats not being a trusted source. I've been with playing with the UsageStatsManager for a little while and it returns inconsistent results based on the time of day. I have found that using the UsageEvents and manually calculating the necessary info to be much more trustworthy (at least for daily stats), as they are points in time and don't have any weird calculating errors that would produce different outputs for the same input depending on the time of day.

I used @Vishal's proposed solution to come up with my own:

/**
 * 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>)
Run Code Online (Sandbox Code Playgroud)

A couple of observations:

  • The timestamps that the Events have are in UTC, which is why I convert my start/end query times to UTC from my default time zone, and why I convert the start time back on each event. This one got me for a while...
  • This takes into account the edge cases where an app was in the foreground before the beginning of the day (i.e. the user opened an app before midnight) or went to the background after the end of the say (i.e. the user still had an app in the foreground past 11:59 PM on that day). Disclaimer: I haven't actually tested these edge cases yet.
  • In the case that the user uses an app past midnight, I opted with using 11:59:59 PM as the end time. You can obviously change this to be 1 millisecond off of midnight, or simply midnight depending on how you choose to calculate this. Simply remove the - 1000 and replace with whatever you want.
  • In my use case I needed total foreground time + start times, which is why I collect that information. However, you can tweak the Stat class and the code to capture whatever info you need. You can keep track of end times, or number of times an app was launched in a day if needed for example.
  • I am using the Java 8 time library here because it was easier to deal with dates. To use this in Android, I use the ThreeTenABP library.

I hope this helps!