我正在尝试编写一个应用程序,在一段时间后恢复到前台时会执行特定操作。是否有一种方法可以检测应用程序何时被发送到后台或恢复到前台?
我正在尝试编写一个应用程序,在一段时间后恢复到前台时会执行特定操作。是否有一种方法可以检测应用程序何时被发送到后台或恢复到前台?
2018年3月更新:现在有更好的解决方案。请参见ProcessLifecycleOwner。您需要使用新的架构组件1.1.0(目前最新版本),但它是专门为此设计的。
这个答案中提供了一个简单的示例in this answer,但我也写了一个sample app和一个blog post。
自从我在2014年写下这篇文章以来,不同的解决方案出现了。有些有效,有些被认为有效,但存在缺陷(包括我的!),我们作为一个社区(Android)学会了应对后果,并为特殊情况编写了解决方法。
不要假设一小段代码就是你正在寻找的解决方案,这很不可能;最好的方法是尝试理解它的作用和原因。
MemoryBoss
类在我这里实际上从未被使用,它只是一段假代码,碰巧能够工作。
除非你有充分的理由不使用新的架构组件(确实有一些,特别是如果你针对超老的API),否则请使用它们。它们远非完美,但ComponentCallbacks2
也不是。
>=
而不是==
,因为文档指出你不应检查确切的值。这对大多数情况来说都是可以的,但要记住,如果你只关心在应用程序进入后台时执行某些操作,你将不得不使用==
并且还结合另一个解决方案(如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);
}
}
public abstract void onTrimMemory (int level)
public static final int TRIM_MEMORY_UI_HIDDEN
它起作用。您不会得到错误的阳性反应。如果活动正在恢复,则每次都会回到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()
方法,并且在进入后台时不会被调用,但您应该使用它来减少内存占用。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);
就是这样。
level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
,这避免了你更新的问题中的第二点。关于第一点,对我来说并不是一个问题,因为应用程序实际上并没有到后台,所以这就是它应该工作的方式。 - sorianiv以下是我解决此问题的方法。其基本原理是,使用活动转换之间的时间参考将很可能提供足够的证据来判断应用程序是否已“进入后台”。
首先,我使用了一个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。
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_START
、ON_RESUME
事件。当最后一个 Activity 完成这些事件后,ON_PAUSE
、ON_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"
google()
)添加生命周期依赖项implementation "android.arch.lifecycle:extensions:1.0.0"
和annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
。 - Sir Codesalot@SirCodesalot
在 2.2.0
版本中不是必需的。 - artem编辑:新的架构组件带来了一些有前途的东西:ProcessLifecycleOwner,请参见@vokilam的答案
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
}
}
}
是的,我知道这个简单的解决方案很难让人相信它能起作用,因为我们在这里有很多奇怪的解决方案。
然而还是有希望的。
onPause()
和onResume()
方法在应用程序进入后台和重新进入前台时调用。但是,它们在应用程序首次启动并在被杀死之前也会被调用。您可以在 Activity 中了解更多信息。
没有直接的方法可以在后台或前台获取应用程序状态,但我也遇到了这个问题,并通过使用 onWindowFocusChanged
和 onStop
来解决。
有关更多详情,请参见此处:Android:无需 getRunningTasks 或 getRunningAppProcesses 就可以检测 Android 应用何时进入后台并返回前台的解决方案。
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);
}
}
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 调用,因为这在我的初始帖子中缺失了,因为这更多是一个概念而不是一个可工作的代码。
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时,如果满足以下条件,应用程序就会被置于后台:
当你可以检测到应用程序已经退到后台时,很容易检测到它何时被带回前台。
@Override
public void onUserLeaveHint() {
inBackground = isApplicationBroughtToBackground();
}
- listing boat创建一个继承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
}
}
FragmentActivity
,您可能还想添加 level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE
。 - Srujan Simhapublic 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 {
...
}