Android服务定时检测前台应用异常:调用getRunningTasks返回启动器包名
Hey there, let's break down why your current approach isn't working and fix it with the right tools for the job!
Why getRunningTasks() Fails
Starting with Android 5.0 (API Level 21), ActivityManager.getRunningTasks() was deprecated, and the system added strict permission restrictions. Regular apps can only retrieve task stack info for their own app, or get the launcher's package name when the launcher is in the foreground—that's exactly why you're seeing com.sec.android.app.launcher when switching to other apps. This method is now only useful for debugging or internal app task management, not for monitoring other apps' foreground status.
The Correct Solution: Use UsageStatsManager
To legally get the foreground app from the background, Android recommends using UsageStatsManager, which provides app usage statistics including the current foreground app. Note that this requires the user to manually grant the ACCESS_USAGE_STATS permission.
Step 1: Declare the Permission
Add this to your AndroidManifest.xml:
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
This is a protected permission—you can't request it via the standard requestPermissions() flow. You'll need to guide the user to the system settings to enable it.
Step 2: Guide Users to Enable the Permission
Check if you have the permission before starting your service (or when your app first launches), and redirect to settings if not:
private boolean hasUsageStatsPermission(Context context) { AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), context.getPackageName()); return mode == AppOpsManager.MODE_ALLOWED; } // Call this to open the permission settings page private void requestUsageStatsPermission(Context context) { Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }
Step 3: Fetch the Current Foreground App
Once you have the permission, use UsageStatsManager to get the foreground app's package name:
private String getForegroundAppPackageName(Context context) { UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); long currentTime = System.currentTimeMillis(); // Fetch usage stats from the last 10 seconds to get the latest foreground app List<UsageStats> usageStatsList = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, currentTime - 10000, currentTime); if (usageStatsList == null || usageStatsList.isEmpty()) { return null; } String foregroundPackageName = null; long lastActiveTime = 0; for (UsageStats stats : usageStatsList) { if (stats.getLastTimeUsed() > lastActiveTime) { lastActiveTime = stats.getLastTimeUsed(); foregroundPackageName = stats.getPackageName(); } } return foregroundPackageName; }
Step 4: Implement Periodic Detection in Your Service
To check every 5 seconds, use a Handler for the recurring task (for production, consider a static inner class + weak reference to avoid memory leaks—here's a simple working version):
public class ForegroundMonitorService extends Service { private Handler mDetectionHandler; private Runnable mDetectionTask; @Override public void onCreate() { super.onCreate(); mDetectionHandler = new Handler(); mDetectionTask = new Runnable() { @Override public void run() { String foregroundPackage = getForegroundAppPackageName(ForegroundMonitorService.this); if (foregroundPackage != null) { // Handle the detection result switch (foregroundPackage) { case "com.android.chrome": Log.d("ForegroundMonitor", "Chrome is active"); // Add your logic here (e.g., return the result) break; case "com.google.android.apps.maps": Log.d("ForegroundMonitor", "Google Maps is active"); // Add your logic here break; default: // Handle other apps if needed break; } } // Schedule next check in 5 seconds mDetectionHandler.postDelayed(this, 5000); } }; // Start the first check immediately mDetectionHandler.post(mDetectionTask); } @Override public void onDestroy() { super.onDestroy(); // Stop the recurring task to prevent memory leaks if (mDetectionHandler != null && mDetectionTask != null) { mDetectionHandler.removeCallbacks(mDetectionTask); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } // Include the hasUsageStatsPermission, requestUsageStatsPermission, and getForegroundAppPackageName methods here }
Key Notes
- Android 10+ Restrictions: From Android 10 (API 29), background apps have limited access to foreground app info.
UsageStatsManagermay have slight delays, or in some cases, return less accurate data. For higher precision (e.g., accessibility tools), consider usingAccessibilityServiceinstead. - Service Persistence: On Android 8.0+, regular background services are prone to being killed by the system. For long-running detection, use a
ForegroundService(which shows a persistent notification) or adjust your logic to useWorkManager(though it's not ideal for 5-second intervals). - Package Name Accuracy: Double-check package names: Chrome is
com.android.chrome, Google Maps iscom.google.android.apps.maps.
内容的提问来源于stack exchange,提问作者DSPNewbie




