【问题标题】:Android: UsageStatsManager not returning correct daily resultsAndroid:UsageStatsManager 未返回正确的每日结果
【发布时间】:2016-03-26 17:30:37
【问题描述】:

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

守则:

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 作为搜索参数,这是我所期望的。

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

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

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

我已经看过以下帖子,我从中获得了很多信息: How to use UsageStatsManager?

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

是我遗漏了一些简单的东西还是UsageStatsManager 无法正常运行?

【问题讨论】:

  • 嗨!您找到解决方案了吗?我也有同样的问题。

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


【解决方案1】:

我也注意到 API 21 中的这种行为,UsageStats 数据在 API 21 中维护的时间不够长。它从 API 22 开始工作正常,如果您签入android /data/system/usagestats,您会发现 API 21 中的条目有限,因此它的在 API 21 中使用它并不可靠。

对于 API 21+, 在根据UsageStatsManager API 查询INTERVAL_DAILY 时,您将得到一整天的usagestats。 如果您想在一天中的几个小时内查询,您应该使用queryEvents 并按照您自己的逻辑对其进行迭代。

我尝试了以下方式...

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

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

        AppUsageInfo(String pName) {
            this.packageName=pName;
        }
}

List&lt;AppUsageInfo&gt; 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());

}

【讨论】:

  • 我们是否需要为开始和结束时间发送 UTC timeInMillis?
【解决方案2】:

我同意您在评论中提到的关于 queryUsageStats 不是可信赖来源的说法。我一直在玩UsageStatsManager 一段时间,它根据一天中的时间返回不一致的结果。我发现使用UsageEvents 并手动计算必要的信息更值得信赖(至少对于日常统计数据),因为它们是时间点并且没有任何奇怪的计算错误会产生不同的输出相同的输入取决于一天中的时间。

我使用@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>)

几个观察:

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

我希望这会有所帮助!

【讨论】:

  • 如果应用程序崩溃了,MOVE_TO_FOREGROUND 事件之后会出现 ACTIVITY_STOPPED 事件。这也值得测试,否则应用程序将被视为运行很长时间:)
【解决方案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) 当我发送 ("package name",02.08.2017 00.00.00, 03.08.2017 00.00.00); to 方法,(我用日历发送了这个日期,你可以在谷歌中搜索,如何设置这样的日期) 我得到了这个输入;

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

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

  25-'apppackagename'-22m 38s

我使用了大约 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 点之后, 我用了大约 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 开始。

希望对你有帮助。

【讨论】:

  • 制作这个 api 的人纯粹是邪恶的。谢谢你分享这个。
  • @IlToro 我很高兴,你认为它是有益的。但是我强烈建议使用 UsageStats.Events 来计算使用时间。它提供了许多使用统计的详细信息,如 [time] [packagename] [foreground |背景]。
  • 你是对的。乍一看从凌晨 3 点到凌晨 3 点的 UsageStats 似乎还不错。我什至写了一个循环来在你可能得到的克隆中找到最新的包(如果你错误地查询了好几天)。经过几天的测试,但又开始变得混乱。所以我认为这个 API 根本不可靠。
【解决方案4】:

我已经解决了同样的问题,并为此向 Google 提出了问题。如果这与您所描述的相符,请查看https://issuetracker.google.com/issues/118564471

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-09
    • 2011-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多