如何从Service检测未知包名的已启动App并获取其名称?
嘿,我懂你要实现的需求了——后台跑着你的App,用户一打开别的App,不管包名熟不熟,你的Service都能立刻揪出它的名字,弹个Toast出来是吧?我给你俩靠谱的方案,都是Android平台上能用的路子:
方法一:使用官方推荐的UsageStatsManager(Android 5.0+)
这是Google官方认可的获取应用使用情况的API,相比旧的getRunningTasks更合规,不过需要申请特殊权限。
步骤1:配置权限
先在AndroidManifest.xml里加必要的权限:
<!-- 获取应用使用统计的系统保护权限 --> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" /> <!-- Android 11及以上版本,要获取未知包名需添加此权限 --> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
步骤2:引导用户开启权限
PACKAGE_USAGE_STATS不能直接动态申请,得跳转到系统设置让用户手动开启:
// 检查是否已获取权限 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; } // 跳转到设置页面引导用户开启权限 private void requestUsageStatsPermission(Context context) { Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }
步骤3:在Service中定时查询最近启动的App
用Handler定时查询最近的应用使用记录,示例如下:
private Handler mHandler = new Handler(); private Runnable mCheckRecentAppRunnable = new Runnable() { @Override public void run() { getRecentLaunchedApp(); // 每隔2秒查询一次,可根据需求调整频率 mHandler.postDelayed(this, 2000); } }; private void getRecentLaunchedApp() { UsageStatsManager usageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); long endTime = System.currentTimeMillis(); long startTime = endTime - 1000 * 10; // 查询最近10秒的使用记录 List<UsageStats> usageStatsList = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, startTime, endTime); if (usageStatsList != null && !usageStatsList.isEmpty()) { // 按最后使用时间排序,取最新的应用 Collections.sort(usageStatsList, (a, b) -> Long.compare(b.getLastTimeUsed(), a.getLastTimeUsed())); UsageStats recentStats = usageStatsList.get(0); String packageName = recentStats.getPackageName(); // 获取应用名称,找不到则显示包名 String appName = ""; try { PackageManager pm = getPackageManager(); ApplicationInfo info = pm.getApplicationInfo(packageName, 0); appName = pm.getApplicationLabel(info).toString(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); appName = packageName; } // 弹出Toast提示 Toast.makeText(getApplicationContext(), "用户启动了:" + appName, Toast.LENGTH_SHORT).show(); } } // 在Service启动时开启定时任务 @Override public int onStartCommand(Intent intent, int flags, int startId) { if (!hasUsageStatsPermission(this)) { requestUsageStatsPermission(this); } else { mHandler.post(mCheckRecentAppRunnable); } return START_STICKY; } // 销毁时移除回调避免内存泄漏 @Override public void onDestroy() { super.onDestroy(); mHandler.removeCallbacks(mCheckRecentAppRunnable); }
注意事项
- 定时频率别太高,避免增加耗电;
- Android 12+对后台Service限制严格,建议将Service设为前台Service,防止被系统杀死;
QUERY_ALL_PACKAGES权限在Google Play上架时需说明用途,否则可能被拒。
方法二:用AccessibilityService监听界面切换(更实时)
这个方法不需要QUERY_ALL_PACKAGES权限,能实时监听App启动/切换事件,但需要用户手动开启辅助功能,体验上多一步操作。
步骤1:自定义AccessibilityService
创建继承自AccessibilityService的类,监听窗口切换事件:
public class AppMonitorAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { // 监听窗口状态变化(App启动/切换的核心事件) if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { String packageName = event.getPackageName().toString(); // 获取应用名称 String appName = ""; try { PackageManager pm = getPackageManager(); ApplicationInfo info = pm.getApplicationInfo(packageName, 0); appName = pm.getApplicationLabel(info).toString(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); appName = packageName; } // 弹出Toast提示 Toast.makeText(getApplicationContext(), "用户启动了:" + appName, Toast.LENGTH_SHORT).show(); } } @Override public void onInterrupt() { // 服务被中断时的提示 Toast.makeText(this, "应用监听服务被中断", Toast.LENGTH_SHORT).show(); } @Override protected void onServiceConnected() { super.onServiceConnected(); // 配置监听的事件类型 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; setServiceInfo(info); } }
步骤2:在Manifest中注册Service
<service android:name=".AppMonitorAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <!-- 配置辅助服务参数 --> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>
步骤3:创建辅助服务配置文件
在res/xml目录下创建accessibility_service_config.xml:
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeWindowStateChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" />
步骤4:引导用户开启辅助功能
需要引导用户到系统设置开启你的辅助服务:
// 检查辅助服务是否已开启 private boolean isAccessibilityServiceEnabled(Context context) { int accessibilityEnabled = 0; final String service = context.getPackageName() + "/" + AppMonitorAccessibilityService.class.getName(); try { accessibilityEnabled = Settings.Secure.getInt( context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED); } catch (Settings.SettingNotFoundException e) { e.printStackTrace(); } TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':'); if (accessibilityEnabled == 1) { String settingValue = Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); if (settingValue != null) { splitter.setString(settingValue); while (splitter.hasNext()) { String serviceName = splitter.next(); if (serviceName.equalsIgnoreCase(service)) { return true; } } } } return false; } // 跳转到辅助功能设置页面 private void requestAccessibilityPermission(Context context) { Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }
注意事项
- 辅助功能需要用户手动开启,需在App内友好提示操作流程;
- 该方法监听更实时,无需定时查询,耗电更低;
- 部分定制ROM可能限制辅助服务后台运行,需测试适配。
内容的提问来源于stack exchange,提问作者user6763460
相关产品推荐
相关产品推荐

