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

450

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


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

17

android.arch.lifecycle包提供了类和接口,让您构建具有生命周期意识的组件。

您的应用程序应该实现LifecycleObserver接口:

public class MyApplication extends Application implements LifecycleObserver {

    @Override
    public void onCreate() {
        super.onCreate();
        ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    private void onAppBackgrounded() {
        Log.d("MyApp", "App in background");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    private void onAppForegrounded() {
        Log.d("MyApp", "App in foreground");
    }
}
要做到这一点,您需要将此依赖项添加到您的build.gradle文件中:
dependencies {
    implementation "android.arch.lifecycle:extensions:1.1.1"
}

根据Google的建议,您应该尽量减少在活动的生命周期方法中执行的代码:

一种常见的模式是在活动和片段的生命周期方法中实现依赖组件的操作。然而,这种模式会导致代码组织不良和错误的繁殖。通过使用生命周期感知组件,您可以将相关组件的代码从生命周期方法中移出并放入组件本身。

您可以在此处阅读更多: https://developer.android.com/topic/libraries/architecture/lifecycle


并将其添加到清单中,如:<application android:name=".AnotherApp"> - Dan Alboteanu
@OnLifecycleEvent已被弃用。请参考https://developer.android.com/jetpack/androidx/releases/lifecycle#2.4.0-beta01。 - Akito

14

看起来这个很干净,对我有用。谢谢。 - windkiosk
这与被接受的答案有何不同,两者都依赖于相同的活动生命周期,对吧? - Saitama

9
在您的应用程序中添加回调函数,并以以下方式检查根活动:
@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
        @Override
        public void onActivityStopped(Activity activity) {
        }

        @Override
        public void onActivityStarted(Activity activity) {
        }

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

        @Override
        public void onActivityResumed(Activity activity) {
        }

        @Override
        public void onActivityPaused(Activity activity) {
        }

        @Override
        public void onActivityDestroyed(Activity activity) {
        }

        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            if (activity.isTaskRoot() && !(activity instanceof YourSplashScreenActivity)) {
                Log.e(YourApp.TAG, "Reload defaults on restoring from background.");
                loadDefaults();
            }
        }
    });
}

我会考虑使用这种实现方式。从一个活动到另一个活动的转换只需要几毫秒。根据最后一个活动消失的时间,可以考虑通过特定策略重新登录用户。 - drindt

7
您可以使用ProcessLifecycleOwner将生命周期观察者附加到它上面。
  public class ForegroundLifecycleObserver implements LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    public void onAppCreated() {
        Timber.d("onAppCreated() called");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void onAppStarted() {
        Timber.d("onAppStarted() called");
    }

    @OnLifecycleEvent(Event.ON_RESUME)
    public void onAppResumed() {
        Timber.d("onAppResumed() called");
    }

    @OnLifecycleEvent(Event.ON_PAUSE)
    public void onAppPaused() {
        Timber.d("onAppPaused() called");
    }

    @OnLifecycleEvent(Event.ON_STOP)
    public void onAppStopped() {
        Timber.d("onAppStopped() called");
    }
}

在您的Application类的onCreate()方法中调用以下代码:
ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());

通过这个,你将能够捕获应用程序进入后台时发生的 ON_PAUSEON_STOP 事件。

6

我在Github上创建了一个项目app-foreground-background-listen

为你的应用程序中的所有Activity创建一个BaseActivity。

public class BaseActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

    public static boolean isAppInFg = false;
    public static boolean isScrInFg = false;
    public static boolean isChangeScrFg = false;

    @Override
    protected void onStart() {
        if (!isAppInFg) {
            isAppInFg = true;
            isChangeScrFg = false;
            onAppStart();
        }
        else {
            isChangeScrFg = true;
        }
        isScrInFg = true;

        super.onStart();
    }

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

        if (!isScrInFg || !isChangeScrFg) {
            isAppInFg = false;
            onAppPause();
        }
        isScrInFg = false;
    }

    public void onAppStart() {

        // Remove this toast
        Toast.makeText(getApplicationContext(), "App in foreground",    Toast.LENGTH_LONG).show();

        // Your code
    }

    public void onAppPause() {

        // Remove this toast
        Toast.makeText(getApplicationContext(), "App in background",  Toast.LENGTH_LONG).show();

        // Your code
    }
}

现在将这个BaseActivity用作所有Activity的超类,例如MainActivity扩展BaseActivity,当您启动应用程序时将调用onAppStart,当应用程序从任何屏幕进入后台时将调用onAppPause()。


