从后台任务或服务确定当前前台应用程序

116

我希望有一款运行在后台的应用程序,可以知道任何内置应用程序(例如消息、联系人等)何时在运行。

我的问题如下:

  1. 我应该如何在后台运行我的应用程序。

  2. 我的后台应用程序如何知道当前正在前台运行的应用程序是什么。

希望有经验的人能提供帮助。


我认为你没有给出足够的解释来说明你想要做什么。你的后台应用程序想要做什么?它应该如何与用户进行交互?你为什么需要知道当前前台应用程序是什么?等等。 - Charles Duffy
http://codingaffairs.blogspot.com/2016/05/check-if-your-android-app-is-in.html - Developine
1
要检测前台应用程序,您可以使用 https://github.com/ricvalerio/foregroundappchecker。 - rvalerio
14个回答

105
关于“2.如何知道前台应用程序是什么”,请勿使用`getRunningAppProcesses()`方法,因为根据我的经验,这会返回各种系统垃圾,并且您将获得多个具有` RunningAppProcessInfo.IMPORTANCE_FOREGROUND` 的结果。相反,请改用`getRunningTasks()`方法。以下是我在服务中使用的代码,非常简单:
ActivityManager am = (ActivityManager) AppService.this.getSystemService(ACTIVITY_SERVICE);
// The first in the list of RunningTasks is always the foreground task.
RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);

就这样,你可以轻松访问前台应用程序/活动的详细信息:

String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();
PackageManager pm = AppService.this.getPackageManager();
PackageInfo foregroundAppPackageInfo = pm.getPackageInfo(foregroundTaskPackageName, 0);
String foregroundTaskAppName = foregroundAppPackageInfo.applicationInfo.loadLabel(pm).toString();

这需要在 activity menifest 中添加一个额外的权限,然后完美运行。

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

1
这应该被标记为正确答案。所需权限:android.permission.GET_TASKS 是其他可以避免它的方法中唯一非常微小的缺点。 - brandall
3
注意:上述方法仅用于调试和展示任务管理用户界面。我在Java文档中看到了这一点。因此,该方法可能会在未来发生变化而不另行通知。 - brandall
53
注意:getRunningTasks() 在API 21(棒棒糖)中已经被弃用- http://developer.android.com/reference/android/app/ActivityManager.html#getRunningTasks(int)。 - dtyler
2
有一种方法可以使用在21中引入的“使用统计API”。 - KunalK
在运行任务列表中,第一个始终是前台任务。但在Marshmallow中不是这样的。 - zihadrizkyef
显示剩余2条评论

38

我曾经不得不通过艰难的方式找出正确的解决方案。下面的代码是 CyanogenMod7 (平板电脑优化)的一部分,并且已在 Android 2.3.3 / Gingerbread 上进行了测试。

方法:

  • getForegroundApp - 返回前台应用程序。
  • getActivityForApp - 返回找到应用程序的活动。
  • isStillActive - 确定先前找到的应用程序是否仍然是活动应用程序。
  • isRunningService - getForegroundApp 的辅助函数

