美文网首页安全
Android获取当前运行的App进程

Android获取当前运行的App进程

作者: 蓝灰_q | 来源:发表于2017-09-22 22:29 被阅读2037次

    三种方式概述及对比

    • getRunningTasks方法
      在Android5.0以前,系统允许我们通过ActivityManager的getRunningTasks()函数,直接获取当前运行的App。
      这种方法唯一的问题就是过时了,在5.0以上不能使用。
    • USAGE_STATS_SERVICE方法
      从Android5.0以后,系统为了安全起见,开始重重设限,要求我们通过用户手动授权的方式,获得USAGE_STATS_SERVICE的访问权限,才能读到数据。
      但是,这种方式有两个问题:
      1.要求用户手动授权,不能自动运行。
      2.在碎片化系统中表现不一,有的系统无法给出权限,例如Android电视就无法真正获取权限。
    • 系统proc文件夹方法
      原理:Android系统在管理进程时,通过low memory killer机制来定时找出oom_score评分高出阈值的进程,进行回收,那么反过来考虑,oom_score值最低的,且oom_adj值为0的进程(0为前台进程的adj值),就是很可能是当前的前台进程了。
      这种方式可以静默运行,但是也有两个问题:
      1.获取的其实是进程名,默认进程名为App的包名,但是开发者可以自定义进程名,所以你可能拿不到App的包名。
      2.获取的结果并不精准,因为评分最低的不一定是当前显示的App,还有可能是某个进程优先级很高的后台服务,我们可能需要维护一个黑名单,在代码中屏蔽掉这个名单上的所有后台服务。

    getRunningTasks

    在Android4.X的时代,获取当前运行的App非常简单:

    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    ComponentName cn = activityManager.getRunningTasks(1).get(0).topActivity;
    String pkg = cn.getPackageName();
    

    USAGE_STATS_SERVICE

    从Android 5.0LOLLIPOP起,为了提升安全性,系统禁用了getRunningTasks,改为通过USAGE_STATS_SERVICE来获取当前运行的App:

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                UsageStatsManager m = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
                if (m != null) {
                    long now = System.currentTimeMillis();
                    //获取60秒之内的应用数据
                    List<UsageStats> stats = m.queryUsageStats(UsageStatsManager.INTERVAL_BEST, now - 60 * 1000, now);
                    Log.i(TAG, "Running app number in last 60 seconds : " + stats.size());
                    String topActivity = "";
                    //取得最近运行的一个app,即当前运行的app
                    if ((stats != null) && (!stats.isEmpty())) {
                        int j = 0;
                        for (int i = 0; i < stats.size(); i++) {
                            if (stats.get(i).getLastTimeUsed() > stats.get(j).getLastTimeUsed()) {
                                j = i;
                            }
                            topActivity = stats.get(j).getPackageName();
                            Log.i(TAG, "top running app is : "+topActivity);
                        }
    
                    }
                    return stats.size();
                }
            }
    

    这需要用户手动授权,调起系统授权页面“有权查看使用情况的应用”


    系统页面授权

    代码如下:

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                if (!hasPermission()) {
                    startActivityForResult(
                            new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS),
                            MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS);
                }
            }
        }
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode == MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS) {
                if (!hasPermission()) {
                    Toast.makeText(this,"未能开启部分App权限!",Toast.LENGTH_SHORT).show();
                }
            }
        }
        //检测用户是否对本app开启了“Apps with usage access”权限
        private boolean hasPermission() {
            AppOpsManager appOps = (AppOpsManager)
                    getSystemService(Context.APP_OPS_SERVICE);
            int mode = 0;
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
                mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
                        android.os.Process.myUid(), getPackageName());
            }
            return mode == AppOpsManager.MODE_ALLOWED;
        }
    
        private static final int MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS = 1101;
    

    通过系统proc文件夹读取

    通过读取系统proc文件夹,找到所有进程列表,然后利用oom_adj文件,判断出当前正在使用的进程。
    代码如下:

        //5.0以上,没有usage state权限
        public static final int AID_APP = 10000;
        public static final int AID_USER = 100000;
        /**
         * 5.0以上,没有usage state权限
         * @return
         */
        public static String getForegroundApp() {
            File[] files = new File("/proc").listFiles();
            int lowestOomScore = Integer.MAX_VALUE;
            String foregroundProcess = null;
            for (File file : files) {
                if (!file.isDirectory()) {
                    continue;
                }
                int pid;
    
                try {
                    pid = Integer.parseInt(file.getName());
                } catch (NumberFormatException e) {
                    continue;
                }
    
                try {
                    String cgroup = read(String.format("/proc/%d/cgroup", pid));
                    String[] lines = cgroup.split("\n");
                    String cpuSubsystem;
                    String cpuaccctSubsystem;
    
                    if (lines.length == 2) {// 有的手机里cgroup包含2行或者3行,我们取cpu和cpuacct两行数据
                        cpuSubsystem = lines[0];
                        cpuaccctSubsystem = lines[1];
                    } else if (lines.length == 3) {
                        cpuSubsystem = lines[0];
                        cpuaccctSubsystem = lines[2];
                    } else {
                        continue;
                    }
    
                    if (!cpuaccctSubsystem.endsWith(Integer.toString(pid))) {
                        // not an application process
                        continue;
                    }
                    if (cpuSubsystem.endsWith("bg_non_interactive")) {
                        // background policy
                        continue;
                    }
    
                    String cmdline = read(String.format("/proc/%d/cmdline", pid));
                    //屏蔽掉你自己的其他后台进程
                    if(cmdline.contains("com.XXX.xxx")){
                        continue;
                    }
                    if (cmdline.contains("com.android.systemui")) {
                        continue;
                    }
                    int uid = Integer.parseInt(cpuaccctSubsystem.split(":")[2]
                            .split("/")[1].replace("uid_", ""));
                    if (uid >= 1000 && uid <= 1038) {
                        // system process
                        continue;
                    }
                    int appId = uid - AID_APP;
                    int userId = 0;
                    // loop until we get the correct user id.
                    // 100000 is the offset for each user.
    
                    while (appId > AID_USER) {
                        appId -= AID_USER;
                        userId++;
                    }
    
                    if (appId < 0) {
                        continue;
                    }
                    // u{user_id}_a{app_id} is used on API 17+ for multiple user
                    // account support.
                    // String uidName = String.format("u%d_a%d", userId, appId);
                    File oomScoreAdj = new File(String.format(
                            "/proc/%d/oom_score_adj", pid));
                    if (oomScoreAdj.canRead()) {
                        int oomAdj = Integer.parseInt(read(oomScoreAdj
                                .getAbsolutePath()));
                        if (oomAdj != 0) {
                            continue;
                        }
                    }
                    int oomscore = Integer.parseInt(read(String.format(
                            "/proc/%d/oom_score", pid)));
                    if (oomscore < lowestOomScore) {
                        lowestOomScore = oomscore;
                        foregroundProcess = cmdline;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return foregroundProcess;
    
        }
    
        private static String read(String path) throws IOException {
            StringBuilder output = new StringBuilder();
            BufferedReader reader = new BufferedReader(new FileReader(path));
            output.append(reader.readLine());
    
            for (String line = reader.readLine(); line != null; line = reader
                    .readLine()) {
                output.append('\n').append(line);
            }
            reader.close();
            return output.toString().trim();// 不调用trim(),包名后会带有乱码
        }
    

    引用

    android5.1+获取当前运行的app(Android5.1-也支持)

    相关文章

      网友评论

        本文标题:Android获取当前运行的App进程

        本文链接:https://www.haomeiwen.com/subject/buyfextx.html