Android 5.0 应用使用情况统计信息

时间:2021-06-10 22:32:29

Android 5.0 应用使用情况统计信息

概述

在Android 5.0以前,我们使用ActivityManager.getRecentTasks()方法来获取最近使用过的应用程序信息集合,此方法不需要用户授权,任何应用都可以获取用户最近使用应用的信息,出于更好的保护用户的隐私,在Android 5.0 及以后google废除了此方法。但是为我们引入了信息更详细的android.app.usage API,但是要使用此API,您必须先在清单中声明 android.permission.PACKAGE_USAGE_STATS 权限,用户还必须通过 Settings > Security > Apps 为该应用启用访问使用情况的权限。看起来比以前麻烦了很多,我们又该如何使用了?下面我们一起来看一看吧!

权限

要使用android.app.usage API ,首先必须要在AndroidManifest.xml中声明权限,如下:

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

然后需要打开允许查看使用情况的应用界面,引导用户授权,如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
} catch (Exception e) {
e.printStackTrace();
}
}

当用户开启了权限之后,我们就可以使用android.app.usage API获取应用最近的使用信息了。当然用户也可能去关闭了权限,所以我们需要每次对权限进行判断,从而保证权限可用,判断方法如下:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean checkUsagePermission(Context context) {
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(),ontext.getPackageName());
return mode == AppOpsManager.MODE_ALLOWED;
}

除了上面的这种判断方法,我们还可以通过是否获取到了使用信息来判断是否开启了权限,如果没有开启权限是获取不到使用信息的,相反开了权限就可以获取到使用信息。以上就是使用android.app.usage API所需要的权限和是否开启了权限的判断方法。下面我们来看一看android.app.usage API中包含了那些信息,这些信息怎样获取,有什么作用,保存时间是多久。

保存时间

系统以应用为单位收集使用数据,按天、周、月、和年汇总数据,系统保存这些数据的最长持续时间如下:

  • 每日数据:7天
  • 每周数据:4周
  • 每月数据:6个月
  • 每年数据:2年

超过了最长持续时间的数据系统会自动汇总并清理。

记录那些数据

系统会为每个应用记录以下数据:

  • 最后一次使用应用的时间
  • 在该时间间隔(以天、周、月或年为单位)内应用位于前台的总时长
  • 一天之中当组件(以软件包和 Activity 名称标识)转入前台或后台时记录的时间戳
  • 设备配置发生变化(如设备屏幕方向因旋转而发生变化)时记录的时间戳

如何获取数据

其实获取应用使用情况的统计信息,主要是通过UsageStatsManager类来获取,调用不同的方法来获取不同的信息,不同的方法返回的数据类型又不一样,返回的数据都是UsageStatsConfigurationStatsUsageEvents这三个类的对象,通过这三个类的对象我们就能获取出我们需要的信息。下面我们一起来看看,这几个类都包含了那些信息,这些信息有什么作用,如何获取这些信息。

UsageStatsManager

UsageStatsManager类提供了对设备使用历史记录和统计信息的访问,通过它我们可以获取到应用使用情况的统计信息和使用设备配置的信息,UsageStatsManager类的对象通过下面的方法获取。

UsageStatsManager usm = (UsageStatsManager) getSystemServic(Context.USAGE_STATS_SERVICE);

获取到了UsageStatsManager类的对象,我们就可以取得我们需要的信息,下面我们来看看UsageStatsManager类有那些信息,这些信息有什么作用,怎样获取。

1、queryUsageStats方法

queryUsageStats方法的作用是在给定的时间范围内获取应用程序使用统计信息,下面是该方法的源码:

public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
try {
@SuppressWarnings("unchecked")
ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,endTime, mContext.getOpPackageName());
if (slice != null) {
return slice.getList();
}
} catch (RemoteException e) {
// fallthrough and return null.
}
return Collections.emptyList();
}

从源码可以看出queryUsageStats方法需要传入三个参数,然后返回UsageStats类的对象集合。下面我们来看看这三个参数应该怎样传入,它们有什么作用。

  1. intervalType —— 获取适合的数据来源,可以是按天、按星期、按月、按年等等,下面是该参数的类型:
    • UsageStatsManager.INTERVAL_DAILY —— 按天
    • UsageStatsManager.INTERVAL_WEEKLY —— 按星期
    • UsageStatsManager.INTERVAL_MONTHLY —— 按月
    • UsageStatsManager.INTERVAL_YEARLY —— 按年
    • UsageStatsManager.INTERVAL_BEST —— 在给定时间范围内使用最佳拟合间隔的间隔类型
  2. beginTime —— 开始时间
  3. endTime —— 结束时间

