关于UsageStats获取应用前台总时长的问题
嘿,我之前做应用使用时长统计时也踩过一模一样的坑,来帮你逐个解决这些问题:
1. 锁屏时应用是否仍会被判定为前台?
答案是不会——正常情况下,锁屏后系统会把所有前台应用切换到后台状态,应用的foreground标记会被系统收回。但UsageStatsManager的getTotalTimeInForeground之所以会出现锁屏后时长继续增长的情况,是因为它的统计逻辑存在延迟或者误判:
- 部分应用可能在锁屏后短暂持有前台状态的标记(比如某些后台服务绑定了前台通知,但这其实不算真正的前台Activity)
- 系统对应用状态的更新同步到
UsageStats数据库有延迟,导致统计数据没有及时修正
所以这个增长的时长是虚假的,不是应用真的在锁屏时处于前台。
2. 如何获取各应用的真实前台时长?
这里给你三个亲测有效的方案,按需选择:
方案一:结合锁屏时间修正UsageStats数据
先获取设备的锁屏/解锁时间戳,然后遍历每个应用的UsageStats数据,把锁屏时间段内的时长从getTotalTimeInForeground的结果中扣除。这种方法不需要额外权限(除了UsageStats的权限),实现简单,但依赖锁屏数据的准确性。方案二:用AccessibilityService监听前台切换(最精准)
通过AccessibilityService监听应用的窗口状态变化,自己记录每个应用的前台起止时间:- 创建继承自
AccessibilityService的类,在onAccessibilityEvent方法中监听TYPE_WINDOW_STATE_CHANGED事件 - 从事件中获取当前活跃应用的包名,结合时间戳记录进入/退出前台的时间点
- 后期通过时间差计算真实的前台时长
注意:这个方案需要用户手动开启辅助功能权限,适合对精度要求高的场景。
- 创建继承自
方案三:用UsageEvents筛选真实前台事件
调用UsageStatsManager.queryEvents()获取应用使用事件,筛选出MOVE_TO_FOREGROUND(进入前台)和MOVE_TO_BACKGROUND(退出前台)事件,通过这些事件的时间戳计算每个应用的前台时长:UsageEvents events = usageStatsManager.queryEvents(startTime, endTime); UsageEvents.Event event = new UsageEvents.Event(); while (events.hasNextEvent()) { events.getNextEvent(event); if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) { // 记录应用进入前台的时间 } else if (event.getEventType() == UsageEvents.Event.MOVE_TO_BACKGROUND) { // 计算该应用本次前台时长并累加 } }这种方法可以避开锁屏时的误统计,因为应用在锁屏时一定会触发
MOVE_TO_BACKGROUND事件(正常情况下)。
3. 如何获取Android设备的锁屏/解锁时间数据?
同样有几种可行的方式:
方案一:监听系统广播(实时获取)
动态注册ACTION_SCREEN_OFF(锁屏)、ACTION_SCREEN_ON(亮屏)、ACTION_USER_PRESENT(解锁成功)广播,在广播接收器中记录时间戳:// 动态注册广播 IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_PRESENT); registerReceiver(screenReceiver, filter); // 广播接收器示例 private BroadcastReceiver screenReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { long timestamp = System.currentTimeMillis(); switch (intent.getAction()) { case Intent.ACTION_SCREEN_OFF: // 记录锁屏时间 break; case Intent.ACTION_USER_PRESENT: // 记录解锁时间 break; } } };注意:Android 8.0及以上静态注册这些广播无效,必须动态注册。
方案二:用UsageEvents获取历史锁屏/解锁数据
调用UsageStatsManager.queryEvents(),筛选SCREEN_INTERACTIVE(设备解锁,处于可交互状态)和SCREEN_NON_INTERACTIVE(设备锁屏,不可交互)事件,就能拿到历史的锁屏/解锁时间:UsageEvents events = usageStatsManager.queryEvents(startTime, endTime); UsageEvents.Event event = new UsageEvents.Event(); while (events.hasNextEvent()) { events.getNextEvent(event); if (event.getEventType() == UsageEvents.Event.SCREEN_NON_INTERACTIVE) { // 锁屏时间 } else if (event.getEventType() == UsageEvents.Event.SCREEN_INTERACTIVE) { // 解锁时间 } }这个方案适合统计过去一段时间内的锁屏解锁记录,不需要额外权限(只要有UsageStats权限)。
方案三:PowerManager判断当前状态
如果只需要判断当前设备是否锁屏,可以用PowerManager的isInteractive()方法:PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); boolean isScreenOn = powerManager.isInteractive(); // true表示未锁屏,false表示锁屏但这个方法只能获取当前状态,无法拿到历史时间数据。
最后要注意的权限问题
- 使用
UsageStatsManager需要申请android.permission.PACKAGE_USAGE_STATS权限,需要引导用户到「设置-应用-权限管理-特殊权限-使用情况访问权限」中开启 AccessibilityService需要用户到「设置-辅助功能」中手动开启对应服务
内容的提问来源于stack exchange,提问作者Christian Gilli

