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目录。

更多相关文章

  1. 【Android】获取设备型号、SDK版本及其系统版本
  2. Android在onCreate()方法中动态获取TextView控件的高度
  3. Arcgis Android(安卓)定位
  4. 如何获取android的相关信息,以及安装路径等信息的获取等
  5. Android(安卓)BroadcastReceiver 学习笔记
  6. uses-permission 与 android:maxSdkVersion
  7. android framework之旅(四)去除Usb权限弹窗
  8. Android常用代码之APK root权限静默安装
  9. Android(安卓)4.0的12大新特性

随机推荐

  1. android刮刮奖效果
  2. Android Layout Tricks #2: Reusing layo
  3. Android 程序启动界面Demo
  4. Android实现音乐的播放与停止(Service的初
  5. Android中Intent对应的category列表大全
  6. 垂直跑马灯
  7. 【Android】Android WebView 网页输入框
  8. react-native02:安卓返回键控制
  9. Android 调用系统相册选择图片并显示
  10. Android实现振动效果