如何检测Android应用程序何时进入后台并回到前台

我正在尝试编写一个应用程序,在经过一段时间之后将它带回到前台时执行某些特定的操作。 有没有办法来检测应用程序何时发送到后台或带到前台?

onPause()onResume()方法在应用程序被带到后台并再次进入前台时被调用。 但是,当应用程序第一次启动时,它们也会被调用。 你可以阅读更多的活动

在后台或前台没有任何直接获取应用程序状态的方法,但是即使我遇到了这个问题,也find了使用onWindowFocusChangedonStop的解决方案。

有关更多详细信息,请点击此处Android:解决方案,用于检测Android应用程序何时进入后台,并在没有getRunningTasks或getRunningAppProcesses的情况下回到前台

以下是我已经设法解决这个问题。 它的工作原理是,在活动转换之间使用时间参考很可能会提供足够的证据,表明应用程序已经“后台”。

首先,我使用了一个android.app.Application实例(我们称之为MyApplication),它有一个Timer,一个TimerTask,一个常量来表示从一个活动到另一个活动的合理转换的最大毫秒数(我去了值为2s)和一个布尔值来指示应用程序是否在“后台”:

 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()事件向这些方法中的每一个添加调用,或者最好在所有具体活动inheritance的基本Activity中添加一个调用:

 @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()会启动计时器,但几乎立即进入的新活动会在计时器达到最大转换时间之前取消该计时器。 所以背景错的

另一方面,当一个活动来自Launcher的前台,设备唤醒,结束电话等等,更可能是在这个事件之前执行的定时器任务,因此wasInBackground被设置为true

警告:如果您正在开发ICE CREAM SANDWICH(API LEVEL 14)或更高版本,那么这样做是非常好的。

更新/说明(2015年11月) :人们一直在做两个评论,首先是>=应该被用来代替==因为文档指出你不应该检查确切的值 。 这在大多数情况下都可以,但请记住,如果您只是在应用程序转到后台时执行某些操作 ,则必须使用== 并将其与另一个解决方案(如“Activity生命周期”回调)结合使用,否则可能无法达到您想要的效果。 这个例子(这发生在我身上)是,如果你想用密码屏幕锁定你的应用程序,当它走到后台(如1Password,如果你熟悉它),你可能会意外地锁定你的应用程序,如果你跑低在内存中,并突然测试>= TRIM_MEMORY ,因为Android会触发一个LOW MEMORY调用,这比你的更高。 所以要小心如何/你测试什么。

另外,有些人在回来的时候询问如何检测。

我能想到的最简单的方法解释如下,但由于有些人不熟悉它,我在这里添加一些伪代码。 假设你有YourApplicationMemoryBoss类,在你的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块中的代码只会执行一次 ,即使你去了另一个活动,新的(也extends BaseActivity )会报告wasInBackgroundfalse所以它不会执行代码, 直到onMemoryTrimmed被调用,标志再次设置为真

希望有所帮助。

更新/说明(2015年4月) :在对此代码执行全部复制和粘贴之前,请注意,我发现有几个实例可能不是100%可靠, 必须与其他方法结合才能取得最佳结果。 值得注意的是,有两个已知的实例不能保证onTrimMemory回调被执行:

  1. 如果您的手机在应用程序可见的情况下锁定了屏幕(例如在nn分钟后您的设备锁定),则不会调用此回调(或不总是),因为锁定屏幕位于顶部,但您的应用程序仍处于“运行”状态。

  2. 如果您的设备内存相对较低(并且内存不足),操作系统似乎会忽略此调用并直接进入更关键的级别。

现在,取决于您知道应用程序何时进入后台有多重要,您可能需要也可能不需要将此解决方案一起扩展,并跟踪活动的生命周期等等。

只要牢记上述内容,并有一个良好的QA团队;)

更新结束

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

原来,当你的应用程序没有更多的可见的用户界面,一个回调被触发。 可以在自定义类中实现的回调称为ComponentCallbacks2 (是的,有两个)。 此回调仅适用于API Level 14(冰淇淋三明治)及以上版本。

你基本上可以调用这个方法:

 public abstract void onTrimMemory (int level) 

等级是20或更具体的

 public static final int TRIM_MEMORY_UI_HIDDEN 

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

引用官方文档:

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

当然,你应该实现这一点,以实际上做它所说的(清除在一定时间内没有使用的内存,清除一些未使用的集合等等。可能性是无止境的(请参阅其他可能的更多的官方文档) 临界水平)。

但是,有趣的是,操作系统告诉你:嘿,你的应用程序去背景!

这正是你想要知道的第一位。

你如何确定你什么时候回来?

这很简单,我相信你有一个“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你可以添加一个else来实现ComponentCallbacks (不包含2),它用在API 14以下的任何东西中。这个回调只有onLowMemory()方法, 当你到达后台时不会被调用。你应该用它来修剪内存。

现在启动你的应用程序并按回家。 你的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); 

就是这样。

编辑:新的架构组件带来了一些有希望的: ProcessLifecycleOwner ,请参阅@ vokilam的答案


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

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

是。 我知道很难相信这个简单的解决方案,因为我们有这么多奇怪的解决方案在这里。

但是有希望。

我们使用这种方法。 它看起来太简单了,但是它在我们的应用程序中经过了充分的测试,实际上在所有情况下效果都非常好,包括通过“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()在更高版本的代码。 此外,我添加了超级调用,这是从我的初始职位缺少,因为这是一个更多的概念,而不是一个工作的代码。

如果你的应用程序由多个活动和/或堆叠活动(如标签栏小部件)组成,则重写onPause()和onResume()将不起作用。 也就是说,当开始一个新的活动时,当前的活动将在创建新活动之前暂停。 完成(使用“后退”按钮)活动时也是如此。

我发现两种方法似乎按照要求工作。

第一个需要GET_TASKS权限,包含一个简单的方法,通过比较包名来检查设备上的最高运行活动是否属于应用程序:

 private boolean isApplicationBroughtToBackground() { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List 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)框架中find。

我已经实现了自我的第二个方法不需要GET_TASKS权限,这是很好的。 相反,实施起来要复杂一点。

在你的MainApplication类中,你有一个跟踪应用程序中运行活动的数量的variables。 在onResume()为每个活动你增加variables,并在onPause()你减少它。

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

  • 正在暂停的活动未完成(使用“后退”按钮)。 这可以通过使用方法activity.isFinishing()
  • 一个新的活动(相同的包名称)没有启动。 你可以重载startActivity()方法来设置一个variables来表示这个variables,然后在onPostResume()中重置它,这是创建/恢复活动的最后一个方法。

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

基于马丁Marconcinis答案(谢谢!)我终于find了一个可靠的(和非常简单)的解决方案。

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

创建一个扩展Application 。 然后在其中,我们可以使用它的覆盖方法onTrimMemory()

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

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