如何判断Android应用程序是否在前台运行?

83

我正在我的Android应用程序中进行状态栏通知,该通知由C2DM触发。如果应用程序正在运行,我不想显示通知。如何确定应用程序是否正在运行并处于前台?


这是一个类似的问题...虽然我尝试在onStart/onStop上设置标志,但它没有起作用。我仍然不明白stop/start和pause/resume之间的区别。 - Andrew Thomas
1
你应该使用这个解决方案:https://dev59.com/KHA65IYBdhLWcg3wuhIR#5862048 - Informatic0re
自API 16以来,有一种更简单的方法,可以使用ActivityManager.getMyMemoryState - Juozas Kontvainis
自支持库版本26以来,您只需在需要时查询ProcessLifecycleOwner即可。请查看https://www.stackoverflow.com/a/52678290/6600000。 - Keivan Esbati
18个回答

112

或者,您可以通过ActivityManager检查正在运行的任务,方法是使用getRunningTasks方法。然后,在返回的任务列表中检查第一个任务(前台任务),是否为您自己的任务。
以下是代码示例:

public Notification buildNotification(String arg0, Map<String, String> arg1) {

    ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> services = activityManager
            .getRunningTasks(Integer.MAX_VALUE);
    boolean isActivityFound = false;

    if (services.get(0).topActivity.getPackageName().toString()
            .equalsIgnoreCase(appContext.getPackageName().toString())) {
        isActivityFound = true;
    }

    if (isActivityFound) {
        return null;
    } else {
        // write your code to build a notification.
        // return the notification you built here
    }

}

别忘了在manifest.xml文件中添加GET_TASKS权限,以便能够运行上述代码中的getRunningTasks()方法:

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

p / s:如果同意这种方式,请注意此权限现已弃用。


24
如果您想使用这段代码,请不要忘记添加以下内容:<uses-permission android:name="android.permission.GET_TASKS" /> - Lakshmanan
13
细节问题:在调用 getPackageName() 返回的字符串上调用 toString() 是多余的。另外,由于我们只关心 getRunningTasks() 返回的第一个任务,因此可以传递 1 而不是 Integer.MAX_VALUE - Jonik
14
注意:这种方法仅用于调试和展示任务管理用户界面。这不应该用于应用程序的核心逻辑,比如根据在这里找到的信息来决定不同行为之间的选择。这样的用法得不到支持,并且将来可能会出现问题。例如,如果多个应用程序可以同时运行,那么基于此处数据含义的控制流假设将是错误的。 - Informatic0re
5
很遗憾,getRunningTasks()自安卓L(API 20)起已被弃用。在安卓L及以上版本中,该方法不再对第三方应用程序开放:由于文档为中心的最近使用列表的引入,该方法可能会向调用者泄漏个人信息。为了向后兼容,该方法仍将返回其数据的一小部分:至少包括调用者自己的任务,以及可能包括一些其他已知不敏感的任务,例如home。 - Sam Lu
2
"activityManager.getRunningTasks"已经从Lollipop开始被弃用,不应该用于任何应用程序构建逻辑。这是由Google官方推荐的。那么现在应该使用什么呢?" - jitain sharma
显示剩余10条评论

55

创建一个全局变量,例如 private boolean mIsInForegroundMode; 并在 onPause() 中赋值为 false,在 onResume() 中赋值为 true

示例代码:

private boolean mIsInForegroundMode;

@Override
protected void onPause() {
    super.onPause();
    mIsInForegroundMode = false;
}

@Override
protected void onResume() {
    super.onResume();
    mIsInForegroundMode = true;
}

// Some function.
public boolean isInForeground() {
    return mIsInForegroundMode;
}

2
@MurVotema:是的,没错。但是我们可以自由地传递这个变量,例如在发生更改时传递到首选项或数据库中。 - Wroclai
19
但是当你在同一个应用程序中切换活动时,你的变量会频繁地从True变为False再变回True。这意味着你必须具有滞后来确切地检测到你的应用程序何时失去前台。 - Radu
10
这不是最佳解决方案。请参考@Gadenkan下面的解决方案。 - Felipe Lima
它适用于简单的情况,但如果您的活动中有onActivityResult(),事情就会变得复杂。我想在我的应用程序的所有活动都在后台运行时才运行后台服务,而上述解决方案不足以满足要求。 - Josh
1
@Tima 如果你想让它在所有活动中可用,你可以创建一个新的MyOwnActivity扩展AppCompatActivity... 然后在MyOwnActivity内部,覆盖onResume() / onPause(),最后你扩展所有应用程序活动从MyOwnActivity而不是AppCompatActivity。问题解决了。; ) - elliotching
显示剩余2条评论

