在安卓L系统中,getRunningTasks无法正常工作。

52

在Android L中,谷歌已禁用了getRunningTasks方法。现在它只能返回自己应用的任务和主屏幕启动器,我不能再获取其他应用的任务了。 我们的应用需要该方法来确定当前顶部应用程序。 有没有其他方法可以做到这一点?

我在谷歌上搜索过,除了这个问题之外,没有更多关于此的话题: https://code.google.com/p/android-developer-preview/issues/detail?id=29


请查看我对这个问题的回答。asd sdffgfg - Gaston Ferrari
5个回答

45

最近我参与的一个项目中,需要检测某些应用程序何时启动。我的所有研究都指向了getRunningTasks方法,但这个方法从Lollipop开始就已经被弃用了。

不过,令我惊讶的是,我发现一些应用锁定应用程序在lollipop设备上仍然有效,所以他们必须想出了解决办法。因此,我深入挖掘了一下。以下是我的发现:

    1. 在早期的设备上,它们仍使用getRunningTasks
    1. 在L设备上,它们使用getRunningAppProcesses,该方法返回当前运行在设备上的进程列表。你可能会认为"这没有用"。每个processInfo都有一个属性叫做importance。当一个应用程序变成顶部活动时,它的processInfo importance也会更改为IMPORTANCE_FOREGROUND。因此,您可以过滤掉那些不在前台的进程。从每个ProcessInfo中,还可以询问它加载的软件包列表。然后,您可以检查该列表是否包含与试图"保护"的应用程序相同的软件包。

以下是检测默认日历应用程序启动的示例代码:

public class DetectCalendarLaunchRunnable implements Runnable {

@Override
public void run() {
  String[] activePackages;
  if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) {
    activePackages = getActivePackages();
  } else {
    activePackages = getActivePackagesCompat();
  }
  if (activePackages != null) {
    for (String activePackage : activePackages) {
      if (activePackage.equals("com.google.android.calendar")) {
        //Calendar app is launched, do something
      }
    }
  }
  mHandler.postDelayed(this, 1000);
}

String[] getActivePackagesCompat() {
  final List<ActivityManager.RunningTaskInfo> taskInfo = mActivityManager.getRunningTasks(1);
  final ComponentName componentName = taskInfo.get(0).topActivity;
  final String[] activePackages = new String[1];
  activePackages[0] = componentName.getPackageName();
  return activePackages;
}

String[] getActivePackages() {
  final Set<String> activePackages = new HashSet<String>();
  final List<ActivityManager.RunningAppProcessInfo> processInfos = mActivityManager.getRunningAppProcesses();
  for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) {
    if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
      activePackages.addAll(Arrays.asList(processInfo.pkgList));
    }
  }
  return activePackages.toArray(new String[activePackages.size()]);
}
}

注意:getRunningAppProcesses也是用于调试或“构建用户界面进程管理”的,不确定Google是否会像他们对getRunningTasks所做的那样关闭这个后门。

因此,现在无法获取topActivity。但是通过一点点的技巧,您可以实现类似的结果。


2
我不明白这怎么能给你最顶层的活动。它返回多个IMPORTANCE_FOREGROUND进程,因此无法知道哪一个是最顶部的。此外,它在每个进程中返回多个软件包,其中任何一个都可能处于顶部。那么,我是错过了什么吗? - user496854
1
它无法获取顶部的活动,但是如上所述的示例,您可以检测何时启动某个应用程序,这对于许多用例来说已经足够好了。 - sunxin8086
1
非常有帮助。将您和KNaito的答案结合起来,现在可以正常工作了。完美运行。 - Smeet
9
很抱歉,@sunxin8086,这种方法在Android M上已经无效了。am.getRunningAppProcesses()仅返回您的应用程序包。 - Lior Iluz
关于@LiorIluz所说的 - 这是Google对此问题的答案 - https://code.google.com/p/android-developer-preview/issues/detail?id=2347。简而言之,您现在需要real_get_tasks,只有系统应用程序才能获取。 - Ginandi
显示剩余2条评论

34

正如MKY所提到的,getRunningTasks()方法在Lollipop中不能用来获取当前应用程序。

正如sunxin8086所写的那样,获取运行中应用程序的一种方法是使用getRunningAppProcesses()方法。然而,info.importance == IMPORTANCE_FOREGROUND条件无法唯一确定当前应用程序。

更好的方法可能是检查RunningAppProcessInfo对象中的processState字段。虽然这个字段是隐藏的,但你可以在RunningAppProcessInfo类中看到它。如果该值为ActivityManager.PROCESS_STATE_TOP(也是一个隐藏的静态常量),则进程是当前的前台进程。

例如,代码如下:

final int PROCESS_STATE_TOP = 2;
ActivityManager.RunningAppProcessInfo currentInfo = null;
Field field = null;
try {
    field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
} catch (Exception ignored) {
}
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appList = am.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo app : appList) {
    if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 
            && app.importanceReasonCode == ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN) {
        Integer state = null;
        try {
            state = field.getInt(app);
        } catch (Exception e) {
        }
        if (state != null && state == PROCESS_STATE_TOP) {
            currentInfo = app;
            break;
        }
    }
}
return currentInfo;

注意:在Lolipop版本之前,processState字段不存在。请确保在运行上述代码之前检查Build.VERSION.SDK_INT >= 21。上述代码仅适用于Lollipop及以上版本。

另一种方法是由Gaston提出的(与此方法相差甚远),并且“当前应用程序”的含义与此方法略有不同。

请选择适合您目的的方法。


[编辑]

正如Sam所指出的,我将START_TASK_TO_FRONT修改为PROCESS_STATE_TOP。(两个值都为2)

[编辑2]

Sam有一个新发现!要唯一确定前台应用程序,还需要一个条件

