如何检测安卓应用程序何时进入后台并返回前台

450

我正在尝试编写一个应用程序,在一段时间后恢复到前台时会执行特定操作。是否有一种方法可以检测应用程序何时被发送到后台或恢复到前台?


3
可以在问题中添加一个使用案例,因为它似乎不太明显,所以答案中没有涉及到。该应用程序可以启动另一个应用程序(例如相册),该应用程序仍将驻留在同一堆栈中并显示为应用程序的一个屏幕,然后按Home按钮。所有依赖于应用程序生命周期(甚至是内存管理)的方法都无法检测到这一点。它们将在外部活动出现时触发后台状态,而不是在按Home键时触发。 - Dennis K
这就是你要找的答案:https://dev59.com/OG855IYBdhLWcg3wUSgW#42679191 - Fred Porciúncula
1
请参考Google解决方案:https://dev59.com/KHA65IYBdhLWcg3wuhIR#48767617 - StepanM
45个回答

231

2018: Android通过生命周期组件原生支持此功能。

2018年3月更新:现在有更好的解决方案。请参见ProcessLifecycleOwner。您需要使用新的架构组件1.1.0(目前最新版本),但它是专门为此设计的。

这个答案中提供了一个简单的示例in this answer,但我也写了一个sample app和一个blog post

自从我在2014年写下这篇文章以来,不同的解决方案出现了。有些有效,有些被认为有效,但存在缺陷(包括我的!),我们作为一个社区(Android)学会了应对后果,并为特殊情况编写了解决方法。

不要假设一小段代码就是你正在寻找的解决方案,这很不可能;最好的方法是尝试理解它的作用和原因。

MemoryBoss 类在我这里实际上从未被使用,它只是一段假代码,碰巧能够工作。

除非你有充分的理由不使用新的架构组件(确实有一些,特别是如果你针对超老的API),否则请使用它们。它们远非完美,但ComponentCallbacks2也不是。

更新/注释(2015年11月):一些人提出了两个评论,首先是应该使用>=而不是==,因为文档指出你不应检查确切的值。这对大多数情况来说都是可以的,但要记住,如果你只关心在应用程序进入后台时执行某些操作,你将不得不使用==并且还结合另一个解决方案(如Activity生命周期回调),否则你可能无法获得所需的效果。例如(我也遇到过这种情况),如果你想在应用程序进入后台时使用密码屏幕锁定你的应用程序(如果你熟悉1Password),如果你的内存不足,并突然测试>= TRIM_MEMORY,你可能会意外地锁定你的应用程序,因为Android会触发一个LOW MEMORY调用,而这比你的更高。所以要小心你测试的方式和内容。
此外,一些人问如何检测何时返回。
以下是我认为最简单的方法,但由于有些人不熟悉它,所以我在此添加一些伪代码。假设您有 YourApplication MemoryBoss 类,在您的 class BaseActivity extends Activity (如果没有,请创建一个)中。
@Override
protected void onStart() {
    super.onStart();

    if (mApplication.wasInBackground()) {
        // HERE YOU CALL THE CODE YOU WANT TO HAPPEN ONLY ONCE WHEN YOUR APP WAS RESUMED FROM BACKGROUND
        mApplication.setWasInBackground(false);
    }
}

