观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

                            本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17425191.html

                            本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。

前言

  UsageStatsManager是用来知晓设备中应用的使用统计。它能给我们提供应用的进入前台动作与时间戳、进入后台的动作与时间戳、上次的使用时间、使用总时长等等信息。此功能在原生的设置-应用-使用统计中有所展示。在一般的场景下,我们可以用此管理开发一些应用使用时长统计、连续使用时长提醒、统计应用被前台化的次数/时间段从而分析应用的使用率。

  下面的部分是讲解一些正常应用开发能使用的情况。博客的最后一部分是系统级别的调用,会直接调用framework的代码,让我们能有最大权限的查询与监视应用的使用情况信息。

所需权限

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

查询当前应用的使用事件

这里使用的是queryEventsForSelf方法,在这个方法返回的数据有每一次进入前后台的时间点。

代码

fun query(context: Context) {
    //查询的开始时间
    val startCalendar = Calendar.getInstance()
    startCalendar.set(Calendar.HOUR_OF_DAY, 0)
    startCalendar.set(Calendar.MINUTE, 0)
    startCalendar.set(Calendar.SECOND, 0)
    //查询的结束时间
    val endCalendar = Calendar.getInstance()
    endCalendar.set(Calendar.HOUR_OF_DAY, 23)
    endCalendar.set(Calendar.MINUTE, 59)
    endCalendar.set(Calendar.SECOND, 59)
    val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    var usageStatsManager: UsageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

    /**
     * 查询当前应用的使用事件
     */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        val usageEvents = usageStatsManager.queryEventsForSelf(startCalendar.timeInMillis, endCalendar.timeInMillis)
        while (usageEvents.hasNextEvent()) {
            val event = UsageEvents.Event()
            usageEvents.getNextEvent(event)
            when (event.eventType) {
                MOVE_TO_FOREGROUND -> Log.e("zh", "进入前台 ${event.packageName} ${simpleDateFormat.format(event.timeStamp)}")
                MOVE_TO_BACKGROUND -> Log.e("zh", "退出后台 ${event.packageName} ${simpleDateFormat.format(event.timeStamp)}")
                else -> Log.e("zh", "其他事件 ${event.eventType} ${event.packageName} ${simpleDateFormat.format(event.timeStamp)}")
            }
        }
    }
}

结果:

2023-05-23 11:25:37.934 14013-14013 zh                      com.xx.dev                         E  进入前台 com.xx.dev 2023-05-23 11:24:56
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  退出后台 com.xx.dev 2023-05-23 11:25:33
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  进入前台 com.xx.dev 2023-05-23 11:25:33
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  退出后台 com.xx.dev 2023-05-23 11:25:34
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  进入前台 com.xx.dev 2023-05-23 11:25:34
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  退出后台 com.xx.dev 2023-05-23 11:25:35
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  进入前台 com.xx.dev 2023-05-23 11:25:35
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  退出后台 com.xx.dev 2023-05-23 11:25:37
2023-05-23 11:25:37.935 14013-14013 zh                      com.xx.dev                         E  进入前台 com.xx.dev 2023-05-23 11:25:37

查询设备全局应用的使用事件

这里使用的是queryEvents方法

代码

fun query(context: Context) {
    //查询的开始时间
    val startCalendar = Calendar.getInstance()
    startCalendar.set(Calendar.HOUR_OF_DAY, 0)
    startCalendar.set(Calendar.MINUTE, 0)
    startCalendar.set(Calendar.SECOND, 0)
    //查询的结束时间
    val endCalendar = Calendar.getInstance()
    endCalendar.set(Calendar.HOUR_OF_DAY, 23)
    endCalendar.set(Calendar.MINUTE, 59)
    endCalendar.set(Calendar.SECOND, 59)
    val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    var usageStatsManager: UsageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager


    /**
     * 查询设备全局应用的使用事件
     */
    val usageEvents = usageStatsManager.queryEvents(startCalendar.timeInMillis, endCalendar.timeInMillis)
    while (usageEvents.hasNextEvent()) {
        val event = UsageEvents.Event()
        usageEvents.getNextEvent(event)
        when (event.eventType) {
            MOVE_TO_FOREGROUND -> Log.e("zh", "进入前台 ${event.packageName} ${simpleDateFormat.format(event.timeStamp)}")
            MOVE_TO_BACKGROUND -> Log.e("zh", "退出后台 ${event.packageName} ${simpleDateFormat.format(event.timeStamp)}")
        }
    }
}

结果

