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


当前回答

有三种方法可以实现这一点:

单一活动架构 ActivityLifecycleCallback 生命周期观察者和进程生命周期所有者

我在这里写了一篇关于这个的详细文章。希望能有所帮助。

其他回答

我使用这个解决方案: http://nathanael.hevenet.com/android-dev-detecting-when-your-app-is-in-the-background-across-activities/

简而言之——构建一个专门的服务,让每个活动向他报告每个生命周期事件,这个服务获得关于应用程序状态的信息。

很像@oldschool4664的解决方案,但在我看来更干净

My app needs to "reboot" after return from background - show a series of activities, according to client solicitations. After extensive search on how to manage the background/foreground transitions (treated very differently between iOS and Android), I crossed this question. Found very useful help here, specially from the most voted answer and the one flagged as correct. However, simply reinstantiate the root activity EVERY TIME the app enters foreground looked too annoying, when you think about UX. The solution that worked for me, and the one I think's most adequated - based on the Youtube and Twitter apps functionality - was to combine the answers from @GirishNair and @d60402: Calling the timer when the app's trimming memory, as follows:

@Override
public void onTrimMemory(int level) {
    if (stateOfLifeCycle.equals("Stop")) {
        startActivityTransitionTimer();
    }

    super.onTrimMemory(level);
}

我的定时器限制设置为30秒-我正在考虑增加一点。

private final long MAX_ACTIVITY_TRANSITION_TIME = 30000;

当app进入前台,重新启动,或者app被销毁时,调用方法取消定时器。

在应用程序扩展:

@Override
public void onActivityCreated(Activity activity, Bundle arg1) {
    stopActivityTransitionTimer();
    stateOfLifeCycle = "Create";
}

@Override
public void onActivityDestroyed(Activity activity) {
    stopActivityTransitionTimer();
    stateOfLifeCycle = "Destroy";
}

在活动上(最好是在一个基础活动上,由其他活动继承):

@Override
protected void onStart() {
    super.onStart();
    if (App.wasInBackground) {
        stopActivityTransitionTimer();
    }
}

在我的情况下,当应用程序在最大时间后进入前台时,会创建一个新任务,因此stopActivityTransitionTimer()在onActivityCreated()或onActivityDestroyed()上被调用,在应用程序扩展类中-转向不必要调用活动中的方法。 希望能有所帮助。

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

就是这样。

你可以使用:

onRestart ()

重新开始和重新开始的区别。

这是我的解决方案。只需在您的主Application类中注册这个ActivityLifecycleCallbacks。在评论中,我提到了一个用户配置文件活动边缘情况。该活动只是一个具有透明边缘的活动。

/**
 * This class used Activity lifecycle callbacks to determine when the application goes to the
 * background as well as when it is brought to the foreground.
 */
public class Foreground implements Application.ActivityLifecycleCallbacks
{
    /**
     * How long to wait before checking onStart()/onStop() count to determine if the app has been
     * backgrounded.
     */
    public static final long BACKGROUND_CHECK_DELAY_MS = 500;

    private static Foreground sInstance;

    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
    private boolean mIsForeground = false;
    private int mCount;

    public static void init(final Application application)
    {
        if (sInstance == null)
        {
            sInstance = new Foreground();
            application.registerActivityLifecycleCallbacks(sInstance);
        }
    }

    public static Foreground getInstance()
    {
        return sInstance;
    }

    public boolean isForeground()
    {
        return mIsForeground;
    }

    public boolean isBackground()
    {
        return !mIsForeground;
    }

    @Override
    public void onActivityStarted(final Activity activity)
    {
        mCount++;

        // Remove posted Runnables so any Meteor disconnect is cancelled if the user comes back to
        // the app before it runs.
        mMainThreadHandler.removeCallbacksAndMessages(null);

        if (!mIsForeground)
        {
            mIsForeground = true;
        }
    }

    @Override
    public void onActivityStopped(final Activity activity)
    {
        mCount--;

        // A transparent Activity like community user profile won't stop the Activity that launched
        // it. If you launch another Activity from the user profile or hit the Android home button,
        // there are two onStops(). One for the user profile and one for its parent. Remove any
        // posted Runnables so we don't get two session ended events.
        mMainThreadHandler.removeCallbacksAndMessages(null);
        mMainThreadHandler.postDelayed(new Runnable()
        {
            @Override
            public void run()
            {
                if (mCount == 0)
                {
                    mIsForeground = false;
                }
            }
        }, BACKGROUND_CHECK_DELAY_MS);
    }

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

    }

    @Override
    public void onActivityResumed(final Activity activity)
    {

    }

    @Override
    public void onActivityPaused(final Activity activity)
    {

    }

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

    }

    @Override
    public void onActivityDestroyed(final Activity activity)
    {

    }
}