Android权限探究——获取正在运行的应用/进程列表
Android5.0以前,可以通过ActivityManagerService.getRunningAppProcesses接口获取系统中正在运行的app进程信息。
但之后的Android版本,此接口只能获取到调用者自己的进程信息,这是为什么呢?本文将一探究竟。
1. ActivityManagerService源码分析
首先看一下Android 8.1 的ActivityManagerService源码。不难理解其逻辑:通过遍历mLruProcesses列表获取正在运行当进程信息,对进程进行过滤,构造RunningAppProcessInfo对象返回给调用者。而对进程信息过滤的过程,就是权限检测的过程。本文分析的重点就是理清权限检测的规则,以及在无法获取相应权限的情况下,如何获取running process。
public List getRunningAppProcesses() { enforceNotIsolatedCaller("getRunningAppProcesses"); final int callingUid = Binder.getCallingUid(); final int clientTargetSdk = mPackageManagerInt.getUidTargetSdkVersion(callingUid); // Lazy instantiation of list List runList = null; final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid) == PackageManager.PERMISSION_GRANTED; final int userId = UserHandle.getUserId(callingUid); final boolean allUids = isGetTasksAllowed( "getRunningAppProcesses", Binder.getCallingPid(), callingUid); synchronized (this) { // Iterate across all processes for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord app = mLruProcesses.get(i); if ((!allUsers && app.userId != userId) || (!allUids && app.uid != callingUid)) { continue; } if ((app.thread != null) && (!app.crashing && !app.notResponding)) { // Generate process state info for running application ActivityManager.RunningAppProcessInfo currApp = new ActivityManager.RunningAppProcessInfo(app.processName, app.pid, app.getPackageList()); fillInProcMemInfo(app, currApp, clientTargetSdk); if (app.adjSource instanceof ProcessRecord) { currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid; currApp.importanceReasonImportance = ActivityManager.RunningAppProcessInfo.procStateToImportance( app.adjSourceProcState); } else if (app.adjSource instanceof ActivityRecord) { ActivityRecord r = (ActivityRecord)app.adjSource; if (r.app != null) currApp.importanceReasonPid = r.app.pid; } if (app.adjTarget instanceof ComponentName) { currApp.importanceReasonComponent = (ComponentName)app.adjTarget; } //Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance // + " lru=" + currApp.lru); if (runList == null) { runList = new ArrayList<>(); } runList.add(currApp); } } } return runList; }
2. 权限探究
以上源码中,过滤进程信息依靠如下逻辑:
if ((!allUsers && app.userId != userId) || (!allUids && app.uid != callingUid)) { continue;}
只有当allUsers 为真,且allUids为真时,处理所有running process. 否则只处理调用者本身的进程信息。而allUsers 和allUids何时为真呢?
1). allUsers
allUsers的值通过调用ActivityManager.checkUidPermission接口,判断是否调用者是否具备INTERACT_ACROSS_USERS_FULL权限,源码如下:
ActivityManager.java
public static int checkUidPermission(String permission, int uid) { try { return AppGlobals.getPackageManager() .checkUidPermission(permission, uid); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
其最终调用的是PackageManagerService.checkUidPermission()接口。
public int checkUidPermission(String permName, int uid) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getUserId(callingUid); final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null; final boolean isUidInstantApp = getInstantAppPackageName(uid) != null; final int userId = UserHandle.getUserId(uid); if (!sUserManager.exists(userId)) { return PackageManager.PERMISSION_DENIED; } synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj != null) { if (obj instanceof SharedUserSetting) { if (isCallerInstantApp) { return PackageManager.PERMISSION_DENIED; } } else if (obj instanceof PackageSetting) { final PackageSetting ps = (PackageSetting) obj; if (filterAppAccessLPr(ps, callingUid, callingUserId)) { return PackageManager.PERMISSION_DENIED; } } final SettingBase settingBase = (SettingBase) obj; final PermissionsState permissionsState = settingBase.getPermissionsState(); if (permissionsState.hasPermission(permName, userId)) { if (isUidInstantApp) { BasePermission bp = mSettings.mPermissions.get(permName); if (bp != null && bp.isInstant()) { return PackageManager.PERMISSION_GRANTED; } } else { return PackageManager.PERMISSION_GRANTED; } } // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) { return PackageManager.PERMISSION_GRANTED; } } else { ArraySet perms = mSystemPermissions.get(uid); if (perms != null) { if (perms.contains(permName)) { return PackageManager.PERMISSION_GRANTED; } if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms .contains(Manifest.permission.ACCESS_FINE_LOCATION)) { return PackageManager.PERMISSION_GRANTED; } } } } return PackageManager.PERMISSION_DENIED; }
不难发现,有两种情况能够获取此权限:
1. 调用者为用户应用
当调用者是用户应用,首先通过getInstantAppPackageName接口判断其是否是Google 小程序,若是,则检查其base app 是否具有INTERACT_ACROSS_USERS_FULL权限。如果是普通应用,则直接检查其权限。
2. 调用者为系统应用
检查/frameworks/base/data/etc/platform.xml中是否定义了此权限,若有则返回成功。
2) allUids
allUids判断逻辑相对简单,通过isGetTasksAllowed方法判断。
当调用者具有REAL_GET_TASKS,返回成功。
当调用者具有GET_TASKS权限且为系统应用时,返回成功。不过GET_TASKS权限即将被Google抛弃,此处只是为兼容性加的temporary代码。
private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) { boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; if (!allowed) { if (checkPermission(android.Manifest.permission.GET_TASKS, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) { // Temporary compatibility: some existing apps on the system image may // still be requesting the old permission and not switched to the new // one; if so, we'll still allow them full access. This means we need // to see if they are holding the old permission and are a system app. try { if (AppGlobals.getPackageManager().isUidPrivileged(callingUid)) { allowed = true; if (DEBUG_TASKS) Slog.w(TAG, caller + ": caller " + callingUid + " is using old GET_TASKS but privileged; allowing"); } } catch (RemoteException e) { } } } if (!allowed) { if (DEBUG_TASKS) Slog.w(TAG, caller + ": caller " + callingUid + " does not hold REAL_GET_TASKS; limiting output"); } return allowed; }
而要获取REAL_GET_TASKS则需要系统签名。
至此,权限已经理清,开发者若想调用getRunningAppProcesses接口获取正在运行进程列表,必须将app集成为系统应用,或者具有系统签名。显而易见,对于大部分第三方开发者,这是不现实的,那么还有没有其他的方法呢?答案是肯定的,不过稍复杂些。
3. 获取正在运行process的其他方法
1)Android M
①Android 6.0系统可参考github : https://github.com/jaredrummler/AndroidProcesses 此方法不需要任何权限。
②可以使用UsageStatsManager,但某些系统APP返回同样的包名。
2)Android N
在更高版本的安卓系统上,可以使用AccessibilityService不过此方法违反Google Play规范,可能被踢出Google Play市场。
使用AccessibilityServices
- 通过AccessibilityService获取当前活跃窗口
- 在onAccessibilityEvent回调中,监测TYPE_WINDOW_STATE_CHANGED消息,来检查current window的改变。
- 通过packageManager.getActivityInfo()检查window是否是一个activity
优点
- 经测试 Android 2.2 (API 8) 到Android 7.1 (API 25)系统均可用
- 不需要轮询.
- 不需要REAL_GET_TASKS/ GET_TASKS权限.
缺点
- 每个用户都需要在辅助功能中打开服务.
- 服务会一直运行
- 当用户尝试打开AccessibilityService, 如果一个应用覆盖或者overlay屏幕,他们将不能点击确认按钮.
- 直到第一次activity改变,AccessibilityService才能检测到。
例子
service
public class WindowChangeDetectingService extends AccessibilityService { @Override protected void onServiceConnected() { super.onServiceConnected(); //Configure these here for compatibility with API 13 and below. AccessibilityServiceInfo config = new AccessibilityServiceInfo(); config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; if (Build.VERSION.SDK_INT >= 16) //Just in case this helps config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; setServiceInfo(config); } @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { if (event.getPackageName() != null && event.getClassName() != null) { ComponentName componentName = new ComponentName( event.getPackageName().toString(), event.getClassName().toString() ); ActivityInfo activityInfo = tryGetActivity(componentName); boolean isActivity = activityInfo != null; if (isActivity) Log.i("CurrentActivity", componentName.flattenToShortString()); } } } private ActivityInfo tryGetActivity(ComponentName componentName) { try { return getPackageManager().getActivityInfo(componentName, 0); } catch (PackageManager.NameNotFoundException e) { return null; } } @Override public void onInterrupt() {}}
AndroidManifest.xml
Service Info
在res/xml/accessibilityservice.xml中添加
<?xml version="1.0" encoding="utf-8"?>
3)Android O
目前没有其他方法,只能集成到system/app目录。
更多相关文章
- 【Android】获取设备型号、SDK版本及其系统版本
- Android在onCreate()方法中动态获取TextView控件的高度
- Arcgis Android(安卓)定位
- 如何获取android的相关信息,以及安装路径等信息的获取等
- Android(安卓)BroadcastReceiver 学习笔记
- uses-permission 与 android:maxSdkVersion
- android framework之旅(四)去除Usb权限弹窗
- Android常用代码之APK root权限静默安装
- Android(安卓)4.0的12大新特性