如何检查活动是否在前台或可见后台?

126

我有一个基于计时器的启动屏幕。我的问题是在调用 finish() 方法之前,我需要检查下一个活动是否已经启动,因为系统会弹出一个对话框,我只想在用户选择对话框选项后才调用 finish() 方法。

我知道有很多问题是关于如何判断活动是否在前台运行,但我不确定这是否适用于位于活动顶部的对话框。

这是问题所在,红色表示我的活动在后台,而对话框在前台:

红色表示我的活动在后台,而对话框在前台

编辑: 我尝试过不使用 finish() 方法,但是这样我的活动会在应用程序栈中被保留,我要避免这种情况发生。


1
可能相关:https://dev59.com/OG855IYBdhLWcg3wUSgW - jarmod
为了澄清一下,您想要启动一个意图选择器,并在用户点击其中一个选项之后等待您的应用程序完成()吗?听起来您需要使用Intent.createChooser()和startActivityForResult(),然后在接收到结果后调用finish()。 - alanv
可能是检查Android应用程序是否在后台运行的重复问题 - Douglas Nassif Roma Junior
ProcessLifecycleOwner 是最新的解决方案。 - S.R
@AlexMisiulia 不,我会让投票说明一切——如果你的回答获得更多的赞同,我很乐意改变已接受的答案。 - Nick
@Nick,我明白你的观点。但问题是,已接受的答案存在缺陷,正如在评论中提到的那样,并且在某些情况下会工作不正确。更多的人会犯错并浪费时间。无论如何,这是你的选择) - Alex Misiulia
26个回答

199

这是推荐的正确解决方案:

正确的解决方案(感谢Dan、CommonsWare和NeTeInStEiN)是通过使用Activity.onPause,Activity.onResume等方法来自己跟踪应用程序的可见性。在一些其他类中存储“可见性”状态。好的选择是你自己实现的Application或Service(如果您想从服务中检查活动的可见性,还有一些其它的变化形式可供选择)。

示例 实现自定义Application类(注意isActivityVisible()静态方法):

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

在AndroidManifest.xml中注册你的应用程序类:

<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >
在项目中的每个Activity中添加onPause和onResume(如果您想要,您可以为您的Activities创建一个共同的祖先,但是如果您的Activity已经扩展自MapActivity/ListActivity等,则仍需要手动编写以下内容):

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

在您的finish()方法中,您想使用isActivityVisible()来检查活动是否可见。在那里,您还可以检查用户是否选择了选项。只有在满足这两个条件时才继续。

该来源还提到了两个错误的解决方案...所以要避免这样做。

来源:stackoverflow


38
这不是可靠的解决方法。你可能会遇到以下情况:恢复A,恢复B,暂停A。此时activityVisible的值是false,但应用程序仍然可见。也许你可以使用一个可见性计数器:在onResume()中执行visibleCounter ++,在onPause()中执行visibleCounter --。 - Joris Weimar
6
同意Joris Weimar的观点,这不是一个百分之百可靠的解决方案。一种情况是如果用户下拉通知面板,则不会调用onPauseonStoponResume事件。那么如果没有触发这些事件,你该怎么办呢?! - user4750643
1
事实上,其他答案也没有100%的可行性。 - user4750643
2
如果应用程序有多个Activity,则此方案将无法正常工作。请至少使用计数器进行替换。 - ruX
这在Android N的分屏模式下也不起作用。其中一个活动将被暂停,但仍然可见。 - David Berry
显示剩余3条评论

76

如果目标 API 级别为 14 或更高,则可以使用 android.app.Application.ActivityLifecycleCallbacks

public class MyApplication extends Application implements ActivityLifecycleCallbacks {
    private static boolean isInterestingActivityVisible;

    @Override
    public void onCreate() {
        super.onCreate();
    
        // Register to be notified of activity state changes
        registerActivityLifecycleCallbacks(this);
        // ...
    }

    public boolean isInterestingActivityVisible() {
        return isInterestingActivityVisible;
    }