intervalType参数的选择,就看你beginTime和endTime的时间范围是多少了,如果你的时间范围是以月为单位,那么intervalType参数就应该传入UsageStatsManager.INTERVAL_MONTHLY,具体传入参数根据具体情况判断;当然你也可以直接使用UsageStatsManager.INTERVAL_BEST,这个参数会根据你传入的时间间隔自动确定适合的数据来源。beginTime和endTime是限制获取信息的时间范围,通过beginTime和endTime来更精确的取出这段时间内的信息。这两个参数非常有用,因为取的范围越小所需的时间就越少,所以我们应该尽可能缩小范围,从而减少查询所需的时间。

2、queryConfigurations方法

queryConfigurations方法的作用是获取设备在给定时间范围内的硬件配置的使用情况,结果会按照queryUsageStats方法获取的集合顺序排列,下面是该方法的源码:

public List<ConfigurationStats> queryConfigurations(int intervalType, long beginTime, long endTime) {
try {
@SuppressWarnings("unchecked")
ParceledListSlice<ConfigurationStats> slice = mService.queryConfigurationStats(intervalType, beginTime, endTime, mContext.getOpPackageName());
if (slice != null) {
return slice.getList();
}
} catch (RemoteException e) {
// fallthrough and return the empty list.
}
return Collections.emptyList();
}

从源码可以看出queryConfigurations方法的参数和queryUsageStats方法的一样,所以不再说明,只是返回值不一样,queryConfigurations方法返回的是ConfigurationStats对象的集合。ConfigurationStats类下面将会详细介绍,所以这里就不在赘述。

3、queryEvents方法

queryEvents方法的作用是查询给定时间范围内的事件,下面是该方法的源码:

public UsageEvents queryEvents(long beginTime, long endTime) {
try {
UsageEvents iter = mService.queryEvents(beginTime, endTime,mContext.getOpPackageName());
if (iter != null) {
return iter;
}
} catch (RemoteException e) {
// fallthrough and return null
}
return sEmptyResults;
}

从上面的源码可以看出queryEvents方法比queryConfigurations方法和queryUsageStats方法少了
获取适合的数据来源这个参数,那是因为queryEvents方法默认的是按天获取数据,所以不需要选择适合的数据来源。beginTime、endTime和上面两个方法的用法是一样的。返回值为UsageEvents类的对象,UsageEvents类下面也会详细介绍,这里也就不在说明。

4、queryAndAggregateUsageStats方法

queryAndAggregateUsageStats方法的作用是查询给定范围内所有统计信息的方便方法(使用该范围的最佳间隔),合并生成的数据,并按包名保存,下面是该方法的源码:

public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
if (stats.isEmpty()) {
return Collections.emptyMap();
}
ArrayMap<String, UsageStats> aggregatedStats = new ArrayMap<>();
final int statCount = stats.size();
for (int i = 0; i < statCount; i++) {
UsageStats newStat = stats.get(i);
UsageStats existingStat = aggregatedStats.get(newStat.getPackageName());
if (existingStat == null) {
aggregatedStats.put(newStat.mPackageName, newStat);
} else {
existingStat.add(newStat);
}
}
return aggregatedStats;
}

从上面的源码可以看出queryAndAggregateUsageStats方法也只有beginTime和endTime两个参数,数据来源默认的是获取在开始和结束时间范围内的最佳数据来源。该方法的返回值是一个以包名为键,UsageStats类的对象为值的Map,这样的好处是可以通过包名很快的找到需要的值。从上面的源码还可以看出queryAndAggregateUsageStats方法内部实现使用的是queryUsageStats方法,只不过将queryUsageStats方法返回的数据封装到了Map中,从而使数据更容易获取。

5、isAppInactive方法

isAppInactive方法的作用是判断指定的应用程序当前是否被视为无效,无效的意思是应用程序在系统定义的一段时间内没有直接或间接使用过,下面是该方法的源码:

public boolean isAppInactive(String packageName) {
try {
return mService.isAppInactive(packageName, UserHandle.myUserId());
} catch (RemoteException e) {
// fall through and return default
}
return false;
}

这个函数的使用比较简单就不做说明了。

以上的5个方法就是UsageStatsManager的主要方法了,通过它们我们可以获取到应用使用情况的统计信息和设备配置的使用信息。说完了UsageStatsManager类,下面我们来看看数据保存的类,这些类都有那些信息,有什么作用,如何获取。

UsageStats

从上面可以看出,在UsageStatsManager类中的queryAndAggregateUsageStatsqueryUsageStats方法保存数据使用的是UsageStats类,这个类包含针对特定时间范围的应用程序包的使用情况统计信息,这里面的数据就是我们需要的应用使用情况的统计信息。该类里面主要包含了五个方法分别是:

  1. getPackageName() —— 获取应用程序的包名
  2. getFirstTimeStamp() —— 获取第一次运行的时间,以毫秒为单位
  3. getLastTimeStamp() —— 获取最后一次运行的时间,以毫秒为单位
  4. getLastTimeUsed() —— 获取上一次运行的时间,以毫秒为单位
  5. getTotalTimeInForeground() —— 获取应用在前台的总时间,以毫秒为单位

使用这几个方法就可以取得应用使用的一些基本信息了。但是在该类里面还有一个比较有用的信息即应用启动次数统计,但是UsageStats类中并没有提供该方法获取,我们只能通过反射来获取到该值,代码如下:

try {
Field field = usageStats.getClass().getDeclaredField("mLaunchCount");//获取应用启动次数,UsageStats未提供方法来获取,只能通过反射来拿到
if(fied != null)
int count = field.getInt(usageStats);
} catch (Exception e) {
e.printStackTrace();
}

以上就是我们能从UsageStats类中获取到的应用使用情况的统计信息了,具体代码如下:

UsageStatsManager usm = (UsageStatsManager) getSystemServic(Context.USAGE_STATS_SERVICE);
Calendar calendar = Calendar.getInstance();
long endTime = calendar.getTimeInMillis();
calendar.add(Calendar.DAY_OF_WEEK, -2);
long startTime = calendar.getTimeInMillis();
List<UsageStats> list = usm.queryUsageStats(UsageStatsManager.INTERVAL_YEARLY,startTime,endTime);
//当没有权限时的处理
if(list == null || list.isEmpty()) {
try {
startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
} catch (Exception e) {
e.printStackTrace();
}
} else {
for(UsageStats usageStats :list) {
usageStats.getPackageName();//获取包名
usageStats.getFirstTimeStamp();//获取第一次运行的时间
usageStats.getLastTimeStamp();//获取最后一次运行的时间
usageStats.getLastTimeUsed();//获取上一次运行的时间
usageStats.getTotalTimeInForeground();//获取总共运行的时间
try {
Field field = usageStats.getClass().getDeclaredField("mLaunchCount");//获取应用启动次数,UsageStats未提供方法来获取,只能通过反射来拿到
if(fied != null)
int count = field.getInt(usageStats);
} catch (Exception e) {
e.printStackTrace();;
}
}
}

ConfigurationStats

UsageStats类包含的是特定时间范围内应用程序的使用信息,而ConfigurationStats类包含的是特定时间范围内设备配置的使用统计信息。所以这两个类还是有很大的区别的,上面介绍了UsageStats类,下面我们一起来看看ConfigurationStats类有那些方法,它们有什么作用。

  1. getConfiguration() —— 获取所有设备配置信息影响应用程序检索的资源即Configuration类的对象
  2. getFirstTimeStamp() —— 获取配置第一次访问时间,以毫秒为单位
  3. getLastTimeStamp() —— 获取配置最后一次访问时间,以毫秒为单位
  4. getLastTimeActive() —— 获取最后一次配置处于活动状态的时间,以毫秒为单位
  5. getTotalTimeActive() —— 获取配置活动的总时间,以毫秒为单位
  6. getActivationCount() —— 获取配置处于活动状态的次数

以上就是ConfigurationStats类的主要方法及其作用,通过这些方法我们就可以获取到设备配置的使用统计信息,具体代码如下:

