如何使用UsageStatsManager?

63

背景

谷歌已废弃了 "ActivityManager" 类的 "getRecentTasks" 函数。现在它所做的只是获取当前应用程序打开的应用程序列表。

我甚至在 StackOverflow 上写了一篇文章(这里),但我注意到这是不可能的。

问题

我发表了一篇关于此问题的帖子 (这里),还有另外一个问题类似的帖子被其他人创建 (这里),请求重新考虑该功能是否可以提供,谷歌决定创建一个新类,看起来提供了类似的功能 (更像统计数据,但也可能有用),但我找不到如何使用它。

这个类被称为 "UsageStatsManager",我的猜测是函数 "queryUsageStats" 执行该任务。

此外,它似乎有一个新的权限 ("android.permission.PACKAGE_USAGE_STATS"),这是一个系统权限,但其中写到:

声明权限意味着使用API的意图,设备用户可以通过设置应用程序授予权限。

这里还有关于这个新功能的链接。

我的发现

我查看了 Android 的代码,并注意到 "Context" 有 USAGE_STATS_SERVICE,在 JavaDocs 中说到下一件事:

/**
 * Use with {@link #getSystemService} to retrieve a {@link
 * android.app.UsageStatsManager} for interacting with the status bar.
 *
 * @see #getSystemService
 * @see android.app.UsageStatsManager
 * @hide
 */
public static final String USAGE_STATS_SERVICE = "usagestats";

奇怪的是它不仅显示“状态栏”,而且包名不匹配(应该是“android.app.usage.UsageStatsManager”)。

我还添加了正确的权限:

<uses-permission
    android:name="android.permission.PACKAGE_USAGE_STATS"
    tools:ignore="ProtectedPermissions" />

这是我使用的代码:

  final UsageStatsManager usageStatsManager=(UsageStatsManager)context.getSystemService("usagestats");// Context.USAGE_STATS_SERVICE);
  final int currentYear=Calendar.getInstance().get(Calendar.YEAR);
  final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,currentYear-2,currentYear);
在模拟器中,我进入"设置"-"安全"-"允许使用的应用",并启用了我的应用程序。 然而,当运行代码时,我得到的只是一个空列表...

问题

如何使用UsageStatsManager

另外,如何以最简单的方式让用户授予权限?或者当应用程序尝试获取所需信息时,是否会自动完成?

如果用户还没有确认,尝试使用此类会发生什么?

如何使代码返回一个真实的应用程序列表?


如何让用户以最简单的方式授予权限?我在设置>安全屏幕中看到了“有使用权限的应用程序”。应该是请求此权限的应用程序会出现在那里。依我看,他们应该使用<meta-data>。 - CommonsWare
@CommonsWare 是的,我注意到了,但我作为开发人员提出这个问题。开发人员应该怎么做呢?我甚至不知道如何初始化整个过程。而且,它是一个系统权限,但却可以使用...在其他情况下是否也有类似的情况?也许处理方式会类似... - android developer
"开发者应该怎么处理它?"--我想告诉用户去访问那个屏幕。 "在另一种情况下有这样的东西吗?"--据我所知没有。 - CommonsWare
数据位于此处 /data/system/usagestats/{user_id_},其中 {user_id} 是设备上用户帐户的 ID。 - TheRealChx101
1
我发现这个repo正好做你所要求的事情。 - bhanu kaushik
显示剩余6条评论
7个回答

36

我觉得这份文档只是对日历功能的简单介绍,我不认为它实际上可以仅使用2014年就能正常工作;但我可能是错的。

为了访问真正的UsageStats列表,您需要创建一个带有正确月、日和年的日历对象。就像MRK在另一个答案中所说的那样。我复制并纠正了MRK代码中的错误,以便将来看到它的人可以看懂。

Calendar beginCal = Calendar.getInstance();
beginCal.set(Calendar.DATE, 1);
beginCal.set(Calendar.MONTH, 0);
beginCal.set(Calendar.YEAR, 2012);

Calendar endCal = Calendar.getInstance();
endCal.set(Calendar.DATE, 1);
endCal.set(Calendar.MONTH, 0);
endCal.set(Calendar.YEAR, 2014);

final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY, beginCal.getTimeInMillis(), endCal.getTimeInMillis());

-Credit MRK; corrected by me (he accidentally just put cal instead of beginCal and endCal)

下面是使用访问设置的代码。 :)

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