    @Override
    public void onActivityResumed(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = true;
        }
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = false;
        }
    }

    // Other state change callback stubs
    // ...
}

21
我认为你可以在常规的生命周期回调函数(如onResume()、onStop())中完成这个操作。 - Daniel Wilson
5
我认为重点不在于建立一个已经存在的系统,而是做一些原来不存在的事情。在我看来,这应该成为被接受的答案。 - Jeffrey Blattman
这对于检查我们的任何活动是否开放非常有用。比在每个活动中编写代码并冒着遗漏某些内容的风险要好得多。可靠而简单。谢谢! - Tyler

72

UPD: 更新为 Lifecycle.State.RESUMED 状态。感谢 @htafoya

2019年,借助新的支持库28+或AndroidX,您可以轻松地使用以下代码:

val isActivityInForeground = activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
您可以在文档中阅读更多内容,以了解背后发生了什么。

6
也许更好的做法是将activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)或者STARTED放在这里,而不是使用INITIALIZED,因为INITIALIZED并不能保证应用在前台运行。 - htafoya
对于监听器,请使用以下代码:lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (event == Lifecycle.Event.ON_RESUME || event == Lifecycle.Event.ON_START || event == Lifecycle.Event.ON_CREATE) { Toast.makeText(this, "ON_RESUME", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "ON_PAUSE", Toast.LENGTH_SHORT).show() } } }) - Javad

14

Activity::hasWindowFocus() 返回你需要的布尔值。

public class ActivityForegroundChecker extends TimerTask
{
    private static final long FOREGROUND_CHECK_PERIOD = 5000;
    private static final long FIRST_DELAY             = 3000;

    private Activity m_activity;
    private Timer    m_timer;

    public ActivityForegroundChecker (Activity p_activity)
    {
        m_activity = p_activity;
    }

    @Override
    public void run()
    {
        if (m_activity.hasWindowFocus() == true) {
            // Activity is on foreground
            return;
        }
        // Activity is on background.
    }

    public void start ()
    {
        if (m_timer != null) {
            return;
        }
        m_timer = new Timer();
        m_timer.schedule(this, FIRST_DELAY, FOREGROUND_CHECK_PERIOD);
    }

    public void stop ()
    {
        if (m_timer == null) {
            return;
        }
        m_timer.cancel();
        m_timer.purge();
        m_timer = null;
    }
}

这是一个示例类,可以检查您的活动在任何地方的可见性。

请记住,如果您显示一个对话框,结果将为false,因为该对话框将具有主要焦点。除此之外,它非常方便,比建议的解决方案更可靠。


1
感谢您修改答案 @Burak Day,现在它确实成为了一个答案。 - Nick
这个不起作用,我宁愿在类中使用一个布尔属性,在OnResume中设置为true,在OnPause中设置为false。 - Chandler
@Chandler,你在这段代码中遇到了什么确切的问题?还有,你使用的是哪个版本? - Burak Day
@Chandler 另外,如果您无法访问活动生命周期方法怎么办?假设您只是从库中检查活动的可见性。 - Burak Day
这个答案的真正问题是它并不能正常工作。activity.hasWindowFocus 是 true 无法保证 Activity 处于 onResume 和 onPause 状态之间。我建议在该 Activity 中添加一个布尔类型的 isResumed 属性,手动设置其值并添加一个 getter 方法。 - Chandler
@Chandler onResule 在活动变为可见之前会稍微提前调用。 - Cactusroot

10

这正是活动的onPauseonStop事件之间的区别,如Activity类文档所述。

如果我理解你的意思正确,你想做的是从你的活动onStop中调用finish()来终止它。 请参见Activity生命周期演示应用程序的附加图像。当从活动A启动活动B时,它看起来就像这样。 事件的顺序是从下到上,因此您可以看到在已经调用了Activity B onResume之后,Activity A onStop被调用。

Activity lifecycle demo

如果显示对话框,则您的活动会在后台变暗,只调用onPause


9

两种可能的解决方案:

  1. 活动生命周期回调