希望这可以完全回答这个问题(:

private RunningAppProcessInfo getForegroundApp() {
    RunningAppProcessInfo result=null, info=null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningAppProcessInfo> l = mActivityManager.getRunningAppProcesses();
    Iterator <RunningAppProcessInfo> i = l.iterator();
    while(i.hasNext()){
        info = i.next();
        if(info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                && !isRunningService(info.processName)){
            result=info;
            break;
        }
    }
    return result;
}

private ComponentName getActivityForApp(RunningAppProcessInfo target){
    ComponentName result=null;
    ActivityManager.RunningTaskInfo info;

    if(target==null)
        return null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <ActivityManager.RunningTaskInfo> l = mActivityManager.getRunningTasks(9999);
    Iterator <ActivityManager.RunningTaskInfo> i = l.iterator();

    while(i.hasNext()){
        info=i.next();
        if(info.baseActivity.getPackageName().equals(target.processName)){
            result=info.topActivity;
            break;
        }
    }

    return result;
}

private boolean isStillActive(RunningAppProcessInfo process, ComponentName activity)
{
    // activity can be null in cases, where one app starts another. for example, astro
    // starting rock player when a move file was clicked. we dont have an activity then,
    // but the package exits as soon as back is hit. so we can ignore the activity
    // in this case
    if(process==null)
        return false;

    RunningAppProcessInfo currentFg=getForegroundApp();
    ComponentName currentActivity=getActivityForApp(currentFg);

    if(currentFg!=null && currentFg.processName.equals(process.processName) &&
            (activity==null || currentActivity.compareTo(activity)==0))
        return true;

    Slog.i(TAG, "isStillActive returns false - CallerProcess: " + process.processName + " CurrentProcess: "
            + (currentFg==null ? "null" : currentFg.processName) + " CallerActivity:" + (activity==null ? "null" : activity.toString())
            + " CurrentActivity: " + (currentActivity==null ? "null" : currentActivity.toString()));
    return false;
}

private boolean isRunningService(String processname){
    if(processname==null || processname.isEmpty())
        return false;

    RunningServiceInfo service;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningServiceInfo> l = mActivityManager.getRunningServices(9999);
    Iterator <RunningServiceInfo> i = l.iterator();
    while(i.hasNext()){
        service = i.next();
        if(service.process.equals(processname))
            return true;
    }

    return false;
}

1
如果您不介意我问一下,为什么您需要这些方法来查看是否正在运行服务,是否仍处于活动状态以及获取活动,只是为了获取前台应用程序? - user631063
2
抱歉,但它无法工作。一些应用程序被过滤掉了,没有任何原因,我认为是当它们运行某些服务时发生的。所以 isRunningService 将它们踢出去了。 - seb
16
不幸的是,自Android L(API 20)以后,getRunningTasks()已被弃用。在L版本中,这个方法不再对第三方应用程序开放:由于文档为中心的最近应用列表的引入,该方法容易向调用者泄漏个人信息。为了向后兼容,它仍将返回其数据的一小部分:至少包括调用者自己的任务,可能还有一些其他任务,如主页,这些任务已知不敏感。 - Sam Lu
有人知道这种方法比仅仅执行mActivityManager.getRunningTasks(1).get(0).topActivity更好吗? - Sam

38

尝试以下代码:

ActivityManager activityManager = (ActivityManager) newContext.getSystemService( Context.ACTIVITY_SERVICE );
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
for(RunningAppProcessInfo appProcess : appProcesses){
    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
        Log.i("Foreground App", appProcess.processName);
    }
}

进程名称是正在前台运行的应用程序的包名。将其与您的应用程序的包名进行比较。如果它们相同,则表示您的应用程序正在前台运行。

希望这回答了你的问题。


7
从安卓 L 版本开始,这是唯一可行的解决方案,因为其他解决方案中使用的方法已经过时。 - androidGuy
4
然而,这在应用程序位于前台且屏幕处于锁定状态时无效。 - Krit
新上下文变量应该是什么? - Torbilicious
1
它需要像被接受的答案那样的额外权限吗? - wonsuc
2
这并不完全正确。进程的名称并不总是与相应的应用程序包名称相同。 - Sam

29
从棒棒糖版本开始,这个过程已经发生了改变。请找到以下代码,在此之前用户必须进入“设置”->“安全”->(向下滚动到底部)“具有使用权限的应用程序”->授予权限给我们的应用程序。

从棒棒糖版本开始,这个过程已经发生了改变。请参考以下代码,在此之前用户必须进入“设置”→“安全”→(向下滚动到底部)“具有使用权限的应用程序”→授予权限给我们的应用程序。

private void printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager usm = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);
        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(TAG, "Current App in foreground is: " + currentApp);
}

1
这个使用统计数据不能通过编程方式设置吗? - rubmz
@rubmz,你有解决这个问题的办法吗? - VVB
1
在我的搭载Android 5.1的Doogee手机上,没有“授予我们应用程序权限”的选项。 - djdance
这并不能产生100%准确的结果...但大部分时间都是正确的。 - CamHart
@CamHart,你还记得它产生不准确结果的具体情况吗?是queryUsageStats()路径还是getRunningAppProcesses()路径出了问题? - Sam
1
这不会显示最后一个前台应用程序,但可能是最后一个“使用”的方式 - 如果您离开一个活动并转到另一个活动,请从上方拉出启动器菜单并释放它,您将处于“错误”的活动中,直到您进入另一个活动。无论您是否滚动屏幕,它都会报告错误的活动,直到您启动另一个活动。 - Azurlake

10
为了确定前台应用程序,您可以使用https://github.com/ricvalerio/foregroundappchecker来检测前台应用程序。它使用不同的方法,具体取决于设备的Android版本。
至于服务,该存储库还提供了所需的代码。实际上,让Android Studio为您创建服务,然后在onCreate中添加使用appChecker的片段。但是,您需要请求权限。

