【问题标题】:Android - How to detect when 3d party app is startingAndroid - 如何检测 3d 派对应用程序何时启动
【发布时间】:2015-12-08 19:27:12
【问题描述】:

我目前正在开发一款平板电脑应用程序,该应用程序将在商店中展示广告。我需要为设置应用程序添加密码保护。为此,我需要检测设置应用程序是在后台服务中启动的。对于具有低于 API 21 的 Android 设备,可以使用 getRunningTasks() 完成此操作。不幸的是,这种方法现在已被弃用。我尝试使用this answer 实现我的目标,但事情并没有按我的需要进行。我正在尝试收听V/WindowManager( 740): Adding window Window{1f4535ef u0 com.android.settings/com.android.settings.Settings} at 9 of 16 (before Window{e14eed2 u0 Starting com.android.settings}),但我无法将其视为输出。我正在使用上面引用的答案中的代码。我不是在烤它,而是在 logcat 中打印它。

您能告诉我如何实现 API 21 以上的 Android 版本的目标吗?我知道这是可能的,因为像 Smart AppLock 这样的应用程序可以做到。提前致谢!

【问题讨论】:

    标签: android password-protection android-5.1.1-lollipop


    【解决方案1】:

    目前无法在 Android 5.1.1+ 上获取前台应用,除非您被授予系统级权限android.permission.PACKAGE_USAGE_STATS。请注意,几家主要供应商已删除允许从其设备访问 UsageStats API 的系统活动。这意味着这些设备上的应用程序永远无法获得必要的权限。这可能会随着系统更新而改变。

    您提到Smart Lock (App Protector) 可以检测Android M 上的当前前台活动。第一次打开Smart Lock(或类似应用)时,系统会提示您授予PACKAGE_USAGE_STATS 权限。所以我们可以断定他们是靠UsageStatsManager来获取前台应用的。

    我已尝试找到一种解决方法(hack)以在没有系统级权限的情况下在 Android M 上获取前台应用程序。请阅读并在我的回答中试用我的脚本:Android 5.1.1 and above - getRunningAppProcesses() returns my application package only


    Logcat

    除非你有root权限,否则读取logcat获取前台应用是行不通的。读取 logcat,因为 Jelly Bean 仅返回来自您的应用程序的日志消息。


    有用的链接:

    How to check if "android.permission.PACKAGE_USAGE_STATS" permission is given?

    How to use UsageStatsManager?

    https://code.google.com/p/android-developer-preview/issues/detail?id=2347


    编辑:

    在挖掘了 Android 源代码后,我编写了以下代码来获取 Android M 上的前台应用程序,而无需任何权限。我只在运行最新开发者预览版的 Nexus 9 上对其进行了测试。请将代码复制粘贴到项目中的类中,调用静态方法getForegroundApp()进行测试:

    /** first app user */
    public static final int AID_APP = 10000;
    
    /** offset for uid ranges for each user */
    public static final int AID_USER = 100000;
    
    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");
    
          if (lines.length != 2) {
            continue;
          }
    
          String cpuSubsystem = lines[0];
          String cpuaccctSubsystem = lines[1];
    
          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.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();
    }
    

    【讨论】:

    • 感谢您的详细解答。我很欣赏你的工作。您提供的代码适用于 Android 5.1.1,但不幸的是并非适用于所有应用程序。 getGoregroundApp() 为设置应用程序返回 null。在 Nexus 5 上测试。
    • @Crash-ID 很有趣。如果你得到com.android.settings 的 pid 并检查文件 (/proc/[pid]) 是否存在,它会说它不存在,即使它确实存在。调查它...
    • @Crash-ID - 您是否可以评论 Jared Rummler 提出的解决方案不适用于哪些类型的应用程序? @贾里德/@Crash-ID。据我所知,它似乎工作得很好。是否保证适用于所有用户安装的应用程序?
    • @blueether 在执行 selinux 时它不适用于系统应用程序。您应该改用 UsageStatsManager。
    • @贾里德。感谢您的回复,以及出色的答案。自上一篇文章(3 月 20 日)以来运行了更多实验后,基于此方法的前台应用检测适用于三星 Galaxy S4、三星 Galaxy Note、华硕 Zenfone,而不适用于 HTC one 8、索尼 Experia(很抱歉也在其他相关帖子)。此外,我们只对用户安装的应用程序感兴趣 - 因此在我们的用例中不检测系统应用程序是可以的。
    猜你喜欢
    • 1970-01-01
    • 2013-10-18
    • 1970-01-01
    • 2015-05-14
    • 2019-06-15
    • 2012-05-12
    • 1970-01-01
    • 2022-12-05
    • 2011-08-29
    相关资源
    最近更新 更多