Skip to content

Android(6): Get running app for Android 5.1

clarkehe edited this page Aug 8, 2016 · 5 revisions

最近项目有个检查app是否是活动的需求,在5.0之前获取的方法是:

    /**
     * 5.0之前获取活动APP
     */
    private static ComponentName getTopActivity(Context context) {
        ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        try {
            List<RunningTaskInfo> taskInfo = mActivityManager.getRunningTasks(1);
            RunningTaskInfo task;
            if (taskInfo != null && taskInfo.size() > 0) {
                task = taskInfo.get(0);
                if (task != null) {
                    return task.topActivity;
                }
            }
        } catch (Exception e) {
            LogUtil.e(TAG, e.getMessage(), e);
        }
        return null;
    }

从5.0开始,Google出于保持个人隐私需要,限制了getRunningTasks的功能,只能获取自己及其他认为不涉及个人隐私信息的app的task。

在5.0的系统上我们可以枚举所有app进程的方法来获取活动app。

    /**
     * 5.0 获取活动APP, 5.1 6.0 就不行了, 只能获取到自己
     */
    private static String getTopActivityPkgNameFor5(Context context) {
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appList = activityManager.getRunningAppProcesses();
        if (appList == null || appList.isEmpty()) {
            LogUtil.e(TAG, "appList is null");
            return null;
        }

        Field field;
        try {
            field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
        } catch (Exception e) {
            LogUtil.e(TAG, e.toString());
            return null;
        }

        ActivityManager.RunningAppProcessInfo currentInfo = null;
        final int START_TASK_TO_FRONT = 2;
        String pkgName = null;
        for (ActivityManager.RunningAppProcessInfo app : appList) {
            if (app == null) {
                LogUtil.w(TAG, "app is null");
                continue;
            }

            if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                Integer state;
                try {
                    state = field.getInt(app);
                } catch (Exception e) {
                    LogUtil.e(TAG, e.toString());
                    return null;
                }

                if (state == START_TASK_TO_FRONT) {
                    currentInfo = app;
                    break;
                }
            }
        }

        if (currentInfo != null) {
            pkgName = currentInfo.processName;
        }
        return pkgName;
    }

同样出于安全性考虑,从5.1开始,getRunningAppProcesses只能获取到自己的相关进程,不能获取到其他app的进程。实际项目中,在有些国产机上测试,5.0.1也获取不到其他app的进程了,这要根据获取的结果进行实际的判断。

针对5.1+获取当前活动app的方法,有两个方案:

第一个,访问使用统计信息。需要系统权限USAGE_STATS_SERVICE,不过可以申请。

    private  String getTopActivityPkgNameByStatsUsage(Context context){
        final long endTime = System.currentTimeMillis();
        final long beginTime = endTime - 10000;

        if (mUsageStatsManager == null) {
            mUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
        }

        String result = "";
        UsageEvents.Event event = new UsageEvents.Event();
        UsageEvents usageEvents = mUsageStatsManager.queryEvents(beginTime, endTime);
        while (usageEvents.hasNextEvent()) {
            usageEvents.getNextEvent(event);
            if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
                result = event.getPackageName();
            }
        }
        if (!android.text.TextUtils.isEmpty(result)) {
            return result;
        }
        return "";
    }
   /**
     跳转,让用户打开权限
    */
    @TargetApi(21)
    private void showPrivilegeDialog(){
        if (mActivity != null) {
            Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
            mActivity.startActivity(intent);
        }
    }

    /**
      判断权限是否已经打开
    */
    @TargetApi(19)
    private boolean isStatsUsagePrivilegeAllowed(Context context){
        AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        int mode = appOps.checkOpNoThrow("android:get_usage_stats", android.os.Process.myUid(), context.getPackageName());
        ALog.i(TAG, "isStatsUsagePrivilegeAllowed, mode:" + mode);
        return mode == AppOpsManager.MODE_ALLOWED;
    }

第二个,访问/proc/[pid]/oom_adj文件。android是基于linux的内核,app启动时,会创建一个主进程,进程的名字就是包名。每一个进程,系统会在/proc下创建一个子目录,进程id就是子目录的名称。每一个进程在/proc/[pid]子目录下,会有一些系统文件(文件夹),其中有一个文件叫oom_adj,这个文件记录了进程对应app的优先级。优化级决定系统在资源不足时,进行回收的顺序;oom_adj值越小,优化级越高,越不容易被回收。系统进程对应oom_adj值是负值,app进程对应oom_adj值>=0。根据android系统的规则,当oom_adj为0时,其对应app就是活动app,这个值会根据app的状态实时变化。因此要判断一个app是不是活动进程,根据其包名找到/proc/[pid]文件,然后读取oom_adj文件就好了。关于这个方案,Github上已经有一个开源的实现,可以参考下。

shell@hammerhead:/proc/173 $ ls -al oom_adj
-rw-r--r-- root     root            0 2016-08-08 18:59 oom_adj
shell@hammerhead:/proc/173 $ cat oom_adj
0
shell@hammerhead:/proc/173 $ 

Android获取栈顶程序 http://www.voidcn.com/blog/ballonge/article/p-5765200.html

Clone this wiki locally