Android 5.1.1及以上版本 - getRunningAppProcesses()仅返回我的应用程序包

109

看起来谷歌最终关闭了获取当前前台应用程序包的所有途径。

在Lollipop更新后,getRunningTasks(int maxNum)已经失效,幸运的是有这个答案,我使用以下代码从Lollipop开始获取前台应用程序包:

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

从Android 5.1.1及以上版本(包括6.0 Marshmallow)开始,getRunningAppProcesses()已经废除了。现在它只会返回你自己应用程序的列表。


UsageStatsManager

我们可以使用新的UsageStatsManager API,如这里所述,但它并不适用于所有应用程序。一些系统应用程序将返回相同的包名。

com.google.android.googlequicksearchbox

AccessibilityService (2017年12月起被 Google 禁止使用)

一些应用程序使用AccessibilityService(如此处所示),但它具有一些缺点。


是否有另一种方法可以获取当前正在运行的应用程序包?


3
请注意,一些主要供应商已经从他们的设备中删除了授予访问 UsageStats API 的系统活动。这意味着这些设备上的应用程序永远无法获取必要的权限。 - Kevin Krumwiede
3
其中之一是三星,它占据了市场的60%以上。 - Kevin Krumwiede
3
这是一张来自 Android Bug 追踪器的票据,涉及此问题: https://code.google.com/p/android-developer-preview/issues/detail?id=2347 - Florian Barth
2
如果我在shell中运行ps命令并解析输出,策略为“fg”,且/proc/[pid]/oom_adj_score的内容等于0,则该应用程序是前台应用程序。不幸的是,在Android 6.0上似乎无法读取/proc/[pid]/oom_adj_score。https://gist.github.com/jaredrummler/7d1498485e584c8a120e - Jared Rummler
1
有人可以运行我刚才在下面回答中放置的脚本吗?谢谢。 - Jared Rummler
显示剩余13条评论
7个回答

96
要获取 Android 1.6 - Android 6.0 上运行中的进程列表,可以使用我编写的这个库:https://github.com/jaredrummler/AndroidProcesses。该库读取 /proc 来获取进程信息。
Google 在 Android Nougat 中大大限制了对 /proc 的访问。要在 Android Nougat 上获取运行中的进程列表,您需要使用 UsageStatsManager 或获取 root 权限。
单击编辑历史记录以查看以前的替代解决方案。

4
不,我只使用libsuperuser因为它提供了一种简单的方法来运行一个shell命令(非root或root)并获得输出。如果有足够的需求,我可以在没有libsuperuser的情况下重新编写它。 - Jared Rummler
1
@JaredRummler 我目前正在将你的解决方案实现到我的应用程序中。就我所知,它似乎运行良好。 - guy.gc
1
@androiddeveloper,如果你想根据不同的属性进行排序,可以让Process实现Comparable接口并重写compareTo方法。然后,当你使用Collections.sort(processesList)时,它将按照你指定的顺序进行排序。 - Srini
1
@JaredRummler,您能详细解释一下如果SELinux正在执行强制策略是什么意思吗? - Srini
2
@BruceWayne 我认为Jared指的是这个链接:https://source.android.com/devices/tech/security/selinux/index.html - Eduardo Herzer
显示剩余27条评论

30
 private String printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager usm = (UsageStatsManager)this.getSystemService("usagestats");
        long time = System.currentTimeMillis();
        List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,  time - 1000*1000, time);
        if (appList != null && appList.size() > 0) {
            SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
            for (UsageStats usageStats : appList) {
                mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (mySortedMap != null && !mySortedMap.isEmpty()) {
                currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
            }
        }
    } else {
        ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
        currentApp = tasks.get(0).processName;
    }

    Log.e("adapter", "Current App in foreground is: " + currentApp);
    return currentApp;
}

使用此方法获取前台任务。您将需要一个系统权限 "android:get_usage_stats"。

public static boolean needPermissionForBlocking(Context context){
    try {
        PackageManager packageManager = context.getPackageManager();
        ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
        AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
        return  (mode != AppOpsManager.MODE_ALLOWED);
    } catch (PackageManager.NameNotFoundException e) {
        return true;
    }
}

如果用户在设置中启用此选项 -> 安全 -> 具有使用权限的应用程序。之后,您将获得前台任务。类似的过程是由Cheetahamobile的Clean matser完成的。 谷歌Play链接