@kiran boghra:你的解决方案中是否存在误报? - Harish Vishwakarma
完美的答案是在这种情况下可以使用onStart()和onStop()函数,它们告诉您有关您的应用程序的信息。 - Pir Fahim Shah

6
这非常容易,使用ProcessLifecycleOwner即可。
添加以下依赖项
implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"

Kotlin 中:
class ForegroundBackgroundListener : LifecycleObserver {


    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun startSomething() {
        Log.v("ProcessLog", "APP IS ON FOREGROUND")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stopSomething() {
        Log.v("ProcessLog", "APP IS IN BACKGROUND")
    }
}

然后在您的基本活动中:

override fun onCreate() {
        super.onCreate()

        ProcessLifecycleOwner.get()
                .lifecycle
                .addObserver(
                        ForegroundBackgroundListener()
                                .also { appObserver = it })
    }

请查看我关于这个主题的文章:https://medium.com/@egek92/how-to-actually-detect-foreground-background-changes-in-your-android-application-without-wanting-9719cc822c48


@OnLifecycleEvent 已被弃用。请参阅 https://developer.android.com/jetpack/androidx/releases/lifecycle#2.4.0-beta01。 - Akito

6

没有直接的生命周期方法可以告诉您整个应用何时进入后台/前台。

我用简单的方法做到了这一点。按照以下说明检测应用程序的后台/前台阶段。

通过一些小技巧,是可能的。在这里,ActivityLifecycleCallbacks 来挽救。让我一步步地走过去。

  1. First, create a class that extends the android.app.Application and implements the ActivityLifecycleCallbacks interface. In the Application.onCreate(), register the callback.

    public class App extends Application implements 
        Application.ActivityLifecycleCallbacks {
    
        @Override
        public void onCreate() {
            super.onCreate();
            registerActivityLifecycleCallbacks(this);
        }
    }
    
  2. Register the “App” class in the Manifest as below, <application android:name=".App".

  3. There will be at least one Activity in the started state when the app is in the foreground and there will be no Activity in the started state when the app is in the background.

    Declare 2 variables as below in the “App” class.

    private int activityReferences = 0;
    private boolean isActivityChangingConfigurations = false;
    

    activityReferences will keep the count of number of activities in the started state. isActivityChangingConfigurations is a flag to indicate if the current Activity is going through configuration change like an orientation switch.

  4. Using the following code you can detect if the App comes foreground.

    @Override
    public void onActivityStarted(Activity activity) {
        if (++activityReferences == 1 && !isActivityChangingConfigurations) {
            // App enters foreground
        }
    }
    
  5. This is how to detect if the App goes background.

