UsageStatsManager统计应用使用情况

Quibbler 5月前 616

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();
    }


        后面两个参数很容易明白是要查询事情的时间范围[beginTimeendTime),传入我们定义的起始时间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还提供了几个方法用来查询统计事件:queryEventsqueryEventsForSelfqueryEventStats

        

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了解这么多就差不多了,除了系统应用有一些应用场景,三放应用开发者一般用不到,也没有实际使用场景。



不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com