我试图写一个应用程序,做一些具体的时候,它被带回前台后一段时间。是否有一种方法可以检测应用程序是被发送到后台还是被带到前台?


当前回答

如果你的应用由多个活动和/或堆叠的活动组成,就像一个标签栏小部件,那么覆盖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()中减少变量。

当正在运行的activity数量达到0时,如果满足以下条件,应用程序将被放入后台:

正在暂停的活动没有结束(使用了“后退”按钮)。这可以通过使用activity.isFinishing()方法来完成。 没有启动一个新活动(相同的包名)。你可以重写startActivity()方法来设置一个变量来指示这一点,然后在onPostResume()中重置它,这是创建/恢复活动时运行的最后一个方法。

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

其他回答

2018: Android通过生命周期组件原生支持这一点。

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

在这个答案中提供了一个简单的示例,但我写了一个示例应用程序和一篇关于它的博客文章。

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

永远不要假设一个代码片段就是你想要的解决方案,这是不可能的;更好的做法是,试着理解它是做什么的,以及它为什么这么做。

MemoryBoss类实际上从未被我使用过,它只是一段碰巧工作的伪代码。

除非有充分的理由不使用新的体系结构组件(确实有一些,特别是如果您的目标是超级老的api),否则就继续使用它们。它们远非完美,但ComponentCallbacks2也不是。

UPDATE / NOTES (November 2015): People has been making two comments, first is that >= should be used instead of == because the documentation states that you shouldn't check for exact values. This is fine for most cases, but bear in mind that if you only care about doing something when the app went to the background, you will have to use == and also combine it with another solution (like Activity Lifecycle callbacks), or you may not get your desired effect. The example (and this happened to me) is that if you want to lock your app with a password screen when it goes to the background (like 1Password if you're familiar with it), you may accidentally lock your app if you run low on memory and are suddenly testing for >= TRIM_MEMORY, because Android will trigger a LOW MEMORY call and that's higher than yours. So be careful how/what you test.

此外,有些人问过如何检测你什么时候回来。

下面解释了我能想到的最简单的方法,但由于有些人不熟悉它,所以我在这里添加了一些伪代码。假设你有YourApplication类和MemoryBoss类,在你的类BaseActivity扩展了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,所以它不会执行代码,直到onmemorytrim被调用并且标志被再次设置为true。

更新/注释(2015年4月):在你对这段代码进行复制和粘贴之前,请注意,我已经发现了一些实例,其中它可能不是100%可靠的,必须与其他方法结合才能获得最佳结果。 值得注意的是,有两个已知的实例,其中onTrimMemory回调不保证被执行:

如果你的手机锁定屏幕,而你的应用程序是可见的(比如你的设备锁定nn分钟后),这个回调不会被调用(或不总是),因为锁定屏幕只是在顶部,但你的应用程序仍然“运行”,尽管覆盖。 如果您的设备内存相对较低(并且处于内存压力之下),操作系统似乎会忽略这个调用,直接进入更关键的级别。

现在,取决于你知道你的应用什么时候进入后台有多重要,你可能需要也可能不需要扩展这个解决方案,同时跟踪活动生命周期等等。

只要记住以上几点,并拥有一个优秀的QA团队;)

更新结束

可能晚了,但在冰淇淋三明治(API 14)及以上有一个可靠的方法。

当你的应用没有更多可见的UI时,一个回调被触发。可以在自定义类中实现的回调称为ComponentCallbacks2(是的,带有一个2)。此回调仅在API级别14(冰淇淋三明治)及以上可用。

你基本上得到一个方法的调用:

public abstract void onTrimMemory (int level)

级别是20或更多

public static final int TRIM_MEMORY_UI_HIDDEN

我一直在测试这个,它总是有效的,因为20级只是一个“建议”,你可能想要释放一些资源,因为你的应用程序不再可见。

引用官方文件:

onTrimMemory(int)的级别:进程已经显示了一个用户界面,并且不再这样做。这时候应该释放大量的UI分配,以便更好地管理内存。

当然,您应该实现它来实现它所说的(清除在特定时间内未使用的内存,清除一些未使用的集合,等等。可能性是无限的(查看官方文档了解其他可能的更关键的级别)。

但是,有趣的是,操作系统告诉你:嘿,你的应用进入了后台!

这正是你一开始就想知道的。

你怎么确定你什么时候回来?

好吧,这很容易,我相信你有一个“BaseActivity”,所以你可以使用你的onResume()标记事实,你回来了。因为只有当您实际接收到对上述onTrimMemory方法的调用时,您才会说您没有返回。

它的工作原理。你不会得到假阳性。如果一项活动恢复了,那么你就百分百地回来了。如果用户再次返回,您将得到另一个onTrimMemory()调用。

您需要订阅您的Activities(或者更好的是,定制类)。

保证你总是收到这个的最简单的方法是创建一个简单的类,像这样:

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

如果你创建了一个接口,你可以在If中添加一个else,并实现在API 14以下的任何东西中使用的ComponentCallbacks(没有2)。该回调只有onLowMemory()方法,当你进入后台时不会被调用,但你应该使用它来修剪内存。

现在启动你的App,按home键。你的onTrimMemory(最终int级别)方法应该被调用(提示:添加日志记录)。

最后一步是从回调取消注册。也许最好的地方是你的应用程序的onTerminate()方法,但是,该方法不会在真正的设备上被调用:

/** *此方法用于模拟过程环境。它将 *永远不要在生产Android设备上调用,那里有进程 *通过简单地杀死它们来移除;没有用户代码(包括此回调) 执行*。 * /

因此,除非你真的遇到了不想再注册的情况,否则你可以安全地忽略它,因为你的进程在操作系统级已经死亡了。

如果你决定在某些时候取消注册(例如,如果你为你的应用程序提供了一个关闭机制来清理和死亡),你可以这样做:

unregisterComponentCallbacks(mMemoryBoss);

就是这样。

我们用这个方法。它看起来太简单了,但它在我们的应用程序中经过了良好的测试,实际上在所有情况下都运行得非常好,包括通过“home”按钮进入主屏幕,通过“返回”按钮,或在屏幕锁定后返回主屏幕。试一试。

想法是,当在前景,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()在以后的版本的代码。此外,我还添加了超级调用,这是我最初的文章中所没有的,因为这更像是一个概念,而不是一个工作代码。

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似乎也是一个很有前途的解决方案。

ProcessLifecycleOwner将分派ON_START, ON_RESUME事件,当第一个活动通过这些事件时。ON_PAUSE, ON_STOP,事件将在最后一个活动通过它们后延迟分派。这个延迟足够长,可以保证ProcessLifecycleOwner在由于配置更改而销毁和重新创建活动时不会发送任何事件。

实现可以简单到

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

根据源代码,当前延迟值为700ms。

使用此特性还需要依赖项:

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"

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

/**
 * 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();

}

基于Martín marconcini的回答(谢谢!)我终于找到了一个可靠(而且非常简单)的解决方案。

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

    }

}