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


83

我正在由c2dm触发的android应用中执行状态栏通知。如果应用程序正在运行,我不想显示通知。您如何确定该应用程序是否正在运行并且在前台?



这是一个类似的问题……尽管我在onStart / onStop上尝试过一个标志,但是它没有用。我仍然没有得到停止/开始和暂停/恢复之间的区别。
Andrew Thomas

1
您应该使用以下解决方案:stackoverflow.com/questions/3667022/…–
Informatic0re

由于API 16有一种使用简单的方法ActivityManager.getMyMemoryState
约扎斯Kontvainis

从支持库版本26开始,您只需要在任何时候查询ProcessLifecycleOwner。检查stackoverflow.com/a/52678290/6600000
Keivan Esbati,

Answers:


55

作出这样一个全局变量private boolean mIsInForegroundMode;和分配false的价值onPause()true价值onResume()

样例代码:

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:是的。但是,无论何时发生更改,我们都可以自由地将此变量传递给首选项或数据库。
弗罗克莱2012年

19
@Shelly但是,在同一应用程序中切换活动时,您的变量会变得很正确/错误/正确。这意味着您必须具有滞后才能真正检测到您的应用失去前景的时间。
Radu 2012年

10
这不是最佳解决方案。检查下面的@Gadenkan解决方案。
菲利佩·利马

它适用于简单的场景,但是如果您的活动具有onActivityResult(),事情就会变得复杂。当且仅当我的应用程序的所有活动都在后台,并且上述解决方案不足时,我才想运行后台服务。
2015年

1
@Tima如果要使其对所有活动都可用,则可以创建一个新的MyOwnActivity扩展AppCompatActivity ...然后在MyOwnActivity中,覆盖onResume()/ onPause(),最后,您从MyOwnActivity扩展所有应用程序活动,而不是AppCompatActivity。问题解决了。; )
elliotching '17

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
    }

}

并且不要忘记GET_TASKSmanifest.xml文件中添加权限,以便能够运行getRunningTasks()上述代码中的方法:

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

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


24
如果您要使用此代码,请不要忘记添加<uses-permission android:name =“ android.permission.GET_TASKS” />
Lakshmanan 2012年

13
Nitpicks:调用toString()由返回的StringgetPackageName()是多余的。另外,由于我们仅对所返回的第一个任务感兴趣getRunningTasks(),因此我们可以传递1而不是Integer.MAX_VALUE
Jonik

14
注意:此方法仅用于调试和显示任务管理用户界面。绝对不要将其用于应用程序的核心逻辑,例如,根据此处找到的信息确定不同的行为。不支持此类用途,将来可能会中断。例如,如果多个应用程序可以同时主动运行,则出于控制流程的目的,此处关于数据含义的假设将是不正确的。
Informatic0re

5
不幸的是,自Android L(API 20)起不赞成使用getRunningTasks()。从L开始,此方法不再对第三方应用程序可用:以文档为中心的最新消息的引入意味着它可以将个人信息泄漏给呼叫者。为了向后兼容,它仍将返回其数据的一小部分:至少调用者自己的任务,以及可能还有一些其他不敏感的任务,例如home。
山姆·卢

2
据推测,如果仅显示呼叫者的任务,则第一个任务和最重要的活动将始终属于您,这意味着棒棒糖发布后将始终返回真正的内容。
Mike Holler 2015年

46

这是一篇很老的文章,但仍然很有用。以上接受的解决方案可能有效,但有误。正如Dianne Hackborn写道:

这些API并不是应用程序基于其UI流程的基础,而是用于诸如向用户显示正在运行的应用程序或任务管理器之类的事情。

是的,在内存中有一个列表保存这些事情。但是,它在另一个进程中处于关闭状态,由与您的线程分开运行的线程进行管理,而不是您可以依靠的东西(a)及时看到正确的决定,或者(b)在返回时保持一致的情况。加上关于“下一步”活动去的决定总是在切换发生的那一刻完成,直到直到那个确切的点(活动状态被短暂锁定以进行切换),我们才做出决定。真正知道接下来会发生什么。

并且这里的实现和全局行为不能保证将来保持不变。

正确的解决方案是实施:ActivityLifeCycleCallbacks

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


7
这是一个示例如何使用它baroqueworksdev.blogspot.com/2012/12/…–
JK

2
我认为这是最好的解决方案,将来可能不会中断。想知道为什么它没有得到足够的选票。
Rohan Kandwal

2
如果您编写示例代码,则将获得更多选票,因为某些人没有示例就跳过了答案……但这是最好的解决方法……
Saman Salehi

这与公认答案中使用的替代方法onPauseonResume方法有何不同?
玉县