UsageStatsManager usageStatsManager = (UsageStatsManager) getSystemService(USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
//获取最近一小时的设备配置的使用统计信息
List<ConfigurationStats> configurationStats = usageStatsManager.queryConfigurations(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 60, time);
if (configurationStats == null || configurationStats.isEmpty()) {
try {
startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
} catch (Exception e) {
e.printStackTrace();
}
} else {
for (ConfigurationStats configurationStats1 : configurationStats) {
configurationStats1.getConfiguration();//获取所有设备配置信息影响应用程序检索的资源即Configuration类
configurationStats1.getFirstTimeStamp();//获取配置第一次访问时间
configurationStats1.getLastTimeStamp();//获取配置最后一次访问时间
configurationStats1.getLastTimeActive();//获取最后一次此配置处于活动状态的时间
configurationStats1.getTotalTimeActive();//获取此配置活动的总时间
configurationStats1.getActivationCount();//获取此配置处于活动状态的次数
}
}

UsageEvents

UsageEvents类的作用是统计在给定时间范围内的应用事件,它有一个内部类Event类,这里面才是应用真正的事件统计信息。下面我们先来看看Event这个内部类里面包含了那些事件信息和怎样获取。

  1. getPackageName() —— 和UsageStats类中的getPackageName()方法一样,获取应用的包名
  2. getClassName() —— 该方法的作用是获取类名,在UsageStats类中没有此方法,该方法配合getPackageName()方法可以精确的找到是那个应用的那个界面的统计信息
  3. getTimeStamp() —— 发生此事件的时间,以毫秒为单位
  4. getEventType() —— 通过此方法可以判断应用是处在前台还是后台,
    UsageEvents.Event.MOVE_TO_FOREGROUND应用在前台,UsageEvents.Event.MOVE_TO_BACKGROUND应用处于后台
  5. getConfiguration() —— 和ConfigurationStats类中的getConfiguration()方法一样,获取Configuration类的对象

以上是Event内部类中的主要方法和作用。UsageEvents类除了Event内部类外,还有两个方法经常使用,如下:

  1. hasNextEvent() —— 返回是否有更多要使用的事件读取,没有会返回false,有会返回true,一般在循环读取数据的时候使用
  2. getNextEvent(Event eventOut) —— 获取应用事件的统计信息,会将数据保存到eventOut中,返回值为boolean型,true表示获取成功,false表示获取失败

上面就是UsageEvents包含的事件统计信息和获取方法,下面我们来看看代码实现,如下:

UsageStatsManager usageStatsManager = (UsageStatsManager) getSystemService(USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
//获取最近一小时的应用最近使用统计信息
UsageEvents usageEvents = usageStatsManager.queryEvents(time - 1000 * 60, time);
if (usageEvents == null || !usageEvents.hasNextEvent()) {
try {
startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
} catch (Exception e) {
e.printStackTrace();
}
} else {
while (usageEvents.hasNextEvent()) {
UsageEvents.Event event = new UsageEvents.Event();
if (usageEvents.getNextEvent(event)) {
if (event.getEventType() == UsageEvents.Event.MOVE_TO_BACKGROUND) {
Log.d("UsageEvents", "应用在前台");
}
event.getPackageName();//获取包名
event.getClassName();//获取类名
event.getTimeStamp();//获取发生此事件的时间
event.getConfiguration();//获取可以的所有设备配置信息影响应用程序检索的资源
}
}
}

应用

以下是我觉得android.app.usage API在实际中主要的应用,如下:

  1. 统计应用的启动次数
  2. 锁定第三方应用
  3. 判断应用是否经常使用
  4. 获取进程名

注意事项

以下是我觉得使用android.app.usage API应该注意的事项,如下:

  1. 在使用之前要引导用户开启权限,不然无法获取数据
  2. 在使用时,需要每次判断权限是否被关闭了,如果关闭了需要重新引导用户开启
  3. 在查询数据时,应该尽量缩小时间范围,从而减少查询时间
  4. 通过queryConfigurations方法获取的设备配置使用信息的顺序是和queryUsageStats方法获取的应用使用情况的信息的顺序一样的,前提是在相同时间范围内
  5. 当时间范围大于数据来源的最大保存天数时,获取的数据只能获取到数据来源的最大保存天数