我怎么会错过那个?也许是我看过之后他们添加了它。无论如何,有没有一种方法可以直接到达应用程序以启用/禁用?这样用户只需要确认即可? - android developer
我认为这是不可能的。如果你查看ACTION_USAGE_ACCESS_SETTINGS下的输入,你会发现它什么也没说。因此,我相信没有办法指定某个应用程序。 - Atish
太糟糕了。现在我不知道该如何处理你和MRK回答的问题的不同部分。对我来说唯一缺少的是如何使用它以及“间隔”到底是什么的解释。我还想知道它是否适用于普通设备而不仅仅是模拟器,因为它似乎是系统权限。 - android developer
它可以在设备上运行。您只需检查软件包已经活动的时间,以决定是否正在使用某个活动,或者您可以使用queryEvents()来查看应用程序是否移动到前台或后台。老实说,这真的会让用户不想使用您的应用程序。如果是第一次使用您的应用程序,去给予特殊权限会让用户感到害怕/奇怪。 - Atish
我修复了MRK的代码并将其整合到一个文件中,以便于未来参考。他忘记使用beginCal和endCal而只是使用cal。 - Atish
我不打算强制用户授予权限,但我会发出警告对话框。该应用程序的主要目的与此无关,所以应该没问题:https://play.google.com/store/apps/details?id=com.lb.app_manager 。不管怎样,谢谢。 - android developer

29

谢谢。你的努力得到了+1的认可。 :) - android developer
1
代码有点问题,它假设当你获得所需的权限时,如果结果为空,则表示你没有权限。我尝试使用这些解决方案:https://dev59.com/Z2w05IYBdhLWcg3wfx80,但是所有的解决方案都返回相同的结果(拒绝),无论发生什么。我在这里写了一篇文章:https://dev59.com/dF4b5IYBdhLWcg3wnCpy。 - android developer
这可能是因为它是一个隐藏的权限吗? - Cole Murray

9
回答你的最后一个问题"如何让代码返回真正的应用程序列表?"。queryUsageStats接受毫秒为单位的开始时间和结束时间,而不是int类型的年份值。
Calendar beginCal = Calendar.getInstance();
beginCal.set(Calendar.DATE, 1);
beginCal.set(Calendar.MONTH, 0);
beginCal.set(Calendar.YEAR, 2012);

Calendar endCal = Calendar.getInstance();
endCal.set(Calendar.DATE, 1);
endCal.set(Calendar.MONTH, 0);
endCal.set(Calendar.YEAR, 2014);

final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY, beginCal.getTimeInMillis(), endCal.getTimeInMillis());

这应该返回2012年和2013年的UsageStats列表(请记住结束时间不包括结果时间范围)。

很奇怪,因为文档上写着:beginTime = 2013,endTime = 2015。现在似乎可以工作了。他们为什么需要时间间隔?我已经给他们范围了……另外,你知道怎么开始询问用户这个权限吗?似乎这是一个需要用户手动启用的权限。 - android developer
1
是的,文档根本不清楚。不太清楚为什么他们需要一个时间间隔以及开始和结束时间,但从我推断出来的情况来看,UsageStatsManger将数据存储在每日、每周、每月和每年的桶中,这些桶都有自己的开始和结束时间。例如,每日桶从早上5:30开始。因此,如果您提供了每日间隔以及10/30/2014 00:00的开始时间和10/30/2014 23:59的结束时间,queryUsageStats将返回10/29和10/30的数据。我可能错了,但这就是我在模拟器上测试时所理解的。 - MRK
我不理解你的理解。:( - android developer

9

实际上,AOSP示例代码中包含一个示例应用程序developers/samples/android/system/AppUsageStatistics/

它包括在应用程序中使用UsageStats所需的所有部分:

  1. Declaring the permission in AndroidManifest.xml

    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>

  2. Show settings to grant permission to access UsageStatsManager

    startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));

  3. Query UsageStatsManager for statistics.

    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.YEAR, -1);
    List<UsageStats> queryUsageStats = mUsageStatsManager
            .queryUsageStats(intervalType, cal.getTimeInMillis(),
                    System.currentTimeMillis());
    
  4. Creating a list of apps based on UsageStats

总结并应用到你的例子中:

  • 你似乎正确地请求和授予权限来访问使用统计数据。
  • 你正确获取了UsageStats系统服务。
  • 然而,你查询的时间段太短了:参数beginTimeendTime是以自纪元以来的毫秒数为单位测量的。 Calendar实例可以使用getTimeinMillis()给你这个值。你错误地只提供了年份(20152017如果你今天运行该程序)。这些值被解释为自纪元以来的毫秒数,因此间隔仅为2毫秒,并且在1970年某个时候。

你应该复制我下面发布的示例,而不是你发布的以下代码片段:

错误:

final int currentYear=Calendar.getInstance().get(Calendar.YEAR);
final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,currentYear-2,currentYear);

正确:

final long currentTime = System.currentTimeMillis(); // Get current time in milliseconds

final Calendar cal = Calendar.getInstance();
cal.add(Calendar.YEAR, -2); // Set year to beginning of desired period.
final long beginTime = cal.getTimeInMillis(); // Get begin time in milliseconds

final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY, beginTime, currentTime);