24

正如Vinay所说的,最好的解决方案(支持更新的android版本,14+)可能是ActivityLifecycleCallbacksApplication类实现中使用的。

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

19

从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();
}

高效的解决方案。谢谢:)
萨耶杜尔·卡里姆(Mr. Sajedul Karim)

非常好的解决方案。不需要权限。
user3144836

太棒了!我正在使用“任务”列表,但需要“获取任务” ..真的很感谢!
hexagod

15

仅供参考,如果您使用Gadenkan解决方案(太好了!),请不要忘记添加

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

到清单。


9
在android Lollipop中,此权限已弃用
Mussa

14

稍微清理了Gadenkan解决方案的版本。将其放置在任何活动中,或者将其作为所有活动的基类。

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月)

请注意,API级别21getRunningTasks()弃用该功能

截至目前LOLLIPOP,该方法不再对第三方应用程序可用:以文档为中心的最新消息的引入意味着它可以将个人信息泄漏给呼叫者。为了向后兼容,它仍将返回其数据的一小部分:至少调用者自己的任务,以及可能还有一些其他不敏感的任务,例如home。

所以我之前写的更有意义:

在许多情况下,您可能会想出一个更好的解决方案。例如,在onPause()和中onResume()(可能在BaseActivity中)为您的所有Activity做某事。

(在我们的案例中,如果我们不在前台,我们不希望启动离线警报活动,因此在BaseActivity中,onPause()我们只是取消订阅RxJava,以Subscription监听“离线”信号。)


您可以张贴您使用过的RxJava代码吗?我想使用RxJava,但现在不想花很多时间来学习RxJava :(
MBH

@MBH:如果不花一些时间熟悉基本概念,使用RxJava可能不会非常有成果... :-)无论如何,这是一个小例子
约尼克

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许可。


3

从支持库版本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
这是正确的答案。其他许多
元素

对于AndroidX Java implementation 'androidx.lifecycle:lifecycle-process:2.2.0',请在项目gradle中使用。
乔纳森

1

根据各种答案和评论,以下是可以内联的版本,您可以将其添加到帮助器类中:

public static boolean isAppInForeground(Context context) {
  List<RunningTaskInfo> task =
      ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
          .getRunningTasks(1);
  if (task.isEmpty()) {
    return false;
  }
  return task
      .get(0)
      .topActivity
      .getPackageName()
      .equalsIgnoreCase(context.getPackageName());
}

如其他答案所述,您需要将以下权限添加到AndroidManifest.xml

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

在android Lollipop中,此权限已弃用
Mussa

1

我想补充一点,比在创建通知之前检查您的应用是否在后台,一种更安全的方法是分别禁用和启用广播接收器onPause()和onResume()。

此方法为您提供了对实际应用程序逻辑的更多控制,并且将来不太可能更改。

@Override
protected void onPause() {
    unregisterReceiver(mHandleMessageReceiver);
    super.onPause();
}

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

1

通过将活动映射到布尔值,我发现了一种更简单,准确的方法来检查应用程序处于前台还是后台。

此处检查完整要点


1

这是@ user2690455上面描述的简单解决方案的代码。虽然看起来有些冗长,但您会发现总体上它非常轻巧

就我而言,我们还使用AppCompatActivity,因此我必须有2个基类。

public class BaseActivity extends Activity {

    /**
     * Let field be set only in base class
     * All callers must use accessors,
     * and then it's not up to them to manage state.
     *
     * Making it static since ..
     * 1. It needs to be used across two base classes
     * 2. It's a singleton state in the app
     */
    private static boolean IS_APP_IN_BACKGROUND = false;

    @Override
    protected void onResume() {
        super.onResume();

        BaseActivity.onResumeAppTracking(this);

        BaseActivity.setAppInBackgroundFalse();
    }

    @Override
    protected void onStop() {
        super.onStop();

        BaseActivity.setAppInBackgroundTrue();
    }

    @Override
    protected void onPause() {
        super.onPause();

        BaseActivity.setAppInBackgroundFalse();
    }

    protected static void onResumeAppTracking(Activity activity) {

        if (BaseActivity.isAppInBackground()) {

            // do requirements for returning app to foreground
        }

    }

    protected static void setAppInBackgroundFalse() {

        IS_APP_IN_BACKGROUND = false;
    }

    protected static void setAppInBackgroundTrue() {

        IS_APP_IN_BACKGROUND = true;
    }

    protected static boolean isAppInBackground() {

        return IS_APP_IN_BACKGROUND;
    }
}

1

仅当您要在活动开始以及要检查应用程序在前台还是后台的位置执行某些操作时,此功能才有用。