我建议使用onStart,因为对话框可能会暂停活动,所以如果你只是显示全屏对话框,你不希望你的应用程序认为“它进入了后台”,但你的结果可能有所不同。
就这些。即使你转到另一个活动,if块中的代码也只会被执行一次,新活动(也扩展BaseActivity)将报告wasInBackground为false,因此不会执行代码,直到调用onMemoryTrimmed并再次将标志设置为true。
更新/注释(2015年4月):在复制和粘贴此代码之前,请注意我发现了几个可能不是100%可靠的实例,并且必须与其他方法结合使用才能实现最佳结果。特别是,已知有两个实例,在这些实例中,不能保证调用onTrimMemory回调:
如果您的手机在应用程序可见时锁定屏幕(例如,设备在 nn 分钟后锁定),则此回调不会被调用(或者不总是被调用),因为锁屏只是在顶部,但您的应用程序仍然“运行”,尽管被覆盖。
如果您的设备内存相对较低(并且处于内存压力之下),操作系统似乎会忽略此调用并直接转到更关键的级别。
现在,根据您知道应用程序何时进入后台的重要性,您可能需要扩展此解决方案,并跟踪活动生命周期等等。
请记住上述内容,并拥有一个良好的 QA 团队 ;)
更新结束
虽然可能有些晚,但在 Ice Cream Sandwich (API 14) 及以上版本中有一种可靠的方法。
原来当你的应用没有更多可见的用户界面时,会触发一个回调。这个回调可以在自定义类中实现,称为ComponentCallbacks2(是的,带有数字 2)。这个回调仅在 API Level 14(冰淇淋三明治)及以上版本中才能使用。
基本上,你会收到一个调用该方法的请求:
public abstract void onTrimMemory (int level)

水平为20或更具体。
public static final int TRIM_MEMORY_UI_HIDDEN

我一直在测试这个功能,它总是有效的,因为等级20只是一个"建议",你可能希望释放一些资源,因为你的应用程序不再可见。
引用官方文档:
内存修剪等级(onTrimMemory(int)):进程曾经显示了用户界面,现在不再这样做。应该在此时释放与 UI 相关的大型分配,以便更好地管理内存。
当然,你应该实现它来真正执行它所说的操作(清除一些长时间未使用的内存、清除一些长时间未使用的集合等)。可能性是无限的(请参阅官方文档以获取其他可能的更严重的等级)。
但有趣的是,操作系统告诉你:嘿,你的应用程序到了后台!
这正是你想要知道的。
你如何确定什么时候回来?
好吧,那很简单,我相信你有一个“BaseActivity”,所以你可以使用 onResume() 来标记你回来的事实。因为唯一会说你没回来的时候就是你实际上接收到上述 onTrimMemory 方法的调用时。

它起作用。您不会得到错误的阳性反应。如果活动正在恢复,则每次都会回到100%。如果用户再次返回,则会收到另一个onTrimMemory()调用。

您需要订阅您的活动(或更好的是,自定义类)。

保证始终收到此信息的最简单方法是创建一个简单的类,如下所示:

public class MemoryBoss implements ComponentCallbacks2 {
    @Override
    public void onConfigurationChanged(final Configuration newConfig) {
    }

    @Override
    public void onLowMemory() {
    }

    @Override
    public void onTrimMemory(final int level) {
        if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // We're in the Background
        }
        // you might as well implement some memory cleanup here and be a nice Android dev.
    }
}

为了使用它,在您的应用程序实现中(您有一个,对吧?),请执行以下操作:

MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
   super.onCreate();
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      mMemoryBoss = new MemoryBoss();
      registerComponentCallbacks(mMemoryBoss);
   } 
}

如果你创建了一个接口Interface,你可以在那个if语句中添加一个else,并实现ComponentCallbacks(不带2)用于API 14以下的任何内容。该回调仅具有onLowMemory()方法,并且在进入后台时不会被调用,但您应该使用它来减少内存占用。
现在启动您的应用程序并按Home键。您的onTrimMemory(final int level)方法应该被调用(提示:添加日志记录)。
最后一步是取消注册回调。可能最好的地方是您的应用程序的onTerminate()方法,但是,该方法在真实设备上不会被调用。
/**
 * This method is for use in emulated process environments.  It will
 * never be called on a production Android device, where processes are
 * removed by simply killing them; no user code (including this callback)
 * is executed when doing so.
 */

所以,除非你真的有不想再注册的情况,否则可以安全地忽略它,因为你的进程在操作系统级别已经停止运行了。

如果你决定在某个时候注销(例如,为你的应用程序提供关闭机制以清理和退出),你可以执行以下操作:

unregisterComponentCallbacks(mMemoryBoss);