46

这是一篇相当古老的文章,但仍然非常相关。上面被接受的解决方案可能有效,但是是错误的。正如Dianne Hackborn所写:

这些API并不是供应用程序基于其UI流程,而是像显示正在运行的应用程序或任务管理器等东西。

是的,有一个在内存中保存的列表。但是,它在另一个进程中,由与您分开运行的线程管理,并且不是您可以依靠的内容:(a)及时看到以做出正确的决策,或者(b)在返回时拥有一致的图像。此外,在切换发生的点处做出关于要“下一个”活动的决定,只有在确切的点(在其中活动状态短暂锁定以执行切换)才能知道下一步将是什么。

这里的实现和全局行为也不能保证未来会保持不变。

正确的解决方法是实现:ActivityLifeCycleCallbacks

这基本上需要一个应用程序类,并且可以在其中设置处理程序来识别应用程序中活动的状态。


7
以下是如何使用它的示例:http://baroqueworksdev.blogspot.com/2012/12/how-to-use-activitylifecyclecallbacks.html - Kaloyan Roussev
2
我认为这是最佳解决方案,很可能将来不会出现故障。不知道为什么它没有得到足够的投票。 - Rohan Kandwal
2
如果您编写示例代码,您将获得更多的投票,因为有些人会跳过没有示例的答案...但这是最好的解决方案... - Saman Salehi
这与已接受答案中使用的覆盖onPauseonResume方法有何不同? - Saitama

24

正如Vinay所说,支持新版本(android 14+)最好的解决方案是在Application类实现中使用ActivityLifecycleCallbacks

package com.telcel.contenedor.appdelegate;

import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;

/** Determines global app lifecycle states. 
 * 
 * The following is the reference of activities states:
 * 
 * The <b>visible</b> lifetime of an activity happens between a call to onStart()
 * until a corresponding call to onStop(). During this time the user can see the
 * activity on-screen, though it may not be in the foreground and interacting with 
 * the user. The onStart() and onStop() methods can be called multiple times, as 
 * the activity becomes visible and hidden to the user.
 * 
 * The <b>foreground</b> lifetime of an activity happens between a call to onResume()
 * until a corresponding call to onPause(). During this time the activity is in front
 * of all other activities and interacting with the user. An activity can frequently
 * go between the resumed and paused states -- for example when the device goes to
 * sleep, when an activity result is delivered, when a new intent is delivered -- 
 * so the code in these methods should be fairly lightweight. 
 * 
 * */
public class ApplicationLifecycleManager implements ActivityLifecycleCallbacks {

    /** Manages the state of opened vs closed activities, should be 0 or 1. 
     * It will be 2 if this value is checked between activity B onStart() and
     * activity A onStop().
     * It could be greater if the top activities are not fullscreen or have
     * transparent backgrounds.
     */
    private static int visibleActivityCount = 0;

    /** Manages the state of opened vs closed activities, should be 0 or 1
     * because only one can be in foreground at a time. It will be 2 if this 
     * value is checked between activity B onResume() and activity A onPause().
     */
    private static int foregroundActivityCount = 0;

    /** Returns true if app has foreground */
    public static boolean isAppInForeground(){
        return foregroundActivityCount > 0;
    }

    /** Returns true if any activity of app is visible (or device is sleep when
     * an activity was visible) */
    public static boolean isAppVisible(){
        return visibleActivityCount > 0;
    }

    public void onActivityCreated(Activity activity, Bundle bundle) {
    }

    public void onActivityDestroyed(Activity activity) {
    }

    public void onActivityResumed(Activity activity) {
        foregroundActivityCount ++;
    }

    public void onActivityPaused(Activity activity) {
        foregroundActivityCount --;
    }


    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    public void onActivityStarted(Activity activity) {
        visibleActivityCount ++;
    }

    public void onActivityStopped(Activity activity) {
        visibleActivityCount --;
    }
}

并且在应用程序的onCreate()方法中:

registerActivityLifecycleCallbacks(new ApplicationLifecycleManager());

然后使用ApplicationLifecycleManager.isAppVisible()ApplicationLifecycleManager.isAppInForeground()来了解所需的状态。


它只能与isAppVisible()一起使用。如果您关闭应用程序并将其发送到前台,则它将无效。 - AlwaysConfused

21

从 API 16 开始,您可以像这样操作:

static boolean shouldShowNotification(Context context) {
    RunningAppProcessInfo myProcess = new RunningAppProcessInfo();
    ActivityManager.getMyMemoryState(myProcess);
    if (myProcess.importance != RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
        return true;

    KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
    // app is in foreground, but if screen is locked show notification anyway
    return km.inKeyguardRestrictedInputMode();
}

高效的解决方案。谢谢 :) - Md. Sajedul Karim
非常好的解决方案。无需权限。 - user3144836
太棒了!我一直在使用任务列表,但它需要 GET TASK.. 非常感谢这个! - hexagod

15

顺便提一下,如果你使用Gadenkan解决方案(非常好用!!),不要忘记添加

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

到清单中。


9
在安卓5.0版中,该权限已被弃用。 - Mussa

14

这是稍微改进过的 Gadenkan 的方案。可以将其放在任何 Activity 中,或者作为所有 Activity 的基类。

protected boolean isRunningInForeground() {
    ActivityManager manager = 
         (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> tasks = manager.getRunningTasks(1);
    if (tasks.isEmpty()) {
        return false;
    }
    String topActivityName = tasks.get(0).topActivity.getPackageName();
    return topActivityName.equalsIgnoreCase(getPackageName());
}
要调用getRunningTasks(),您需要在AndroidManifest.xml中添加以下内容:
<uses-permission android:name="android.permission.GET_TASKS"/>

需要注意的是,ActivityManager.getRunningTasks() 的 Javadoc 中有明确说明:

注意:此方法仅用于调试和显示任务管理用户界面。这不应该用于应用程序中的核心逻辑,例如根据此处找到的信息决定不同行为。这样的用法不受支持,将来可能会出现问题。

更新(2015 年 2 月)

请注意,getRunningTasks() 已在 API 级别 21 中被弃用

LOLLIPOP 开始,此方法不再提供给第三方应用程序使用:介绍文档导向的最近列表意味着它可能会向调用者泄漏个人信息。为了向后兼容,它仍然会返回其数据的一小部分:至少包括调用者自己的任务,以及可能一些其他已知不敏感的任务,如主页。

因此,之前我写的内容变得更加相关:

在许多情况下,您可能可以想出更好的解决方案。例如,在所有活动中的 BaseActivity 中执行一些操作。可以在 onPause()onResume() 中完成,也可以使用 RxJava 中的 Subscription,例如,在 BaseActivity 的 onPause() 中取消订阅 "went offline" 信号的监听。


你能否请发布一下你所使用的RxJava代码?我想使用RxJava,但我现在不想花太多时间学习它 :( - MBH
@MBH:如果不花时间熟悉基本概念,使用RxJava可能不会很有成效... :-) 无论如何,这里有一个小例子 - Jonik

9

在回答Gadenkan后,我需要这样的东西,以便我可以知道我的应用程序是否没有在前台运行,但我需要一些适用于整个应用程序的东西,并且不需要我在整个应用程序中设置/取消标志。

Gadenkan的代码基本上解决了问题,但它不符合我的风格,感觉可以更简洁,所以在我的应用程序中,它被简化为这样。

if (!context.getPackageName().equalsIgnoreCase(((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0).topActivity.getPackageName()))
{
// App is not in the foreground
}

(顺便提一句:如果你想让检查反过来工作,只需删除!符号)

虽然通过这种方法,您需要GET_TASKS权限。


7

从支持库版本26开始,您可以使用ProcessLifecycleOwner来确定应用程序的当前状态,只需像这里描述的那样将其添加到您的依赖项中,例如:

dependencies {
    def lifecycle_version = "1.1.1"

    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData).
    //     Support library depends on this lightweight import
    implementation "android.arch.lifecycle:runtime:$lifecycle_version"
    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
}

现在,您可以随时查询ProcessLifecycleOwner来检查应用程序状态,例如,要检查应用程序是否在前台运行,只需执行以下操作:

 boolean isAppInForeground = ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
 if(!isAppInForeground)
    //Show Notification in status bar

1
这是正确的答案。许多其他答案应被视为已弃用。 - 11m0
对于 AndroidX Java,请在您的项目 gradle 中使用 implementation 'androidx.lifecycle:lifecycle-process:2.2.0' - Jon

1
我希望补充一下,比起在创建通知之前检查应用程序是否在后台运行的方法,更安全的方法是在onPause()和onResume()中分别禁用和启用广播接收器。
这种方法可以在实际应用逻辑中给您更多的控制,并且不太可能在将来发生更改。
@Override
protected void onPause() {
    unregisterReceiver(mHandleMessageReceiver);
    super.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION));
}

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