2023-05-23 14:09:23.051  6144-6144  zh                      com.xx.dev                         E  进入前台 com.android.launcher3 2023-05-23 14:08:57
2023-05-23 14:09:23.051  6144-6144  zh                      com.xx.dev                         E  退出后台 com.android.launcher3 2023-05-23 14:09:05
2023-05-23 14:09:23.051  6144-6144  zh                      com.xx.dev                         E  进入前台 com.xx.dev 2023-05-23 14:09:05
2023-05-23 14:09:23.051  6144-6144  zh                      com.xx.dev                         E  退出后台 com.xx.dev 2023-05-23 14:09:08

查询设备全局应用使用统计数据

请注意下面的输入了时间间隔类型UsageStatsManager.INTERVAL_BEST,但是不代表返回的数值就是准确的,比如如果我查询中午12点至下午六点的使用统计,实际上它依然会返回今天应用的全部使用情况

代码

fun query(context: Context) {
    //查询的开始时间
    val startCalendar = Calendar.getInstance()
    startCalendar.set(Calendar.HOUR_OF_DAY, 0)
    startCalendar.set(Calendar.MINUTE, 0)
    startCalendar.set(Calendar.SECOND, 0)
    //查询的结束时间
    val endCalendar = Calendar.getInstance()
    endCalendar.set(Calendar.HOUR_OF_DAY, 23)
    endCalendar.set(Calendar.MINUTE, 59)
    endCalendar.set(Calendar.SECOND, 59)
    val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    var usageStatsManager: UsageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

    /**
     * 查询设备的应用使用统计情况
     */
    val list = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, startCalendar.timeInMillis, endCalendar.timeInMillis)
    for (item in list) {
        Log.e("zh", "包名:${item.packageName}")
        Log.e("zh", "开始时间:${simpleDateFormat.format(item.firstTimeStamp)}")
        Log.e("zh", "上次时间戳:${simpleDateFormat.format(item.lastTimeStamp)}")
        Log.e("zh", "最后使用时间:${simpleDateFormat.format(item.lastTimeUsed)}")
        Log.e("zh", "前台总的时间:${item.totalTimeInForeground}")
    }
}

结果

2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  包名:com.android.server.telecom
2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  开始时间:2023-05-23 09:44:08
2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  上次时间戳:2023-05-23 14:23:13
2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  最后使用时间:1970-01-01 08:00:00
2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  前台总的时间:0
2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  包名:android.ext.services
2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  开始时间:2023-05-23 09:44:08
2023-05-23 14:28:04.162  9020-9020  zh     com.xx.dev     E  上次时间戳:2023-05-23 14:23:13
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  最后使用时间:1970-01-01 08:00:00
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  前台总的时间:0
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  包名:com.mediatek.capctrl.service
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  开始时间:2023-05-23 09:44:08
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  上次时间戳:2023-05-23 14:23:13
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  最后使用时间:1970-01-01 08:00:00
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  前台总的时间:0
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  包名:net.huanci.hsj
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  开始时间:2023-05-23 09:44:08
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  上次时间戳:2023-05-23 14:23:13
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  最后使用时间:2023-05-23 11:10:10
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  前台总的时间:11449
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  包名:com.android.smspush
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  开始时间:2023-05-23 09:44:08
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  上次时间戳:2023-05-23 14:23:13
2023-05-23 14:28:04.163  9020-9020  zh     com.xx.dev     E  最后使用时间:1970-01-01 08:00:00

查询设备全局应用给定范围内的所有统计数据(使用该范围的最佳间隔)

这里使用的是queryAndAggregateUsageStats方法,这个方法里不需要传入时间间隔参数,并且返回的是map集合。

代码

fun query(context: Context) {
    //查询的开始时间
    val startCalendar = Calendar.getInstance()
    startCalendar.set(Calendar.HOUR_OF_DAY, 0)
    startCalendar.set(Calendar.MINUTE, 0)
    startCalendar.set(Calendar.SECOND, 0)
    //查询的结束时间
    val endCalendar = Calendar.getInstance()
    endCalendar.set(Calendar.HOUR_OF_DAY, 23)
    endCalendar.set(Calendar.MINUTE, 59)
    endCalendar.set(Calendar.SECOND, 59)
    val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    var usageStatsManager: UsageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager

    val map = usageStatsManager.queryAndAggregateUsageStats(startCalendar.timeInMillis, System.currentTimeMillis())
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        map.forEach { key, value ->
            Log.e("zh", "包名:${key}")
            Log.e("zh", "开始时间:${simpleDateFormat.format(value.firstTimeStamp)}")
            Log.e("zh", "上次使用时间戳:${simpleDateFormat.format(value.lastTimeStamp)}")
            Log.e("zh", "最后使用时间:${simpleDateFormat.format(value.lastTimeUsed)}")
            Log.e("zh", "前台总的时间:${value.totalTimeInForeground}")
        }
    }
}