    @Override
    public void onActivityStopped(Activity activity) {
        isActivityChangingConfigurations = activity.isChangingConfigurations();
        if (--activityReferences == 0 && !isActivityChangingConfigurations) {
            // App enters background
        }
    }
    

工作原理:

这是通过按顺序调用生命周期方法来完成的小技巧。让我演示一个场景。

假设用户启动应用程序并启动启动器活动 A。生命周期调用将是,

A.onCreate()

A.onStart() (++activityReferences == 1)(应用进入前台)

A.onResume()

现在 Activity A 启动了 Activity B。

A.onPause()

B.onCreate()

B.onStart() (++activityReferences == 2)

B.onResume()

A.onStop() (--activityReferences == 1)

然后用户从 Activity B 返回,

B.onPause()

A.onStart() (++activityReferences == 2)

A.onResume()

B.onStop() (--activityReferences == 1)

B.onDestroy()

然后用户按 Home 按钮,

A.onPause()

A.onStop() (--activityReferences == 0)(应用进入后台)

如果用户从 Activity B 而不是 Back 按钮按 Home 按钮,则仍然是相同的,并且 activityReferences 将为 0。因此,我们可以检测到应用程序进入后台。

那么,isActivityChangingConfigurations 的作用是什么?在上述情况下,假设 Activity B 更改了方向。回调序列将是,

B.onPause()

B.onStop() (--activityReferences == 0)(应用进入后台??)

B.onDestroy()

B.onCreate()

B.onStart() (++activityReferences == 1)(应用进入前台??)

B.onResume()

这就是为什么我们有一个额外的检查 isActivityChangingConfigurations 来避免 Activity 正在进行配置更改时的情况。


5
您可以使用以下代码:

protected void onRestart ()

来区分新启动和重新启动。

enter image description here


3

我发现了一种很好的方法来检测应用程序是否进入前台或后台。 这是我的代码。 希望这可以帮助你。

/**
 * Custom Application which can detect application state of whether it enter
 * background or enter foreground.
 *
 * @reference http://www.vardhan-justlikethat.blogspot.sg/2014/02/android-solution-to-detect-when-android.html
 */
 public abstract class StatusApplication extends Application implements ActivityLifecycleCallbacks {

public static final int STATE_UNKNOWN = 0x00;
public static final int STATE_CREATED = 0x01;
public static final int STATE_STARTED = 0x02;
public static final int STATE_RESUMED = 0x03;
public static final int STATE_PAUSED = 0x04;
public static final int STATE_STOPPED = 0x05;
public static final int STATE_DESTROYED = 0x06;

private static final int FLAG_STATE_FOREGROUND = -1;
private static final int FLAG_STATE_BACKGROUND = -2;

private int mCurrentState = STATE_UNKNOWN;
private int mStateFlag = FLAG_STATE_BACKGROUND;

@Override
public void onCreate() {
    super.onCreate();
    mCurrentState = STATE_UNKNOWN;
    registerActivityLifecycleCallbacks(this);
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    // mCurrentState = STATE_CREATED;
}

@Override
public void onActivityStarted(Activity activity) {
    if (mCurrentState == STATE_UNKNOWN || mCurrentState == STATE_STOPPED) {
        if (mStateFlag == FLAG_STATE_BACKGROUND) {
            applicationWillEnterForeground();
            mStateFlag = FLAG_STATE_FOREGROUND;
        }
    }
    mCurrentState = STATE_STARTED;

}

@Override
public void onActivityResumed(Activity activity) {
    mCurrentState = STATE_RESUMED;

}

@Override
public void onActivityPaused(Activity activity) {
    mCurrentState = STATE_PAUSED;

}

@Override
public void onActivityStopped(Activity activity) {
    mCurrentState = STATE_STOPPED;

}

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

}

@Override
public void onActivityDestroyed(Activity activity) {
    mCurrentState = STATE_DESTROYED;
}

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    if (mCurrentState == STATE_STOPPED && level >= TRIM_MEMORY_UI_HIDDEN) {
        if (mStateFlag == FLAG_STATE_FOREGROUND) {
            applicationDidEnterBackground();
            mStateFlag = FLAG_STATE_BACKGROUND;
        }
    }else if (mCurrentState == STATE_DESTROYED && level >= TRIM_MEMORY_UI_HIDDEN) {
        if (mStateFlag == FLAG_STATE_FOREGROUND) {
            applicationDidDestroyed();
            mStateFlag = FLAG_STATE_BACKGROUND;
        }
    }
}

/**
 * The method be called when the application been destroyed. But when the
 * device screen off,this method will not invoked.
 */
protected abstract void applicationDidDestroyed();

/**
 * The method be called when the application enter background. But when the
 * device screen off,this method will not invoked.
 */
protected abstract void applicationDidEnterBackground();

/**
 * The method be called when the application enter foreground.
 */
protected abstract void applicationWillEnterForeground();

}


3
编辑2:我下面写的实际上不会起作用。谷歌拒绝了一个包含调用ActivityManager.getRunningTasks()的应用程序。从文档中可以看出,这个API仅用于调试和开发目的。我将在有时间更新下面的GitHub项目方案,使用定时器,效果几乎一样好。
编辑1:我写了一篇博客文章,并创建了一个简单的GitHub存储库,使得这个过程非常容易。
接受和评分最高的答案都不是最佳方法。最高评分答案的isApplicationBroughtToBackground()实现没有处理这样一种情况:应用程序的主Activity正在让给在同一应用程序中定义但具有不同Java包的Activity。我想出了一种方法,在这种情况下也能工作。
在onPause()中调用它,它会告诉你,你的应用程序是否进入后台,因为另一个应用程序已经启动,或者用户按下了主屏幕按钮。
public static boolean isApplicationBroughtToBackground(final Activity activity) {
  ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
  List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1);

  // Check the top Activity against the list of Activities contained in the Application's package.
  if (!tasks.isEmpty()) {
    ComponentName topActivity = tasks.get(0).topActivity;
    try {
      PackageInfo pi = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES);
      for (ActivityInfo activityInfo : pi.activities) {
        if(topActivity.getClassName().equals(activityInfo.name)) {
          return false;
        }
      }
    } catch( PackageManager.NameNotFoundException e) {
      return false; // Never happens.
    }
  }
  return true;
}

顺便说一下,在onStart()中调用这个方法可以避免在弹出简单对话框时(例如,闹钟响起)被调用。 - Sky Kelsey

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