process.importanceReasonCode == 0

是必要的。上面的代码已经更新。谢谢!


1
太好了!节省了很多时间。我也遇到了同样的问题。现在运行得非常顺畅。 - Smeet
3
我发现你的代码还会返回多个包:既包含了包含活动的进程,也包括由该进程使用的进程。一个例子是Firefox应用程序,它似乎使用Google GMS和YouTube进程。为了解决这个问题,我在if语句中加入了以下检查:process.importanceReasonCode == 0 - Sam
4
太好了,谢谢。需要指出的是,您应该使用app.pkgList [0]来获取当前运行的应用程序包,而不是app.processName。大多数情况下它们将是相同的,但有时不同,比如运行TuneIn Pro应用程序时。 - Lior Iluz
8
抱歉,@KNaito,这种方法在安卓M上不再可行。am.getRunningAppProcesses() 只会返回你的应用程序包。 - Lior Iluz
1
@KNaito 我已经开始了一个新的问题,并列出了所有当前的选项。希望社区能找到另一种方法。https://dev59.com/c10a5IYBdhLWcg3wLWGx - Lior Iluz
显示剩余13条评论

30

以下是获取 Android L/Lollipop 和 Android M/Marshmallow 设备上当前顶部活动的确切解决方案。

首先调用此行代码:(仅一次)

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

上述代码将打开一个名为“具有使用权限的应用程序”的屏幕。只需勾选单选按钮以打开/启用使用权限。 现在在您的服务或任何您想要的地方调用以下方法:

public void getTopActivtyFromLolipopOnwards() {
    String topPackageName;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
        long time = System.currentTimeMillis();
        // We get usage stats for the last 10 seconds
        List < UsageStats > stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 10, time);
        // Sort the stats by the last time used
        if (stats != null) {
            SortedMap < Long, UsageStats > mySortedMap = new TreeMap < Long, UsageStats > ();
            for (UsageStats usageStats: stats) {
                mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (mySortedMap != null && !mySortedMap.isEmpty()) {
                topPackageName = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
                Log.e("TopPackage Name", topPackageName);
            }
        }
    }
}

添加权限

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
     tools:ignore="ProtectedPermissions" />

这将返回当前正在运行的活动的包名,无论是Facebook还是WhatsApp。

该方法的唯一复杂之处在于您需要提示用户允许应用程序使用情况统计数据...即第一步。

希望这可以帮助每个人。


1
感谢使用“tools:ignore="ProtectedPermissions"”。 - jiashie
对于那些想要获取顶部活动名称的人来说,这将返回顶部活动的包名而不是顶部活动的名称。 - ajan
PACKAGE_USAGE_STATS 权限出现错误,'权限仅授予系统应用程序'。 - hoi
1
代码可以正常工作,但有没有办法过滤通知?也就是说,当通知出现时,此代码也会将通知显示为前台进程...我只想获取前台活动。 - Lins Louis
可以工作,但目前有没有一种方法可以在不提示用户的情况下完成相同的操作? - Alice Van Der Land

5
private String getProcess() throws Exception {
    if (Build.VERSION.SDK_INT >= 21) {
        return getProcessNew();
    } else {
        return getProcessOld();
    }
}

//API 21 and above
private String getProcessNew() throws Exception {
    String topPackageName = null;
    UsageStatsManager usage = (UsageStatsManager) context.getSystemService(Constant.USAGE_STATS_SERVICE);
    long time = System.currentTimeMillis();
    List<UsageStats> stats = usage.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - ONE_SECOND * 10, time);
    if (stats != null) {
        SortedMap<Long, UsageStats> runningTask = new TreeMap<Long,UsageStats>();
        for (UsageStats usageStats : stats) {
            runningTask.put(usageStats.getLastTimeUsed(), usageStats);
        }
        if (runningTask.isEmpty()) {
            return null;
        }
        topPackageName =  runningTask.get(runningTask.lastKey()).getPackageName();
    }
    return topPackageName;
}

//API below 21
@SuppressWarnings("deprecation")
private String getProcessOld() throws Exception {
    String topPackageName = null;
    ActivityManager activity = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> runningTask = activity.getRunningTasks(1);
    if (runningTask != null) {
        RunningTaskInfo taskTop = runningTask.get(0);
        ComponentName componentTop = taskTop.topActivity;
        topPackageName = componentTop.getPackageName();
    }
    return topPackageName;
}

//required permissions
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="android.permission.GET_TASKS"/>

请提供更多细节。 - tod
1
执行以下代码并调用getProcess()函数,即可获取当前可见进程。 - user4759293
1
Constant.USAGE_STATS_SERVICE 的值是什么?这段代码能在后台服务中运行吗? - maddy d
我相信你可以直接使用Context.USAGE_STATS_SERVICE,即:context.getSystemService(Context.USAGE_STATS_SERVICE); - behelit
无法工作。我已在运行Android 5.1.1的设备上进行了测试,runningTask.isEmpty()始终为true并返回null。 - Nagaraj Alagusundaram
在Android 10和Marshmallow上运行良好。我没有在清单中包含android.permission.GET_TASKS,因为它已被弃用。 - hullabaloon

3
我认为无法获取其他应用程序的任务,
这是文档所说的。
引入新的并发文档和活动任务功能后(请参见下面的最近屏幕中的并发文档和活动),ActivityManager.getRecentTasks()方法现已弃用,以提高用户隐私。为了向后兼容,该方法仍会返回其数据的一小部分,包括调用应用程序自己的任务和可能的其他非敏感任务(例如Home)。如果您的应用程序正在使用此方法检索自己的任务,请改用android.app.ActivityManager.getAppTasks()来检索该信息。
在此处查看Android L的API概述 https://developer.android.com/preview/api-overview.html#Behaviors

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接