UsageStatsManager统计应用使用情况
UsageStatsManager提供对设备使用历史记录和统计信息的访问,时间周期为days、weeks、months、years。可以帮助开发者了解用户在设备上使用应用程序的情况,包括应用程序的启动次数、使用时长等。
1、权限
使用UsageStatsManager需要权限:
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
可以通过AppOpsManager检查是否拥有UsageStatsManager权限:
fun checkUsageAccessPermission(): Boolean {
val appOpsManager: AppOpsManager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
val mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), packageName)
return when (mode) {
AppOpsManager.MODE_ALLOWED -> true
AppOpsManager.MODE_DEFAULT -> checkCallingPermission(PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED
else -> false
}
}
如果没有权限,就打开应用使用情况访问权限
val intent: Intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
startActivity(intent)
引导用户授予相应的权限。
获取到权限之后,和众多Service一样,通过getSystemService()方法获取UsageStatsManager实例:
val usageStateManager: UsageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
一共有六个查询使用情况的方法:queryUsageStats()、queryAndAggregateUsageStats()、queryEvents()、queryEventStats()、queryEventsForSelf()、queryConfigurations(),接下来分别了解这些方法的查询作用。
val calendar = Calendar.getInstance()
val endTime = calendar.timeInMillis
calendar.add(Calendar.DAY_OF_WEEK, -1)
val startTime = calendar.timeInMillis
在这之前先定义查询的时间范围,startTime为要查询事件范围的起始时间点,endTime为时间范围终点(不包括)。
2、使用统计
本节来看和应用使用情况统计信息有关的方法:queryUsageStats()和queryAndAggregateUsageStats()。可以用来查询应用使用统计信息,包括:应用的前台使用时长,应用首次启动的时间,应用启动次数,应用最近一次使用的时间等。
2.1、queryUsageStats
queryUsageStats()方法获取给定时间范围内的应用程序使用情况统计信息,按指定的时间间隔聚合。
@UserHandleAware
public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) {
try {
@SuppressWarnings("unchecked")
ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,
endTime, mContext.getOpPackageName(), mContext.getUserId());
if (slice != null) {
return slice.getList();
}
} catch (RemoteException e) {
// fallthrough and return the empty list.
}
return Collections.emptyList();
}
后面两个参数很容易明白是要查询事情的时间范围[beginTime,endTime),传入我们定义的起始时间startTime和终止时间endTime即可。第一个参数intervalType为聚合统计信息的时间间隔,定义了四个间隔类型:
/**
* The number of available intervals. Does not include {@link #INTERVAL_BEST}, since it
* is a pseudo interval (it actually selects a real interval).
* {@hide}
*/
public static final int INTERVAL_COUNT = 4;
这四个值分别以日、周、月、年为统计时间周期。
/**
* An interval type that spans a day. See {@link #queryUsageStats(int, long, long)}.
*/
public static final int INTERVAL_DAILY = 0;
/**
* An interval type that spans a week. See {@link #queryUsageStats(int, long, long)}.
*/
public static final int INTERVAL_WEEKLY = 1;
/**
* An interval type that spans a month. See {@link #queryUsageStats(int, long, long)}.
*/
public static final int INTERVAL_MONTHLY = 2;
/**
* An interval type that spans a year. See {@link #queryUsageStats(int, long, long)}.
*/
public static final int INTERVAL_YEARLY = 3;
当然其实还有一个间隔类型:INTERVAL_BEST,在给定时间范围内使用最佳拟合间隔。
/**
* An interval type that will use the best fit interval for the given time range.
* See {@link #queryUsageStats(int, long, long)}.
*/
public static final int INTERVAL_BEST = 4;
2.2、queryAndAggregateUsageStats
queryAndAggregateUsageStats()其实是逐个遍历queryUsageStats()返回的结果,按照包名将UsageStats整合:
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;
}
同一个应用如有多条记录会合并结果后再封装起来。UsageStats类定义了add()加法,用来相加合并两个对象的统计信息:
/**
* Add the statistics from the right {@link UsageStats} to the left. The package name for
* both {@link UsageStats} objects must be the same.
* @param right The {@link UsageStats} object to merge into this one.
* @throws java.lang.IllegalArgumentException if the package names of the two
* {@link UsageStats} objects are different.
*/
public void add(UsageStats right) {
if (!mPackageName.equals(right.mPackageName)) {
throw new IllegalArgumentException("Can't merge UsageStats for package '" +
mPackageName + "' with UsageStats for package '" + right.mPackageName + "'.");
}
// We use the mBeginTimeStamp due to a bug where UsageStats files can overlap with
// regards to their mEndTimeStamp.
if (right.mBeginTimeStamp > mBeginTimeStamp) {
// Even though incoming UsageStat begins after this one, its last time used fields
// may somehow be empty or chronologically preceding the older UsageStat.
mergeEventMap(mActivities, right.mActivities);
mergeEventMap(mForegroundServices, right.mForegroundServices);
mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed);
mLastTimeVisible = Math.max(mLastTimeVisible, right.mLastTimeVisible);
mLastTimeComponentUsed = Math.max(mLastTimeComponentUsed, right.mLastTimeComponentUsed);
mLastTimeForegroundServiceUsed = Math.max(mLastTimeForegroundServiceUsed,
right.mLastTimeForegroundServiceUsed);
}
mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
mTotalTimeInForeground += right.mTotalTimeInForeground;
mTotalTimeVisible += right.mTotalTimeVisible;
mTotalTimeForegroundServiceUsed += right.mTotalTimeForegroundServiceUsed;
mLaunchCount += right.mLaunchCount;
mAppLaunchCount += right.mAppLaunchCount;
...
}
3、事件统计
UsageStatsManager还提供了几个方法用来查询统计事件:queryEvents、queryEventsForSelf、queryEventStats。
3.1、queryEvents
queryEvents(long beginTime, long endTime):查询给定时间范围内的事件,事件仅由系统保留几天。
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 empty result.
}
return sEmptyResults;
}
返回UsageEvents类型对象,通过hasNextEvent()方法遍历全部事情。
while (event.hasNextEvent()) {
val e = UsageEvents.Event()
event.getNextEvent(e)
Log.d("${TAG}_UsageEvents", "e :${e.eventType} ${e.packageName} ${e.className}")
}
从Android R开始,如果用户的设备未处于解锁状态(由UserManager.isUserUnlocked()定义),则将返回null。
3.2、queryEventsForSelf
queryEventsForSelf(long beginTime, long endTime):与queryEvents(long beginTime, long endTime)类似,但只返回调用包的事件。
public UsageEvents queryEventsForSelf(long beginTime, long endTime) {
try {
final UsageEvents events = mService.queryEventsForPackage(beginTime, endTime,
mContext.getOpPackageName());
if (events != null) {
return events;
}
} catch (RemoteException e) {
// fallthrough
}
return sEmptyResults;
}
在UserUsageStatsService中查询的时候过滤非当前应用的事件:
@Nullable
UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
final String packageName, boolean includeTaskRoot) {
...
if (!packageName.equals(event.mPackage)) {
continue;
}
...
final String[] table = names.toArray(new String[names.size()]);
Arrays.sort(table);
return new UsageEvents(results, table, includeTaskRoot);
}
同样从Android R开始都无法在未解锁状态下查询事件信息。
3.3、queryEventStats
queryEventStats(int intervalType, long beginTime, long endTime):获取给定时间范围的聚合事件统计信息,按指定的intervalType时间间隔聚合。
public List<EventStats> queryEventStats(int intervalType, long beginTime, long endTime) {
try {
@SuppressWarnings("unchecked")
ParceledListSlice<EventStats> slice = mService.queryEventStats(intervalType, beginTime,
endTime, mContext.getOpPackageName());
if (slice != null) {
return slice.getList();
}
} catch (RemoteException e) {
// fallthrough and return the empty list.
}
return Collections.emptyList();
}
queryEventStats(int intervalType, long beginTime, long endTime)方法返回聚合的List列表,包含每个事件类型的EventStats对象。通过该对象可以获取到一些有用的信息:
public final class EventStats implements Parcelable {
/**
* {@hide}
*/
public int mEventType;
/**
* {@hide}
*/
public long mBeginTimeStamp;
/**
* {@hide}
*/
public long mEndTimeStamp;
/**
* {@hide}
*/
public long mLastEventTime;
/**
* {@hide}
*/
public long mTotalTime;
/**
* {@hide}
*/
public int mCount;
...
}
除了以上介绍的方法,还有一个queryConfigurations(int intervalType, long beginTime,long endTime)方法,查询给定时间范围内按照指定时间聚合的配置变化统计,返回一个ConfigurationStats集合,没有什么应用场景。
UsageStatsManager了解这么多就差不多了,除了系统应用有一些应用场景,三放应用开发者一般用不到,也没有实际使用场景。