完美!!已在Android 7上测试。 - Pratik Tank
非常感谢。这是唯一解决我们在应用通知方面遇到的问题的答案。当通知弹出时,所有其他答案都会返回发送通知的应用程序包名称。您的LollipopDetector解决了这个问题。一个提示:从API 29开始,UsageEvents.Event中的MOVE_TO_FOREGROUND已被弃用,因此从Android Q开始,应该使用ACTIVITY_RESUMED。 然后它将像魔法般工作! - Jorn Rigter

9

如果我们需要从自己的服务/后台线程中检查应用程序是否在前台,可以采用以下方式进行实现,这是我采用的方法,对我而言很有效:

public class TestApplication extends Application implements Application.ActivityLifecycleCallbacks {

    public static WeakReference<Activity> foregroundActivityRef = null;

    @Override
    public void onActivityStarted(Activity activity) {
        foregroundActivityRef = new WeakReference<>(activity);
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (foregroundActivityRef != null && foregroundActivityRef.get() == activity) {
            foregroundActivityRef = null;
        }
    }

    // IMPLEMENT OTHER CALLBACK METHODS
}

现在,要从其他类检查应用程序是否在前台,只需调用以下方法:
if(TestApplication.foregroundActivityRef!=null){
    // APP IS IN FOREGROUND!
    // We can also get the activity that is currently visible!
}

更新(由SHS指出):

不要忘记在您的Application类的onCreate方法中注册回调。

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}

2
这就是我过去两个小时一直在寻找的东西。谢谢! :) - waseefakhtar
2
我们需要在Application类(TestApplication)的Override方法“onCreate()”中调用“registerActivityLifecycleCallbacks(this);”。这将启动我们的应用程序类中的回调。 - SHS
@SarthakMittal,如果设备进入待机模式或锁定时,则会调用onActivityStopped()。根据您的代码,这将表明应用程序在后台运行。但实际上它是在前台运行的。 - Ayaz Alifov
如果手机处于待机状态或锁屏状态,我不认为它在前台运行,但这还取决于个人偏好。 - Sarthak Mittal

8
考虑到getRunningTasks()已经被弃用,而getRunningAppProcesses()不可靠,我决定结合StackOverflow中提到的两种方法。
   private boolean isAppInForeground(Context context)
    {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
        {
            ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
            ActivityManager.RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
            String foregroundTaskPackageName = foregroundTaskInfo.topActivity.getPackageName();

            return foregroundTaskPackageName.toLowerCase().equals(context.getPackageName().toLowerCase());
        }
        else
        {
            ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
            ActivityManager.getMyMemoryState(appProcessInfo);
            if (appProcessInfo.importance == IMPORTANCE_FOREGROUND || appProcessInfo.importance == IMPORTANCE_VISIBLE)
            {
                return true;
            }

            KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
            // App is foreground, but screen is locked, so show notification
            return km.inKeyguardRestrictedInputMode();
        }
    }

4
您需要提醒将已弃用的权限<uses-permission android:name="android.permission.GET_TASKS" />添加到清单文件中,否则应用程序会崩溃。 - usernotnull
2
android.permission.GET_TASKS - 目前在Play Store中被禁止。 - Duna

5

ActivityManager类是查看正在运行的进程的适当工具。

要在后台运行,通常需要使用Service


1
这对我有用。但它只给出了主菜单名称。也就是说,如果用户打开“设置” -> “蓝牙” -> “设备名称”屏幕,RunningAppProcessInfo将其称为“设置”。无法进一步深入。
ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE );
                PackageManager pm = context.getPackageManager();
                List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
                for(RunningAppProcessInfo appProcess : appProcesses) {              
                    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                        CharSequence c = pm.getApplicationLabel(pm.getApplicationInfo(appProcess.processName, PackageManager.GET_META_DATA));
                        Log.i("Foreground App", "package: " + appProcess.processName + " App: " + c.toString());
                    }               
                }

5
我认为这只有在您自己的应用程序在前台运行时才有效。当其他应用程序在前台时,它不会报告任何信息。 - Alice Van Der Land

1
一种简单的解决方案是使用LiveData。 创建一个单例LiveData变量。(可能在一个普通的Kotlin文件中)。
val foregroundHelper = MutableLiveData<Unit>()

从 Activity 或 Fragment 中观察:

foregroundHelper.observe(this, Observer {}) // for Activity
foregroundHelper.observe(viewLifecycleOwner, Observer {}) // for Fragments

现在从您的后台服务、广播接收器等开始:
val appIsVisibleToTheUser = foregroundHelper.hasActiveObservers()
// Now your logic goes.
if (!appIsVisibleToUser) {
   // App is in background
   // So In my case:
   // I'm showing Notification to the user for the error happened in Background Service.
}

感谢您。

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