就是这样。


该服务没有用户界面,因此可能与此有关。在您的基本活动中进行检查,而不是在服务中进行检查。您想知道何时隐藏了UI(并可能告诉服务,以便它转为前台)。 - Martin Marconcini
1
当你关闭手机时,它不起作用。它没有被触发。 - Juangcg
3
使用ComponentCallbacks2.onTrimMemory()(结合ActivityLifecycleCallbacks)是我迄今为止找到的唯一可靠的解决方案,感谢Martin!对于那些感兴趣的人,请查看我提供的答案。 - rickul
3
自从一年前开始我就一直使用这种方法了,它对我来说始终很可靠。知道其他人也在使用它感觉很好。我只是使用level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,这避免了你更新的问题中的第二点。关于第一点,对我来说并不是一个问题,因为应用程序实际上并没有到后台,所以这就是它应该工作的方式。 - sorianiv
1
我不喜欢这个答案的唯一一点是我不能双倍点赞,这应该是被采纳的答案! - desgraci
显示剩余26条评论

178

以下是我解决此问题的方法。其基本原理是,使用活动转换之间的时间参考将很可能提供足够的证据来判断应用程序是否已“进入后台”。

首先,我使用了一个android.app.Application实例(称为MyApplication),它具有一个计时器、一个计时器任务、一个常数来表示从一个活动到另一个活动的转换最长可能需要的毫秒数(我选择了2秒),以及一个布尔值来指示应用程序是否处于“后台”状态:

public class MyApplication extends Application {

    private Timer mActivityTransitionTimer;
    private TimerTask mActivityTransitionTimerTask;
    public boolean wasInBackground;
    private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
    ...

该应用程序还提供了两种启动和停止计时器/任务的方法:

public void startActivityTransitionTimer() {
    this.mActivityTransitionTimer = new Timer();
    this.mActivityTransitionTimerTask = new TimerTask() {
        public void run() {
            MyApplication.this.wasInBackground = true;
        }
    };

    this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask,
                                           MAX_ACTIVITY_TRANSITION_TIME_MS);
}

public void stopActivityTransitionTimer() {
    if (this.mActivityTransitionTimerTask != null) {
        this.mActivityTransitionTimerTask.cancel();
    }

    if (this.mActivityTransitionTimer != null) {
        this.mActivityTransitionTimer.cancel();
    }

    this.wasInBackground = false;
}

这个解决方案的最后一步是在所有活动的onResume()和onPause()事件中添加对这些方法的调用,或者更好的是,在所有具体活动继承的基础活动中添加:

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

    MyApplication myApp = (MyApplication)this.getApplication();
    if (myApp.wasInBackground)
    {
        //Do specific came-here-from-background code
    }

    myApp.stopActivityTransitionTimer();
}

@Override
public void onPause()
{
    super.onPause();
    ((MyApplication)this.getApplication()).startActivityTransitionTimer();
}

因此,在用户仅在您的应用程序活动之间导航的情况下,离开活动的onPause()会启动定时器,但几乎立即进入的新活动会在其达到最大转换时间之前取消定时器。因此,wasInBackground 将为false

另一方面,当Activity从Launcher、设备唤醒、结束电话等后台事件中返回前台时,很可能定时器任务已经执行,因此 wasInBackground 被设置为 true


4
嗨,d60402,你的回答非常有帮助。非常感谢你的回复。小提示:为了避免应用程序崩溃,MyApplication 应在清单文件中的应用程序标签中进行提及,如 android:name="MyApplication"。这只是为了帮助像我这样的人。 - praveenb
2
伟大程序员的标志,是能够轻松解决我曾经遇到的最复杂问题之一。 - Aashish Bhatnagar
2
太棒了!谢谢。如果有人遇到“ClassCastException”错误,则可能忘记将其添加到Manifest.xml中的应用程序标签中:<application android:name="your.package.MyApplication" - Wahib Ul Haq
27
这是一个很好而简单的实现。然而,我认为这应该在onStart/onStop中实现,而不是在onPause/onResume中实现。如果我启动一个部分覆盖活动的对话框,那么onPause将会被调用。关闭对话框实际上会调用onResume,使得应用程序似乎刚刚回到前台。 - Shubhayu
7
我希望您能翻译以下内容:我希望使用这个解决方案的一个变体。上面提到的关于对话框的问题对我来说是个问题,所以我尝试了@Shubhayu的建议(onStart/onStop)。然而这并没有帮助,因为当从A转到B时,Activity B的onStart()会在Activity A的onStop()之前调用。 - Trevor
显示剩余10条评论