除了使用活动管理器之外,您还可以通过代码完成一个简单的技巧。如果仔细观察活动周期,则两个活动之间以及从前台到背景的流程如下。假设A和B是两个活动。

从A到B的转换时:1.调用A的onPause()2.调用B的onResume()3.在完全恢复B时调用A的onStop()

当应用程序进入后台时:1.调用A的onPause()2.调用A的onStop()

您可以通过简单地将标记置于活动中来检测背景事件。

进行抽象活动并将其扩展到其他活动中,这样您就无需在需要背景事件的任何地方复制粘贴所有其他活动的代码。

在抽象活动中,创建标志isAppInBackground。

在onCreate()方法中:isAppInBackground = false;

在onPause()方法中:isAppInBackground = false;

在onStop()方法中:isAppInBackground = true;

您只需要检入onResume()是否isAppInBackground为true。n在检查标志后再次设置isAppInBackground = false

对于两个活动之间的过渡,因为第一个活动的onSTop()总是在第二个活动恢复后调用,所以标志永远不会为真,并且当应用程序处于后台时,活动的onStop()将在onPause之后立即调用,因此,当您稍后再打开应用。

尽管采用这种方法,还有另外一种情况。如果您的应用程序屏幕中的任何一个已经打开,并且您将手机置于空闲状态,则一段时间后,手机将进入睡眠模式,并且当您解锁手机时,将在后台事件中对其进行处理。


1

这是我使用的一种方法(和支持方法):

private boolean checkIfAppIsRunningInForeground() {
    ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
    for(ActivityManager.RunningAppProcessInfo appProcessInfo : activityManager.getRunningAppProcesses()) {
        if(appProcessInfo.processName.contains(this.getPackageName())) {
            return checkIfAppIsRunningInForegroundByAppImportance(appProcessInfo.importance);
        }
    }
    return false;
}

private boolean checkIfAppIsRunningInForegroundByAppImportance(int appImportance) {
    switch (appImportance) {
        //user is aware of app
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE:
            return true;
        //user is not aware of app
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE:
        default:
            return false;
    }
}

好像我不赞成两年前发布的解决方案。在Android中进行各种安全更新后,该方法将不再起作用。
Droid Chris

0

没有全局回调,但是对于每个活动,它都是onStop()。您不需要弄乱一个原子整数。只需具有一个已启动活动数的全局整数,则在每个活动中,在onStart()中将其递增,在onStop()中将其递减。

跟随这个


0
     public static boolean isAppRunning(Context context) {

 // check with the first task(task in the foreground)
 // in the returned list of tasks

   ActivityManager activityManager = (ActivityManager)
   context.getSystemService(Context.ACTIVITY_SERVICE);
 List<RunningTaskInfo> services =
 activityManager.getRunningTasks(Integer.MAX_VALUE);
     if
     (services.get(0).topActivity.getPackageName().toString().equalsIgnoreCase(context.getPackageName().toString()))
     {
     return true;
     }
     return false;
     }

getRunningTasks不再是一个选项-较新的安全模型使此操作几乎无用,并且已弃用。
Tony Maro,2016年

@AnaghHegde通过从系统中提取正在运行的活动列表,请参见Gadenkan的上述答案。
托尼·马洛

0

这里提到的先前方法不是最佳的。基于任务的方法需要一个可能不希望的权限,而“布尔”方法则易于同时进行修改。

我使用的方法以及在大多数情况下(我认为)效果很好的方法:

有一个“ MainApplication”类来跟踪AtomicInteger中的活动计数:

import android.app.Application;

import java.util.concurrent.atomic.AtomicInteger;

public class MainApplication extends Application {
    static class ActivityCounter {
        private static AtomicInteger ACTIVITY_COUNT = new AtomicInteger(0);

        public static boolean isAppActive() {
            return ACTIVITY_COUNT.get() > 0;
        }

        public static void activityStarted() {
            ACTIVITY_COUNT.incrementAndGet();
        }

        public static void activityStopped() {
            ACTIVITY_COUNT.decrementAndGet();
        }
    }
}

并创建其他活动将扩展的基本Activity类:

import android.app.Activity;
import android.support.annotation.CallSuper;

public class TestActivity extends Activity {
    @Override
    @CallSuper
    protected void onStart() {
        MainApplication.ActivityCounter.activityStarted();
        super.onStart();
    }

    @Override
    @CallSuper
    protected void onStop() {
        MainApplication.ActivityCounter.activityStopped();
        super.onStop();
    }
}

好的...但是考虑使用ACTIVITY_COUNT.decrementAndGet(); 在activityStopped()中。
克里斯·卡内罗(ChrisCarneiro)'17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.