Android后台服务检测当前运行应用方案(适配Android10+)
问题原因
你当前使用的ActivityManager.getRunningAppProcesses()接口从Android 5.0开始就被逐步收紧调用权限,Android 10(API 29)及以上版本中,当应用处于后台状态时,调用该接口仅能返回应用自身的进程信息,无法获取其他第三方应用的运行状态,这是系统出于隐私保护做的强制限制,不存在绕开权限的黑科技,必须使用官方公开的合规API实现需求。
实现方案
按实现成本和兼容性从高到低排序:
方案一:监听系统屏保广播(最轻量,优先推荐)
所有合规实现的屏保应用(包括系统原生屏保、第三方屏保)在启动/退出时,系统都会发送对应广播,Android 6.0(API 23)及以上版本可直接动态注册广播接收器监听,不需要申请特殊权限,无额外功耗,不需要轮询:
- 注册广播的代码:
IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.DREAMING_STARTED"); // 屏保启动 filter.addAction("android.intent.action.DREAMING_STOPPED"); // 屏保退出 context.registerReceiver(new ScreenSaverReceiver(), filter);
- 广播接收器实现:
public class ScreenSaverReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if ("android.intent.action.DREAMING_STARTED".equals(action)) { // 屏保正在运行,按业务逻辑判断是否需要启动自身应用 } else if ("android.intent.action.DREAMING_STOPPED".equals(action)) { // 屏保已退出 } } }
注意:部分不遵循Android开发规范的第三方屏保应用可能不会触发该广播,这类场景需要搭配下面的前台应用检测方案兜底。
方案二:UsageStatsManager获取前台应用(全场景兼容)
这是目前普通应用检测前台应用兼容性最好的方案,支持Android 5.0到最新版本,仅需要用户手动授权使用情况访问权限即可:
- 在
AndroidManifest.xml中声明特殊权限:
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- 检测权限是否已授权的方法:
public static boolean checkUsagePermission(Context context) { AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); int mode; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { mode = appOps.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), context.getPackageName()); } else { mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), context.getPackageName()); } return mode == AppOpsManager.MODE_ALLOWED; }
- 权限未授权时,引导用户跳转到设置页开启:
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); intent.setData(Uri.parse("package:" + context.getPackageName())); context.startActivity(intent);
- 获取当前前台应用包名的实现:
public static String getCurrentForegroundPackage(Context context) { UsageStatsManager usm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); long now = System.currentTimeMillis(); // 查询最近10秒内的应用使用记录,保证数据时效性 List<UsageStats> stats = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, now - 10000, now); if (stats == null || stats.isEmpty()) return ""; UsageStats latestStats = null; for (UsageStats s : stats) { if (latestStats == null || s.getLastTimeUsed() > latestStats.getLastTimeUsed()) { latestStats = s; } } return latestStats != null ? latestStats.getPackageName() : ""; }
拿到前台包名后,你可以通过两种方式判断是否为屏保应用:
- 检查目标应用是否包含
BIND_DREAM_SERVICE权限,所有正规屏保应用都必须实现DreamService,会自动持有该权限 - 提前收集需要识别的屏保应用包名,做名单匹配
方案三:无障碍服务获取前台应用
如果你的应用本身已经在使用无障碍服务,可以直接通过无障碍事件回调获取前台应用包名,不需要额外申请使用情况访问权限:
@Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.getPackageName() != null) { String foregroundPkg = event.getPackageName().toString(); // 在此处判断是否为屏保应用即可 } }
该方案的缺点是需要用户手动开启应用的无障碍权限,用户接受度较低。
避坑提示
- Android 12(API 31)及以上版本对后台启动Activity做了严格限制,就算检测到屏保运行,也不能直接从后台拉起自身应用,必须满足系统规定的后台启动豁免条件(比如持有悬浮窗权限、用户刚刚点击过应用通知、应用为默认桌面/默认助手等),否则启动请求会被系统直接拦截。
- 不要高频轮询前台应用状态,检测间隔建议不低于15秒,否则会被系统判定为异常耗电,强制杀死后台服务。
- 后台服务建议使用前台服务实现,同时做好保活逻辑,避免长时间后台运行被系统回收。
内容的提问来源于stack exchange,提问作者Mohammed Asim
相关产品推荐
相关产品推荐