173

2021年11月更新:

实际设置如下:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
    }
}

class AppLifecycleListener : DefaultLifecycleObserver {

    override fun onStart(owner: LifecycleOwner) { // app moved to foreground
    }

    override fun onStop(owner: LifecycleOwner) { // app moved to background
    }
}

依赖项

implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version"

ProcessLifecycleOwner 看起来也是一个有前途的解决方案。

当一个 Activity 进入这些事件时,ProcessLifecycleOwner 将分发 ON_STARTON_RESUME 事件。当最后一个 Activity 完成这些事件后,ON_PAUSEON_STOP 事件将被延迟分发一段时间。这个延迟足够长,可以保证在活动由于配置更改而被销毁和重建时不会发送任何事件。

实现可以非常简单,例如:

class AppLifecycleListener : LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onMoveToForeground() { // app moved to foreground
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onMoveToBackground() { // app moved to background
    }
}

// register observer
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
根据源代码,当前延迟值为700毫秒。同时,使用此功能需要依赖项
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"

11
请注意,您需要从Google的存储库(即google())添加生命周期依赖项implementation "android.arch.lifecycle:extensions:1.0.0"annotationProcessor "android.arch.lifecycle:compiler:1.0.0" - Sir Codesalot
1
这对我非常有效,谢谢。由于错误指出Android依赖项在编译和运行时类路径上具有不同的版本,因此我不得不使用api 'android.arch.lifecycle:extensions:1.1.0'而不是implementation。 - FSUWX2011
1
当应用程序崩溃时,这个方法无法工作。有没有办法通过这个解决方案来获取应用程序崩溃事件呢? - tejraj
1
@SirCodesalot2.2.0 版本中不是必需的。 - artem
4
当调用外部活动(如文件选择器)时,此方法无法按预期工作。在这种情况下,当选择器打开时,应用程序将被通知为后台,当选择器关闭时,应用程序将再次成为前台。有什么解决办法吗? - 4face
显示剩余10条评论

171

编辑:新的架构组件带来了一些有前途的东西:ProcessLifecycleOwner,请参见@vokilam的答案


根据Google I/O talk的实际解决方案:

class YourApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    registerActivityLifecycleCallbacks(AppLifecycleTracker())
  }

}


class AppLifecycleTracker : Application.ActivityLifecycleCallbacks  {

  private var numStarted = 0

  override fun onActivityStarted(activity: Activity?) {
    if (numStarted == 0) {
      // app went to foreground
    }
    numStarted++
  }

  override fun onActivityStopped(activity: Activity?) {
    numStarted--
    if (numStarted == 0) {
      // app went to background
    }
  }

}

是的,我知道这个简单的解决方案很难让人相信它能起作用,因为我们在这里有很多奇怪的解决方案。

然而还是有希望的。


3
这个方法非常有效!我已经尝试了很多奇怪的解决方案,但都有很多缺陷...非常感谢!我已经寻找这个方法有一段时间了。 - Eggakin Baconwalker
8
它适用于多种活动,但对于一个活动而言,onrotate 将指示所有活动都已消失或进入后台。 - deadfish
2
@Shyri 你说得对,但这是解决方案的一部分,所以不需要担心。如果 Firebase 依赖于此,我认为我的平庸应用也可以使用 :) 非常好的答案。 - ElliotM
3
请查看答案顶部提供的 I/O 链接。您可以检查活动停止和开始之间的时间间隔,以确定您是否真正进入了后台。实际上,这是一个很棒的解决方案。 - Oleksandr Berdnikov
4
有没有Java解决方案?这是Kotlin。 - Giacomo Bartoli
显示剩余20条评论