正如 OP 和评论中指出的那样,使用 UsageStatsManager 存在重大缺点。三星已经删除了请求,并且用户授予系统权限相当麻烦。 - Jared Rummler
我已在Moto E2设备上进行了测试。它运行良好,是的,我们需要实现这个权限。我们都面临着相同的问题。我们真的需要一个更好的方法。祝你好运,伙计。 - Tarun Sharma
2
这种方法至少在LG G3上不起作用,因为没有“设置->安全->具有使用访问权限的应用程序”菜单项。 - Dmitry
2
这不是我的问题的答案。此外,此答案中未提供任何新信息。 - Lior Iluz
1
工作正常,但在棉花糖系统中无法正常运行。如果有通知,则当前正在运行的进程将是接收到通知的应用程序包。实际上,该应用程序未在前台或后台运行 :(. 需要解决方案。 - WonderSoftwares
显示剩余6条评论

6

请查看https://github.com/ricvalerio/foregroundappchecker,这可能是您需要的东西。它提供示例代码,并且可以避免实现跨版本前台检测器的痛苦。

这里有两个示例:

AppChecker appChecker = new AppChecker();
String packageName = appChecker.getForegroundApp();

或者定期检查:

AppChecker appChecker = new AppChecker();
appChecker
    .when("com.other.app", new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .when("com.my.app", new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .other(new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .timeout(1000)
    .start(this);

3
谢谢,但是这里没有什么新东西。他只是封装了在OP中提到的UsageStatsManager API。用户需要像其他方法一样在Android 5.1.1及以上版本上启用访问权限。 - Lior Iluz
@Jérémy,你请求了所需的权限吗? - rvalerio

4

我想针对在Android M上检测最顶层应用程序的一大段复制粘贴代码提出一个潜在的优化方案。

这个

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    UsageStatsManager usm = (UsageStatsManager)this.getSystemService("usagestats");
    long time = System.currentTimeMillis();
    List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,  time - 1000*1000, time);
    if (appList != null && appList.size() > 0) {
        SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
        for (UsageStats usageStats : appList) {
            mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
        }
        if (mySortedMap != null && !mySortedMap.isEmpty()) {
            currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
        }
    }
}

可以简化为这样
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    UsageStatsManager usm = (UsageStatsManager) context.getSystemService(
        Context.USAGE_STATS_SERVICE);
    long time = System.currentTimeMillis();
    List<UsageStats> appStatsList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
            time - 1000 * 1000, time);
    if (appStatsList != null && !appStatsList.isEmpty()) {
        currentApp = Collections.max(appStatsList, (o1, o2) ->
            Long.compare(o1.getLastTimeUsed(), o2.getLastTimeUsed())).getPackageName();
    }
}

我发现自己在一个2秒的循环中使用了这段代码,并想知道为什么我要使用一个O(n*log(n))的复杂解决方案,而不是使用更简单的Collections.max()函数,后者的时间复杂度是O(n)。

4

谷歌只为系统应用程序限制了此功能。如错误报告中所述,您需要获得REAL_GET_TASKS权限才能访问这里。

现在,应用程序必须具有...permission.REAL_GET_TASKS权限才能获取所有应用程序的进程信息。如果应用程序没有该权限,则仅返回调用应用程序的进程信息。特权应用程序将暂时能够获取所有应用程序的进程信息,即使它们没有新权限,但已弃用...permission.GET_TASKS。此外,只有系统应用可以获取REAL_GET_TASKS权限。


我发现AppLock在5.1.1上仍可用,一旦该应用在安全性->“具有使用权限的应用程序”中获得了许可... 这有点令人惊讶。 - Mahesh Renduchintala
具有保护级别签名、特权或signatureOrSystem的权限仅授予系统应用程序。如果一个应用程序是普通的非系统应用程序,它将永远无法使用这些权限。在Android Studio中说...祝你与Google谈判成功,被接受为“系统应用程序”。 - Jérémy

0
public class AccessibilityDetectingService 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(final AccessibilityEvent event) {
        if (event == null ) {
            return;
        } else if(event.getPackageName() == null && event.getClassName() == null){
            return;
        }

            if (activityInfo != null){

                Log.d("CurrentActivity", componentName.flattenToShortString());
        }

}

private ActivityInfo tryGetActivity(ComponentName componentName) {
    try {
        return getPackageManager().getActivityInfo(componentName, 0);
    } catch (PackageManager.NameNotFoundException e) {
        return null;
    }
}
@Override
public void onInterrupt() {
}                
}
}//`enter code here`uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<uses-permission android:name="android.permission.GET_TASKS" />

然后在您的设备设置中启动服务和应用程序可访问性 -> 辅助功能 -> 应用程序,开启该服务。

这个问题已经有答案了,而且你只是从StackOverflow上的原始源代码中复制粘贴了它。 - Sam

-4
请尝试使用getRunningServices()方法,而不是getRunningAppProcesses()方法。
 ActivityManager mActivityManager = (ActivityManager) getSy stemService(Context.ACTIVITY_SERVICE);

 List<ActivityManager.RunningServiceInfo> appProcessInfoList = mActivityManager.getRunningServices(Integer.MAX_VALUE);

3
欢迎来到 Stack Overflow!我认为这可能行不通,因为前台应用程序可能没有运行服务。 - Sam

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