我不明白。这里有什么需要聚合的呢?它难道不只是显示使用统计数据吗?我已经查看了UsageStats文档(在此处:https://developer.android.com/reference/android/app/usage/UsageStats.html),对我来说似乎非常简单...例如,如果我选择一个月来聚合,那意味着什么?“getLastTimeUsed”会返回仅在上个月范围内和前一个月第二个月的时间吗? - android developer
@android-developer,建议您查看UsageStatsManager中的queryUsageStats()方法,该方法可用于查询应用程序的统计信息。您可以在其中看到每个包在每个时间段内返回的UsageStats。例如,如果您查询一年并希望按月间隔显示,则列表可能包含同一包的12个UsageStats元素(如果该应用程序在该时间间隔的12个月中使用)。 - justfortherec
那么就像我写的一样吗? - android developer
@android-developer,不是的。这取决于您传递给beginTimeendTime的值以及您查看的UsageStats实例。如果您需要更多关于UsageStatsManager.queryUsageStats()文档的澄清,请发布一个新问题。这些评论不是做这件事的正确地方。我很乐意在适当答案的更大空间中详细说明。 - justfortherec
但这就是我发布的问题:“如何使用UsageStatsManager”。 - android developer
显示剩余17条评论

7

使用UsageStats会在用户打开通知抽屉或锁定屏幕时获取错误信息。您需要使用UsageStatsManager.queryEvents()并查找具有MOVE_TO_FOREGROUND事件类型的最新事件。


为什么是错误的?您能否展示一下正确的代码,以便演示它更好的表现? - android developer
我刚在三星Galaxy S5 6.0.1和摩托罗拉Moto G5 Plus 8.1.0上测试了这个问题。当屏幕被锁定时,最新的“UsageStats”包含一个“android”而不是最后使用的应用程序。这就是你所指的问题吗? - Sam

1
如果您想查看特定时间段的使用统计数据,您需要首先计算该时间段开始和结束时距离纪元以来的毫秒数。(纪元是自1970年1月1日UTC 00:00:00以来经过的秒数。)或者,如下面的示例代码所示,一种简单的方法是从当前时间以毫秒为单位向后计算。
例如,如果您想要过去4天的使用统计数据,可以使用以下代码:
UsageStatsManager mUsageStatsManager = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);    
List<UsageStats> queryUsageStats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
                    (System.currentTimeMillis() - 345600000), System.currentTimeMillis());

数字345600000是4天的毫秒数。

关于INTERVAL_TYPE这篇文章很好地解释了它。

系统在4个不同的时间间隔内收集和汇总数据,它们分别是:INTERVAL_DAILY、INTERVAL_WEEKLY、INTERVAL_MONTHLY和INTERVAL_YEARLY。系统记录在时间上是有限制的,所以您可以检索最多7天的应用使用数据,以获取每日间隔,最多4周的应用使用数据,以获取每周间隔,最多6个月的应用使用数据,以获取每月间隔,最后最多2年的应用使用数据,以获取每年间隔。

还有第五个选项要提到: INTERVAL_BEST将根据您选择的时间跨度,在上述四个时间间隔之间选择最合适的时间间隔。


但是你使用了INTERVAL_TYPE。 - android developer
那么我们能使用的最长时间是6个月?如果我们已经给定了确切的时间间隔,那么间隔类型的意义是什么,我不明白... - android developer
INTERVAL_TYPE是为了帮助您查看所选时间间隔的统计信息。对于我使用的上面的片段,该统计信息将显示过去4天中软件包的每日应用程序使用情况。 如果您只想查看指定时间间隔(开始和结束时间)的使用统计信息,请查看queryAndAggregateUsageStats(long, long)。否则,INTERVAL_TYPE将生成四种时间间隔类型(每日、每周、每月或每年)的使用统计信息。 - Msgun
我不明白为什么他们要增加额外的参数。他们为什么不能只有一个时间参数,告诉我们从哪个时间段获取数据,就行了呢?如果有某些限制,就在文档里说明嘛… - android developer
可能与此无关,但是您能否告诉我为什么在this上调用#getSystemService而不是直接调用它? - fsljfke

0

你可以这样做

//noinspection ResourceType
final UsageStatsManager usageStatsManager=(UsageStatsManager)context.getSystemService("usagestats");// Context.USAGE_STATS_SERVICE);
final int currentYear=Calendar.getInstance().get(Calendar.YEAR);
final List<UsageStats> queryUsageStats=usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,currentYear-2,currentYear);

UsageStatsManager.INTERVAL_YEARLY 是什么意思? - android developer
提供访问使用历史和统计数据的功能。使用数据被聚合到时间间隔中:一年以上。https://developer.android.com/reference/android/app/usage/UsageStatsManager.html - Abdul Rehman Janjua
我不明白。如果你想要最近两年的数据,为什么会有一年的间隔?如果你选择了不同的间隔,结果会有多大的差异? - android developer
如果您选择 INTERVAL_DAILY,将显示今天的统计数据;如果您选择 INTERVAL_MONTHLY,则会显示本月的统计数据,以此类推。希望这能帮到您。 - Abdul Rehman Janjua
让我们在聊天中继续这个讨论 - Abdul Rehman Janjua
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接