112

onPause()onResume()方法在应用程序进入后台和重新进入前台时调用。但是,它们在应用程序首次启动并在被杀死之前也会被调用。您可以在 Activity 中了解更多信息。

没有直接的方法可以在后台或前台获取应用程序状态,但我也遇到了这个问题,并通过使用 onWindowFocusChangedonStop 来解决。

有关更多详情,请参见此处:Android:无需 getRunningTasks 或 getRunningAppProcesses 就可以检测 Android 应用何时进入后台并返回前台的解决方案


191
正如其他人指出的那样,这种方法会导致假阳性结果,因为在同一应用程序的活动之间转换时也会调用这些方法。 - John Lehmann
9
这情况比你想的更糟。我尝试了一下,有时当手机锁屏时会调用onResume函数。如果你看一下onResume在文档中的定义,你会发现: 请注意,onResume不是判断你的Activity是否对用户可见的最佳指标;系统窗口如键盘锁可能在其前面。使用onWindowFocusChanged(boolean)函数来确保你的Activity对用户可见(例如恢复一个游戏)。 http://developer.android.com/reference/android/app/Activity.html#onResume%28%29 - J-Rou
2
链接中发布的解决方案没有使用onResume/onPause,而是使用了onBackPressed、onStop、onStart和onWindowsFocusChanged的组合。它对我起作用了,而且我的UI层次结构相当复杂(带有抽屉、动态视图页面等)。 - Martin Marconcini
22
onPause和onResume是针对Activity而不是Application的。当一个应用程序被放到后台并重新恢复时,它会继续上次进入后台前所处的那个Activity。这意味着您需要在应用程序的所有Activity中实现任何您希望在从后台恢复时执行的操作。我相信原问题是在寻找类似于应用程序级别的"onResume"而不是Activity级别的。 - SysHex
4
很难相信一个如此常见的需求没有提供适当的API。起初我认为onUserLeaveHint()可以解决问题,但你无法确定用户是离开应用程序还是仅仅跳转到另一个活动。 - atsakiridis
显示剩余3条评论

80
根据Martin Marconcinis(谢谢!)的回答,我最终找到了一个可靠(且非常简单)的解决方案。
public class ApplicationLifecycleHandler implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {

    private static final String TAG = ApplicationLifecycleHandler.class.getSimpleName();
    private static boolean isInBackground = false;

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

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {

        if(isInBackground){
            Log.d(TAG, "app went to foreground");
            isInBackground = false;
        }
    }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

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

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    @Override
    public void onConfigurationChanged(Configuration configuration) {
    }

    @Override
    public void onLowMemory() {
    }

    @Override
    public void onTrimMemory(int i) {
        if(i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN){
            Log.d(TAG, "app went to background");
            isInBackground = true;
        }
    }
}

然后将以下内容添加到你的Application类的onCreate()方法中

public class MyApp extends android.app.Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
        registerActivityLifecycleCallbacks(handler);
        registerComponentCallbacks(handler);

    }

}

1
你能展示一下如何在应用程序中使用它吗?我需要从App类或其他地方调用它吗? - JPM
这个例子是不完整的。registerActivityLifecycleCallbacks是什么? - Noman
这是在类android.app.Application中的一个方法。 - rickul
1
干得好!+1 排在最前面,因为它是完美的。不要看其他答案,这是基于 @reno 的回答,但有真实的例子。 - Stoycho Andreev
1
尝试了您的答案,但不够可靠。当屏幕被锁定或按下“电源”按钮锁定屏幕时,onTrimMemory回调将不会被触发。如果您的应用程序可见并通过状态栏通知打开另一个应用程序,则它也不会始终返回TRIM_MEMORY_UI_HIDDEN。唯一可靠的解决方案是实现ActivityLifecycleCallbacks并根据自己的用例进行调整。 - velval
显示剩余4条评论