结果

2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  包名:net.huanci.hsj
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  开始时间:2023-05-23 09:44:08
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  上次使用时间戳:2023-05-23 14:23:13
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  最后使用时间:2023-05-23 11:10:10
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  前台总的时间:11449
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  包名:com.android.smspush
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  开始时间:2023-05-22 09:38:07
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  上次使用时间戳:2023-05-23 14:23:13
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  最后使用时间:1970-01-01 08:00:23
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  前台总的时间:0
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  包名:com.android.settings
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  开始时间:2023-05-22 09:38:07
2023-05-23 14:38:09.392 11063-11063 zh   com.xx.dev   E  上次使用时间戳:2023-05-23 09:38:07
2023-05-23 14:38:09.393 11063-11063 zh   com.xx.dev   E  最后使用时间:2023-05-23 09:33:18
2023-05-23 14:38:09.393 11063-11063 zh   com.xx.dev   E  前台总的时间:3328776

时间间隔类型

在上面的方法中,有要求输入查询时间的间隔类型,下面一共提供了4种间隔类型。

/**
 * 跨越一天的间隔类型。参见{@link queryUsageStats(int, long, long)}。
 */
public static final int INTERVAL_DAILY = 0;

/**
 * 跨越一周的间隔类型。参见{@link queryUsageStats(int, long, long)}。
 */
public static final int INTERVAL_WEEKLY = 1;

/**
 * 跨越一个月的间隔类型。参见{@link queryUsageStats(int, long, long)}。
 */
public static final int INTERVAL_MONTHLY = 2;

/**
 * 跨越一年的间隔类型。参见{@link queryUsageStats(int, long, long)}。
 */
public static final int INTERVAL_YEARLY = 3;

/**
 * 一种间隔类型,它将使用给定时间范围的最佳拟合间隔。参见{@link queryUsageStats(int, long, long)}。
 */
public static final int INTERVAL_BEST = 4;

查询目标包名应用是否活跃

代码

@RequiresApi(Build.VERSION_CODES.M)
fun isAppInactive(context: Context, packageName:String):Boolean {
    val usageStatsManager: UsageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
    return usageStatsManager.isAppInactive(packageName)
}

查询当前应用的使用情况桶

其实就是查询当前应用在用户行为中经常使用的习惯,注意只能查自己的应用

    @RequiresApi(Build.VERSION_CODES.P)
    fun standbyBucket(context: Context) {
        val usageStatsManager: UsageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
        val standbyBucket = usageStatsManager.appStandbyBucket
        when (standbyBucket) {
            UsageStatsManager.STANDBY_BUCKET_ACTIVE -> {
                Log.e("zh", "当前应用在前台运行")
            }
            UsageStatsManager.STANDBY_BUCKET_WORKING_SET -> {
                Log.e("zh", "当前应用是一个活跃的前台应用,会被频繁的切换到前台")
            }
            UsageStatsManager.STANDBY_BUCKET_FREQUENT -> {
                Log.e("zh", "当前应用是一个常用的后台应用")
            }
            UsageStatsManager.STANDBY_BUCKET_RARE -> {
                Log.e("zh", "当前应用是一个不常用的后台应用")
            }
            //此类型是一个系统级别的被隐藏的类型
//            UsageStatsManager.STANDBY_BUCKET_NEVER -> {
//                Log.e("zh", "当前应用很少被使用")
//            }
        }
    }

其他api

queryConfigurations(int intervalType, long beginTime, long endTime) 获取指定时间区间内硬件配置信息统计数据。

查看IUsageStatsManager的实现位置(系统级别调用代码在framework里)

接下来我们讲解的部分都是系统级的功能,framework里的调用,直接解除一切限制。所以老生常谈,需要你的应用是系统级的并且最好能framework架包(这个我的个人博客里都有讲解自行搜索),新人请了解普通应用与系统级应用的区别。

上面的例子中使用的都是UsageStatsManager,它只是一个功能封装的管理类,它的功能实现其实大部分是调用了IUsageStatsManager。IUsageStatsManager其实是AIDL的接口类。它在framework的UsageStatsService实现位置是如下图片:

 

 

 

end

posted on 2023-05-23 14:51  观心静  阅读(2049)  评论(2编辑  收藏  举报