使用实现了ActivityLifecycleCallbacks应用程序来跟踪您的应用程序中的活动生命周期事件。请注意,ActivityLifecycleCallbacks适用于Android api >= 14。对于早期的Android api,您需要在所有活动中自己实现它;-)

当您需要在活动之间共享/存储状态时,请使用应用程序

  1. 检查正在运行的进程信息

你可以使用这个类来检查正在运行的进程状态:RunningAppProcessInfo
使用ActivityManager.getRunningAppProcesses()获取正在运行的进程列表,并过滤结果列表以检查所需的RunningAppProcessInfo并检查其“重要性”。

4
我在github上创建了一个项目app-foreground-background-listen,它使用非常简单的逻辑,并且在所有android API级别上都可以正常工作。

3

我认为我有更好的解决方案。因为您可以通过一个扩展简单地将MyApplication.activityResumed(); 添加到每个Activity中。

首先,您需要创建(例如CyberneticTwerkGuruOrc)

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

接下来,您需要将Application类添加到AndroidManifest.xml中。
<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >

接着,创建一个名为ActivityBase的类

public class ActivityBase extends Activity {

    @Override
    protected void onPause() {
        super.onPause();
        MyApplication.activityPaused();
    }

    @Override
    protected void onResume() {
        super.onResume();
        MyApplication.activityResumed();
    }
}

最后,当您创建新的Activity时,您可以简单地通过继承ActivityBase而不是Activity来扩展它。
public class Main extends ActivityBase {
    @Override
    protected void onResume() {
        super.onResume();
    }

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

对我来说,这是一种更好的方法,因为你只需要记住通过ActivityBase进行扩展即可。此外,您还可以在未来扩展基本函数。在我的情况下,我在一个类中添加了我的服务接收器和网络警报。

如果您想检查应用程序的可见性,可以简单地调用

MyApplication.isActivityVisible()

如果我需要让我的活动(Activity)扩展(AppCombatActivity),该怎么办? - winklerrr

3

利用从后台暂停到恢复之间的时间差来判断应用是否从后台唤醒

在自定义应用中

private static boolean isInBackground;
private static boolean isAwakeFromBackground;
private static final int backgroundAllowance = 10000;

public static void activityPaused() {
    isInBackground = true;
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (isInBackground) {
                isAwakeFromBackground = true;
            }
        }
    }, backgroundAllowance);
    Log.v("activity status", "activityPaused");
}

public static void activityResumed() {
    isInBackground = false;
    if(isAwakeFromBackground){
        // do something when awake from background
        Log.v("activity status", "isAwakeFromBackground");
    }
    isAwakeFromBackground = false;
    Log.v("activity status", "activityResumed");
}

在 BaseActivity 类中。
@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

2

如果您想知道应用程序的任何活动是否可见于屏幕上,您可以像这样操作:

public class MyAppActivityCallbacks implements Application.ActivityLifecycleCallbacks {
private Set<Class<Activity>> visibleActivities = new HashSet<>();

@Override
public void onActivityResumed(Activity activity) {
    visibleActivities.add((Class<Activity>) activity.getClass());
}

@Override
public void onActivityStopped(Activity activity) {
     visibleActivities.remove(activity.getClass());
}

public boolean isAnyActivityVisible() {
    return !visibleActivities.isEmpty();
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

@Override
public void onActivityStarted(Activity activity) {}

@Override
public void onActivityPaused(Activity activity) {}

@Override
public void onActivityDestroyed(Activity activity) {}

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

只需创建此类的单例并在您的应用程序实例中设置它,如下所示:
class App extends Application{
     @Override
     public void onCreate() {
         registerActivityLifecycleCallbacks(myAppActivityCallbacks);
     }
}

然后你可以在任何地方使用你的MyAppActivityCallbacks实例的isAnyActivityVisible()方法!


我认为这是一个不错的解决方案,但为什么需要保留一组活动类?为什么不只使用计数器,在恢复/暂停时增加/减少它,然后检查是否等于0? - Raphael C

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