64
我们使用这种方法。它看起来太简单而无法发挥作用,但在我们的应用程序中进行了充分测试,并且实际上在所有情况下都表现出惊人的良好效果,包括通过“主页”按钮、通过“返回”按钮或在屏幕锁定后返回主屏幕。不妨试试。
思路是当应用在前台时,Android总是在停止前一个活动之前启动新的活动。尽管这并不保证,但这就是它的工作方式。顺便说一句,Flurry似乎也使用相同的逻辑(只是猜测,我没有检查过,但它在相同的事件上挂钩)。
public abstract class BaseActivity extends Activity {

    private static int sessionDepth = 0;

    @Override
    protected void onStart() {
        super.onStart();       
        sessionDepth++;
        if(sessionDepth == 1){
        //app came to foreground;
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (sessionDepth > 0)
            sessionDepth--;
        if (sessionDepth == 0) {
            // app went to background
        }
    }

}

编辑:根据评论,我们也在代码的后续版本中转移到了 onStart()。此外,我添加了 super 调用,因为这在我的初始帖子中缺失了,因为这更多是一个概念而不是一个可工作的代码。


2
这是最可靠的答案,尽管我使用onStart而不是onResume。 - Greg Ennis
1
对我来说它不起作用...或者至少在你旋转设备时也会触发事件(在我看来这有点误报)。 - Noya
非常简单而有效的解决方案!但我不确定它是否适用于让前一个活动的某些部分可见的部分透明活动。根据文档,“当活动对用户不再可见时,将调用onStop”。 - Nicolas Buquet
3
如果用户在第一个活动中改变了方向,会报告应用程序进入后台,但实际上并不是这样。您如何处理这种情况? - Nimrod Dayan
为了避免在旋转设备时报告应用程序进入后台,我所要做的就是在AndroidManifest中为每个允许旋转的活动添加“android:configChanges =”orientation | screenSize“。 - Luccas Correa
显示剩余4条评论

55
如果你的应用程序由多个活动或堆栈活动(如选项卡小部件)组成,那么重写onPause()和onResume()将无效。也就是说,在启动新活动时,在创建新的活动之前,当前活动将被暂停。当结束一个活动(使用“返回”按钮)时,同样也会出现这种情况。
我找到了两种看起来可以实现预期功能的方法。
第一种需要GET_TASKS权限,并且包括一个简单的方法,该方法检查设备上运行的顶部活动是否属于该应用程序,通过比较包名来确定:
private boolean isApplicationBroughtToBackground() {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> tasks = am.getRunningTasks(1);
    if (!tasks.isEmpty()) {
        ComponentName topActivity = tasks.get(0).topActivity;
        if (!topActivity.getPackageName().equals(context.getPackageName())) {
            return true;
        }
    }

    return false;
}

这个方法来自Droid-Fu(现在称为Ignition)框架。

我自己实现的第二种方法不需要GET_TASKS权限,这很好。但是它实现起来要复杂一些。

在你的MainApplication类中,你有一个变量用于跟踪应用程序中正在运行的活动数量。在每个活动的onResume()方法中增加该变量,在onPause()方法中减少它。

当运行的活动数达到0时,如果满足以下条件,应用程序就会被置于后台:

  • 被暂停的活动没有被结束(使用了“返回”按钮)。可以通过activity.isFinishing()方法来判断。
  • 新的活动(相同的包名)没有被启动。您可以重写startActivity()方法来设置一个变量,指示此情况,并在onPostResume()方法中重置它,该方法是活动创建/恢复时运行的最后一个方法。

当你可以检测到应用程序已经退到后台时,很容易检测到它何时被带回前台。


20
使用 ActivityManager.getRunningTasks() 的应用程序可能会被 Google 拒绝。文档说明仅适用于开发目的。请参考 http://developer.android.com/reference/android/app/ActivityManager.html#getRunningTasks%28int%29。 - Sky Kelsey
只是想提一下这个问题,因为getRunnintTasks()存在问题。链接 - Cornstalks
1
我发现我必须使用这些方法的组合。在14中启动活动时,会调用onUserLeaveHint()。 @Override public void onUserLeaveHint() { inBackground = isApplicationBroughtToBackground(); } - listing boat
7
用户使用一个强大的权限 android.permission.GET_TASKS 可能不会感到太满意。 - MSquare
能否知道活动是否来自后台? - Vaishali Sutariya
7
在API 21及以上版本中,getRunningTasks方法已被弃用。 - Noya

37

创建一个继承Application。然后在其内部,我们可以使用它的重写方法onTrimMemory()

为了检测应用程序是否进入后台,我们将使用:

 @Override
    public void onTrimMemory(final int level) {
        if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Works for Activity
            // Get called every-time when application went to background.
        } 
        else if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { // Works for FragmentActivty
        }
    }

1
对于 FragmentActivity,您可能还想添加 level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE - Srujan Simha
2
非常感谢您指出这个方法,我需要在用户恢复后台活动时显示一个 Pin 对话框,使用此方法编写了一个 pref 值并在 baseActivity 上检查了该值。 - Sam

20
考虑使用onUserLeaveHint。这只会在应用程序进入后台时调用。onPause将需要处理一些特殊情况,因为它可能会因其他原因而被调用;例如,如果用户在您的应用程序中打开另一个活动(如设置页面),即使他们仍然在应用程序中,也会调用主活动的onPause方法; 跟踪正在进行的操作将导致错误,您可以改用onUserLeaveHint回调来实现您期望的功能。
当调用onUserLeaveHint时,您可以将一个布尔型inBackground标志设置为true。当调用onResume时,只有在inBackground标志被设置时才假定您回到了前台。这是因为如果用户只是在您的设置菜单中,从未离开应用程序,则onResume也将在您的主活动上调用。
请记住,如果用户在您的设置屏幕中按下主屏幕按钮,则会在您的设置活动中调用onUserLeaveHint,当他们返回时,在您的设置活动中将调用onResume。如果您只有在主活动中添加此检测代码,则会忽略此用例。要在所有活动中使用此代码而不重复代码,请创建一个抽象活动类,该类扩展Activity,并将公共代码放入其中,然后每个活动都可以扩展此抽象活动。
例如:
public abstract AbstractActivity extends Activity {
    private static boolean inBackground = false;

    @Override
    public void onResume() {
        if (inBackground) {
            // You just came from the background
            inBackground = false;
        }
        else {
            // You just returned from another activity within your own app
        }
    }

    @Override
    public void onUserLeaveHint() {
        inBackground = true;
    }
}

public abstract MainActivity extends AbstractActivity {
    ...
}

public abstract SettingsActivity extends AbstractActivity {
    ...
}

22
当导航到另一个活动时,也会调用onUserLeaveHint方法。 - Jonas Stawski
3
当手机来电并且呼叫活动变为活跃状态时,onUserLeaveHint 不会被调用,因此这也有一个边缘情况 - 也可能存在其他情况,因为您可以向意图添加标志以抑制 onUserLeaveHint 调用。http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NO_USER_ACTION - Groxx
1
另外,onResume 不起作用。我尝试了一下,有时当手机被锁定时,onResume 会被调用。如果您查看文档中 onResume 的定义,您会发现: 请记住,onResume 并不是您的活动可见于用户的最佳指示器;系统窗口(如键盘保护)可能在前面。使用 onWindowFocusChanged(boolean) 可以确定您的活动对用户可见(例如,恢复游戏)。 http://developer.android.com/reference/android/app/Activity.html#onResume%28%29 - J-Rou
如果有多个活动,则此解决方案无法帮助确定前台/后台。请参阅https://dev59.com/KHA65IYBdhLWcg3wuhIR#5862048。 - Raj Trivedi

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