我正在尝试编写一个应用程序,当它在一段时间后回到前台时会执行特定的操作。有没有办法检测应用程序何时被发送到后台或被带到前台?
45 回答
2018:Android 通过生命周期组件原生支持这一点。
2018 年 3 月更新:现在有更好的解决方案。请参阅ProcessLifecycleOwner。您将需要使用新的架构组件 1.1.0(此时是最新版本),但它是专门为此而设计的。
这个答案中提供了一个简单的示例,但我写了一个示例应用程序和一篇关于它的博客文章。
自从我在 2014 年写这篇文章以来,出现了不同的解决方案。有些有效,有些被认为有效,但存在缺陷(包括我的!),作为一个社区(Android),我们学会了忍受后果并为特殊情况编写解决方法。
永远不要假设一个代码片段就是您正在寻找的解决方案,这不太可能;更好的是,尝试了解它的作用以及它为什么这样做。
我从来没有像这里写的那样真正使用过这个MemoryBoss
类,它只是一段碰巧起作用的伪代码。
除非您有正当理由不使用新的架构组件(并且有一些,特别是如果您针对超级旧的 API),否则请继续使用它们。它们远非完美,但也不是ComponentCallbacks2
。
更新/注释(2015 年 11 月):人们一直在发表两条评论,首先是>=
应该使用它,而不是==
因为文档声明您不应该检查确切的值。这在大多数情况下都很好,但请记住,如果您只关心在应用程序进入后台时执行某项操作,您将不得不使用 ==并将其与另一个解决方案(如 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);
}
}
我推荐 onStart 因为对话框可以暂停一个活动,所以我敢打赌,如果你所做的只是显示一个全屏对话框,你不希望你的应用认为“它进入了后台”,但你的里程可能会有所不同。
就这样。if 块中的代码只会执行一次,即使你去另一个活动,新的(也extends BaseActivity
)会报告wasInBackground
它false
不会执行代码,直到onMemoryTrimmed
被调用并且标志再次设置为 true .
希望有帮助。
更新/注释(2015 年 4 月):在对这段代码进行所有复制和粘贴之前,请注意,我发现了几个实例,它可能不是 100% 可靠的,必须与其他方法结合才能获得最佳结果。值得注意的是,有两个已知实例不能保证执行onTrimMemory
回调:
如果您的手机在您的应用程序可见时锁定了屏幕(比如您的设备在 nn 分钟后锁定),则不会调用此回调(或并非总是),因为锁屏只是在顶部,但您的应用程序仍在“运行”,尽管被覆盖。
如果您的设备内存相对较低(并且处于内存压力之下),操作系统似乎会忽略此调用并直接进入更关键的级别。
现在,根据您知道应用程序何时进入后台的重要性,您可能需要也可能不需要扩展此解决方案以及跟踪活动生命周期等。
请牢记以上几点,并拥有一支优秀的 QA 团队 ;)
更新结束
可能为时已晚,但Ice Cream Sandwich (API 14) 及更高版本中有一个可靠的方法。
事实证明,当您的应用不再可见 UI 时,会触发回调。您可以在自定义类中实现的回调称为ComponentCallbacks2(是的,有两个)。此回调仅在 API 级别 14(冰淇淋三明治)及更高版本中可用。
您基本上会调用该方法:
public abstract void onTrimMemory (int level)
等级为 20 或更高
public static final int TRIM_MEMORY_UI_HIDDEN
我一直在对此进行测试,并且它始终有效,因为级别 20 只是一个“建议”,您可能希望释放一些资源,因为您的应用程序不再可见。
引用官方文档:
onTrimMemory(int) 的级别:进程一直显示用户界面,现在不再显示。此时应释放 UI 的大量分配,以便更好地管理内存。
当然,您应该实现它以实际执行它所说的(清除在特定时间未使用的内存,清除一些未使用的集合等。可能性是无穷无尽的(有关其他可能的更多信息,请参阅官方文档)临界水平)。
但是,有趣的是,操作系统会告诉您:嘿,您的应用程序已进入后台!
这正是您首先想知道的。
你怎么确定你什么时候回来的?
这很简单,我确定你有一个“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
并if
实现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);
就是这样。
这是我设法解决这个问题的方法。它的工作前提是,在活动转换之间使用时间参考很可能会提供足够的证据证明应用程序是否已“后台”。
首先,我使用了一个 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() 事件中添加对这些方法中的每一个的调用,或者最好在所有具体活动都继承自的基础活动中添加对这些方法的调用:
@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();
}
因此,当用户只是在应用程序的 Activity 之间导航时,离开 Activity 的 onPause() 会启动计时器,但几乎立即进入的新 Activity 会在达到最大转换时间之前取消计时器。所以wasInBackground将是false。
另一方面,当 Activity 从 Launcher 进入前台、设备唤醒、结束电话通话等时,很可能是在此事件之前执行的计时器任务,因此wasInBackground被设置为true。
编辑:新的架构组件带来了一些有希望的东西:ProcessLifecycleOwner,请参阅@vokilam 的答案
根据Google I/O talk的实际解决方案:
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
}
}
}
是的。我知道很难相信这个简单的解决方案有效,因为我们这里有很多奇怪的解决方案。
但有希望。
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
.
使用此功能还需要dependencies
:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
当应用程序进入后台并再次进入前台时,将调用onPause()
and方法。onResume()
但是,它们也会在应用程序第一次启动时以及在它被终止之前被调用。您可以在Activity中阅读更多内容。
没有任何直接的方法可以在后台或前台获取应用程序状态,但即使我也遇到了这个问题,并使用 and 找到了解决onWindowFocusChanged
方案onStop
。
有关更多详细信息,请在此处查看Android:检测 Android 应用程序何时进入后台并在没有 getRunningTasks 或 getRunningAppProcesses 的情况下返回前台的解决方案。
基于 Martín Marconcinis 的回答(谢谢!),我终于找到了一个可靠(而且非常简单)的解决方案。
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;
}
}
}
然后将此添加到您的应用程序类的 onCreate()
public class MyApp extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
registerActivityLifecycleCallbacks(handler);
registerComponentCallbacks(handler);
}
}
我们使用这种方法。它看起来太简单了,但它在我们的应用程序中经过了很好的测试,实际上在所有情况下都运行得非常好,包括通过“主页”按钮、“返回”按钮或屏幕锁定后进入主屏幕。试试看。
想法是,在前台时,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<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 时,如果满足以下条件,应用程序将进入后台:
- 暂停的活动尚未完成(使用了“返回”按钮)。这可以通过使用方法 activity.isFinishing() 来完成
- 未启动新活动(相同的包名称)。您可以重写 startActivity() 方法来设置一个指示这一点的变量,然后在 onPostResume() 中将其重置,这是创建/恢复活动时运行的最后一个方法。
当您可以检测到应用程序已退出到后台时,很容易检测到它何时返回到前台。
创建一个扩展类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
}
}
考虑使用 onUserLeaveHint。这只会在您的应用程序进入后台时调用。onPause 将有一些特殊情况需要处理,因为它可以因其他原因被调用;例如,如果用户在您的应用程序中打开另一个活动,例如您的设置页面,您的主要活动的 onPause 方法将被调用,即使它们仍在您的应用程序中;当您可以简单地使用 onUserLeaveHint 回调来满足您的要求时,跟踪正在发生的事情将导致错误。
当调用 UserLeaveHint 时,您可以将布尔 inBackground 标志设置为 true。当 onResume 被调用时,如果设置了 inBackground 标志,只假设你回到了前台。这是因为如果用户只是在您的设置菜单中并且从未离开应用程序,那么您的主要活动也会调用 onResume。
请记住,如果用户在您的设置屏幕中点击主页按钮,onUserLeaveHint 将在您的设置活动中被调用,当他们返回时,onResume 将在您的设置活动中被调用。如果您的主要活动中只有此检测代码,您将错过此用例。要在您的所有活动中使用此代码而不重复代码,请拥有一个扩展 Activity 的抽象活动类,并将您的公共代码放入其中。然后,您拥有的每个活动都可以扩展此抽象活动。
例如:
public 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 {
...
}
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
ActivityLifecycleCallbacks可能很有趣,但没有很好的文档记录。
但是,如果您调用registerActivityLifecycleCallbacks () 您应该能够获取活动创建、销毁等时间的回调。您可以为活动调用getComponentName ()。
在您的应用程序中添加回调并以如下方式检查根活动:
@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();
}
}
});
}
您可以使用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");
}
}
然后在onCreate()
你的应用程序类中你称之为:
ProcessLifecycleOwner.get().getLifecycle().addObserver(new ForegroundLifecycleObserver());
ON_PAUSE
有了这个,您将能够捕获ON_STOP
应用程序在后台运行时发生的事件。
我在 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() 将在应用程序从任何屏幕进入后台时调用。
添加这些依赖项
implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"
在科特林:
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 })
}
没有直接的生命周期方法可以告诉您整个应用程序何时进入后台/前台。
我用简单的方法做到了这一点。按照以下说明检测应用程序背景/前景阶段。
通过一些解决方法,这是可能的。在这里,ActivityLifecycleCallbacks来救援。让我一步一步来。
首先,创建一个扩展android.app.Application并实现ActivityLifecycleCallbacks接口的类。在 Application.onCreate() 中,注册回调。
public class App extends Application implements Application.ActivityLifecycleCallbacks { @Override public void onCreate() { super.onCreate(); registerActivityLifecycleCallbacks(this); } }
在 Manifest 中注册“App”类,如下所示
<application android:name=".App"
。应用在前台时,至少有一个处于已启动状态的Activity,而在应用处于后台时,将没有处于已启动状态的Activity。
在“App”类中声明 2 个变量,如下所示。
private int activityReferences = 0; private boolean isActivityChangingConfigurations = false;
activityReferences
将保持已启动状态的活动数计数。isActivityChangingConfigurations
是一个标志,指示当前 Activity 是否正在经历配置更改,如方向切换。使用以下代码,您可以检测应用程序是否进入前台。
@Override public void onActivityStarted(Activity activity) { if (++activityReferences == 1 && !isActivityChangingConfigurations) { // App enters foreground } }
这是检测应用程序是否进入后台的方法。
@Override public void onActivityStopped(Activity activity) { isActivityChangingConfigurations = activity.isChangingConfigurations(); if (--activityReferences == 0 && !isActivityChangingConfigurations) { // App enters background } }
这个怎么运作:
这是按顺序调用生命周期方法的方式完成的一个小技巧。让我演练一个场景。
假设用户启动了 App,启动了 Launcher Activity A。生命周期调用将是,
A.onCreate()
A.onStart() (++activityReferences == 1) (App进入前台)
A.onResume()
现在活动 A 开始活动 B。
A.onPause()
B.onCreate()
B.onStart() (++activityReferences == 2)
B.onResume()
A.onStop() (--activityReferences == 1)
然后用户从活动 B 导航回来,
B.onPause()
A.onStart() (++activityReferences == 2)
A.onResume()
B.onStop() (--activityReferences == 1)
B.onDestroy()
然后用户按下主页按钮,
A.onPause()
A.onStop() (--activityReferences == 0) (应用进入后台)
万一,如果用户从 Activity B 按下 Home 按钮而不是 Back 按钮,它仍然是相同的并且 activityReferences 将是0
。因此,我们可以检测到应用程序进入后台。
那么,它的作用是isActivityChangingConfigurations
什么?在上述场景中,假设 Activity B 改变了方向。回调序列将是,
B.onPause()
B.onStop() (--activityReferences == 0) (应用进入后台??)
B.onDestroy()
B.onCreate()
B.onStart() (++activityReferences == 1) (App进入前台??)
B.onResume()
这就是为什么我们有一个额外的检查isActivityChangingConfigurations
来避免 Activity 正在经历配置更改的情况。
编辑 2:我在下面写的内容实际上不起作用。Google 拒绝了一个包含对 ActivityManager.getRunningTasks() 的调用的应用。从文档中可以看出,此 API 仅用于调试和开发目的。只要我有时间用一个使用计时器的新方案更新下面的 GitHub 项目,我就会更新这篇文章,而且几乎一样好。
编辑 1:我写了一篇博客文章并创建了一个简单的 GitHub 存储库,以使这变得非常容易。
接受和评价最高的答案都不是最好的方法。isApplicationBroughtToBackground() 的最高评价答案的实现不处理应用程序的主要活动让步给在同一应用程序中定义的活动的情况,但它具有不同的 Java 包。我想出了一种在这种情况下可行的方法。
在 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;
}
我找到了一种检测应用程序是否进入前台或后台的好方法。这是我的代码。希望这对您有所帮助。
/**
* 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();
}
您可以简单地在您的应用程序类中调用此方法
ProcessLifecycleOwner.get().getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
Log.e(TAG, "onStateChanged: " + event.toString());
}
});
Lifecycle.Event
将简单地返回应用程序的状态
ON_CREATE
ON_START
ON_RESUME
ON_PAUSE
ON_STOP
ON_DESTROY
ON_ANY
当应用程序进入后台时,它将返回 ON_PAUSE 和 ON_STOP,当应用程序进入前台时,它将返回 ON_START 和 ON_RESUME
我将它与 Google Analytics EasyTracker 一起使用,它确实有效。它可以扩展为使用简单的整数来完成您所寻求的工作。
public class MainApplication extends Application {
int isAppBackgrounded = 0;
@Override
public void onCreate() {
super.onCreate();
appBackgroundedDetector();
}
private void appBackgroundedDetector() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStart(activity);
}
@Override
public void onActivityResumed(Activity activity) {
isAppBackgrounded++;
if (isAppBackgrounded > 0) {
// Do something here
}
}
@Override
public void onActivityPaused(Activity activity) {
isAppBackgrounded--;
}
@Override
public void onActivityStopped(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStop(activity);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
正确答案在这里
创建名为 MyApp 的类,如下所示:
public class MyApp implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
private Context context;
public void setContext(Context context)
{
this.context = context;
}
private boolean isInBackground = false;
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
isInBackground = true;
Log.d("status = ","we are out");
}
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
if(isInBackground){
isInBackground = false;
Log.d("status = ","we are in");
}
}
@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() {
}
}
然后,在您想要的任何地方(在应用程序中启动更好的第一个活动),添加以下代码:
MyApp myApp = new MyApp();
registerComponentCallbacks(myApp);
getApplication().registerActivityLifecycleCallbacks(myApp);
完毕!现在当应用程序在后台时,我们会得到日志 status : we are out
,当我们进入应用程序时,我们会得到日志status : we are out
由于我没有找到任何方法,它也可以在不检查时间戳的情况下处理旋转,我想我也分享了我们现在在我们的应用程序中是如何做到这一点的。这个答案https://stackoverflow.com/a/42679191/5119746的唯一补充是,我们也考虑了方向。
class MyApplication : Application(), Application.ActivityLifecycleCallbacks {
// Members
private var mAppIsInBackground = false
private var mCurrentOrientation: Int? = null
private var mOrientationWasChanged = false
private var mResumed = 0
private var mPaused = 0
然后,对于回调,我们首先有简历:
// ActivityLifecycleCallbacks
override fun onActivityResumed(activity: Activity?) {
mResumed++
if (mAppIsInBackground) {
// !!! App came from background !!! Insert code
mAppIsInBackground = false
}
mOrientationWasChanged = false
}
和 onActivityStopped:
override fun onActivityStopped(activity: Activity?) {
if (mResumed == mPaused && !mOrientationWasChanged) {
// !!! App moved to background !!! Insert code
mAppIsInBackground = true
}
然后,添加了以下内容:检查方向更改:
override fun onConfigurationChanged(newConfig: Configuration) {
if (newConfig.orientation != mCurrentOrientation) {
mCurrentOrientation = newConfig.orientation
mOrientationWasChanged = true
}
super.onConfigurationChanged(newConfig)
}
而已。希望这可以帮助某人:)
我的解决方案受到@d60402 回答的启发,并且还依赖于时间窗口,但不使用Timer
:
public abstract class BaseActivity extends ActionBarActivity {
protected boolean wasInBackground = false;
@Override
protected void onStart() {
super.onStart();
wasInBackground = getApp().isInBackground;
getApp().isInBackground = false;
getApp().lastForegroundTransition = System.currentTimeMillis();
}
@Override
protected void onStop() {
super.onStop();
if( 1500 < System.currentTimeMillis() - getApp().lastForegroundTransition )
getApp().isInBackground = true;
}
protected SingletonApplication getApp(){
return (SingletonApplication)getApplication();
}
}
其中SingletonApplication
是Application
类的扩展:
public class SingletonApplication extends Application {
public boolean isInBackground = false;
public long lastForegroundTransition = 0;
}
您可以通过三种方式实现这一目标:
- 单一活动架构
- ActivityLifecycleCallback
- LifecycleObserver 和 ProcessLifecycleOwner
在这里详细写过一篇文章。希望能帮助到你。
我知道它有点晚了,但我认为所有这些答案确实存在一些问题,而我像下面那样做,而且效果很好。
创建一个活动生命周期回调,如下所示:
class ActivityLifeCycle implements ActivityLifecycleCallbacks{
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
Activity lastActivity;
@Override
public void onActivityResumed(Activity activity) {
//if (null == lastActivity || (activity != null && activity == lastActivity)) //use this condition instead if you want to be informed also when app has been killed or started for the first time
if (activity != null && activity == lastActivity)
{
Toast.makeText(MyApp.this, "NOW!", Toast.LENGTH_LONG).show();
}
lastActivity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
只需在您的应用程序类中注册它,如下所示:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifeCycle());
}
这是@d60402 答案的修改版本:https ://stackoverflow.com/a/15573121/4747587
做那里提到的一切。但是,不要将 aBase Activity
和作为每个活动的父级并覆盖onResume()
and onPause
,而是执行以下操作:
在您的应用程序类中,添加以下行:
registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks 回调);
这callback
具有所有活动生命周期方法,您现在可以覆盖onActivityResumed()
和onActivityPaused()
.
看看这个要点:https ://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b
在Activity
(或任何类)中使用ProcessLifecycleOwner
.
当应用程序启动时,我缓存启动时间,然后在每个活动中我将检查应用程序时间以了解活动是第一次启动还是从后台启动
class MyApplication : Application(), LifecycleObserver {
var appStartBeginTime: Long? = null
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
Log.i("TAG", "onMoveToForeground")
appStartBeginTime = System.currentTimeMillis()
}
}
LoginActivity
class LoginActivity : AppCompatActivity() {
var localAppStartBeginTime: Long? = null
...
// Detect in onResume() instead of onStart because
// onMoveToForeground() in MyApplication will fired before onStart
override fun onResume() {
super.onResume()
if (isOpenedFirstTimeOrFromBackground()) {
Log.i("TAG", "open first time or from background")
// do something: eg, call API
} else {
Log.i("TAG", "on in another time")
}
}
private fun isOpenedFirstTimeOrFromBackground(): Boolean {
val globalStartBeginTime = (application as MyApplication).appStartBeginTime
if (localAppStartBeginTime != globalStartBeginTime) {
localAppStartBeginTime = globalStartBeginTime
return true
}
return false
}
}
AndroidManifest
<manifest ...>
<application
android:name=".MyApplication"
...>
</application>
</manifest>
演示 https://github.com/PhanVanLinh/AndroidDetectAppFromBackgroundToForeground
这似乎是 Android 中最复杂的问题之一,因为(在撰写本文时)Android 没有 iOS 等效项applicationDidEnterBackground()
或applicationWillEnterForeground()
回调。我使用了一个由 @jenzz组合在一起的AppState库。
[AppState 是] 一个简单的、基于 RxJava 的响应式 Android 库,用于监控应用程序状态的变化。每次应用程序进入后台并返回前台时,它都会通知订阅者。
事实证明这正是我所需要的,特别是因为我的应用程序有多个活动,所以简单地检查onStart()
或检查onStop()
一个活动并不会削减它。
首先,我将这些依赖项添加到 gradle:
dependencies {
compile 'com.jenzz.appstate:appstate:3.0.1'
compile 'com.jenzz.appstate:adapter-rxjava2:3.0.1'
}
然后将这些行添加到代码中的适当位置很简单:
//Note that this uses RxJava 2.x adapter. Check the referenced github site for other ways of using observable
Observable<AppState> appState = RxAppStateMonitor.monitor(myApplication);
//where myApplication is a subclass of android.app.Application
appState.subscribe(new Consumer<AppState>() {
@Override
public void accept(@io.reactivex.annotations.NonNull AppState appState) throws Exception {
switch (appState) {
case FOREGROUND:
Log.i("info","App entered foreground");
break;
case BACKGROUND:
Log.i("info","App entered background");
break;
}
}
});
根据您订阅 observable 的方式,您可能必须取消订阅它以避免内存泄漏。再次在 github 页面上提供更多信息。
您可以在下面的帮助下轻松实现这一ActivityLifecycleCallbacks
目标ComponentCallbacks2
。
创建一个AppLifeCycleHandler
实现上述接口的类。
package com.sample.app;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.os.Bundle;
/**
* Created by Naveen on 17/04/18
*/
public class AppLifeCycleHandler
implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
AppLifeCycleCallback appLifeCycleCallback;
boolean appInForeground;
public AppLifeCycleHandler(AppLifeCycleCallback appLifeCycleCallback) {
this.appLifeCycleCallback = appLifeCycleCallback;
}
@Override
public void onActivityResumed(Activity activity) {
if (!appInForeground) {
appInForeground = true;
appLifeCycleCallback.onAppForeground();
}
}
@Override
public void onTrimMemory(int i) {
if (i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
appInForeground = false;
appLifeCycleCallback.onAppBackground();
}
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@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() {
}
interface AppLifeCycleCallback {
void onAppBackground();
void onAppForeground();
}
}
在您的类中扩展Application
实现AppLifeCycleCallback
以在应用程序在前台和后台之间切换时获取回调。像下面的东西。
public class BaseApplication extends Application implements AppLifeCycleHandler.AppLifeCycleCallback{
@Override
public void onCreate() {
super.onCreate();
AppLifeCycleHandler appLifeCycleHandler = new AppLifeCycleHandler(this);
registerActivityLifecycleCallbacks(appLifeCycleHandler);
registerComponentCallbacks(appLifeCycleHandler);
}
@Override
public void onAppBackground() {
Log.d("LifecycleEvent", "onAppBackground");
}
@Override
public void onAppForeground() {
Log.d("LifecycleEvent", "onAppForeground");
}
}
希望这可以帮助。
编辑 作为替代方案,您现在可以使用生命周期感知架构组件。
我们可以使用以下方法扩展此解决方案LiveData
:
class AppForegroundStateLiveData : LiveData<AppForegroundStateLiveData.State>() {
private var lifecycleListener: LifecycleObserver? = null
override fun onActive() {
super.onActive()
lifecycleListener = AppLifecycleListener().also {
ProcessLifecycleOwner.get().lifecycle.addObserver(it)
}
}
override fun onInactive() {
super.onInactive()
lifecycleListener?.let {
this.lifecycleListener = null
ProcessLifecycleOwner.get().lifecycle.removeObserver(it)
}
}
internal inner class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
value = State.FOREGROUND
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
value = State.BACKGROUND
}
}
enum class State {
FOREGROUND, BACKGROUND
}
}
现在我们可以订阅这个 LiveData 并捕获所需的事件。例如:
appForegroundStateLiveData.observeForever { state ->
when(state) {
AppForegroundStateLiveData.State.FOREGROUND -> { /* app move to foreground */ }
AppForegroundStateLiveData.State.BACKGROUND -> { /* app move to background */ }
}
}
我所做的是确保启动所有应用内活动,startActivityForResult
然后检查是否在 onResume 之前调用了 onActivityResult。如果不是,这意味着我们刚刚从应用程序之外的某个地方返回。
boolean onActivityResultCalledBeforeOnResume;
@Override
public void startActivity(Intent intent) {
startActivityForResult(intent, 0);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
onActivityResultCalledBeforeOnResume = true;
}
@Override
protected void onResume() {
super.onResume();
if (!onActivityResultCalledBeforeOnResume) {
// here, app was brought to foreground
}
onActivityResultCalledBeforeOnResume = false;
}
这些答案似乎并不正确。当另一个活动开始和结束时,也会调用这些方法。您可以做的是保留一个全局标志(是的,全局变量很糟糕:)并在每次开始新活动时将其设置为 true。在每个活动的 onCreate 中将其设置为 false。然后,在 onPause 中检查这个标志。如果它是假的,你的应用程序将进入后台,或者它被杀死。
基本上涉及使用计时器计算所有 Activity 的生命周期方法,以捕捉当前在前台没有活动但应用程序在(即轮换)的情况
通过使用下面的代码,我可以获得我的应用程序前台或后台状态。
有关其工作的更多详细信息,强文本请单击此处
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private Context context;
private Toast toast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
}
private void showToast(String message) {
//If toast is already showing cancel it
if (toast != null) {
toast.cancel();
}
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
toast.show();
}
@Override
protected void onStart() {
super.onStart();
showToast("App In Foreground");
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
showToast("App In Background");
}
}
}
这是通过使用去抖动逻辑的解决方案,确保我们不会获得连续的背景/前景事件。因此,它始终反映背景/前景的稳定状态。
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import java.util.Timer
import java.util.TimerTask
/**
* An observer class to listen on the app's lifecycle.
*/
class AppLifecycleObserver(
private val onAppGoesToBackground: () -> Unit = {},
private val onAppEntersForeground: () -> Unit = {}
) : LifecycleEventObserver {
private val debounce = DebouncingTimer(timeout = 10)
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
debounce.refresh {
when (event.targetState) {
Lifecycle.State.CREATED -> onAppGoesToBackground()
Lifecycle.State.RESUMED -> onAppEntersForeground()
else -> Unit
}
}
}
fun attach() {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
fun detach() {
ProcessLifecycleOwner.get().lifecycle.removeObserver(this)
}
private class DebouncingTimer(private val timeout: Long) {
private var timer: Timer? = null
fun refresh(job: () -> Unit) {
timer?.cancel()
timer = Timer()
timer?.schedule(object : TimerTask() {
override fun run() = job.invoke()
}, timeout)
}
}
}
只需要创建一个实例AppLifecycleObserver
:
private val appLifecycleObserver = AppLifecycleObserver(
onAppGoesToBackground = { // do whatever... },
onAppEntersForeground = { // do whatever... }
)
// Attach the observer when it is needed:
appLifecycleObserver.attach()
// Remove when there is no need to it:
appLifecycleObserver.detach()
不要忘记添加正确版本的依赖项:
implementation("androidx.lifecycle:lifecycle-process:$lifecycle_version")
这是我的解决方案。只需在您的主应用程序类中注册此 ActivityLifecycleCallbacks。在评论中,我提到了一个用户配置文件 Activity 边缘案例。该活动只是一个具有透明边缘的活动。
/**
* 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)
{
}
}
我喜欢这种ProcessLifecycleOwner
方法,但实际上可以跳过所有这些,因为在Activity
'sonCreate()
方法中,可以轻松确定它是第一次运行还是后续运行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
/* savedInstanceState is always null on first run */
} else {
/* it's a subsequent run */
}
}
这个解决方案怎么样
public class BaseActivity extends Activity
{
static String currentAct = "";
@Override
protected void onStart()
{
super.onStart();
if (currentAct.equals(""))
Toast.makeText(this, "Start", Toast.LENGTH_LONG).show();
currentAct = getLocalClassName();
}
@Override
protected void onStop()
{
super.onStop();
if (currentAct.equals(getLocalClassName()))
{
currentAct = "";
Toast.makeText(this, "Stop", Toast.LENGTH_LONG).show();
}
}
}
所有 Activity 都需要扩展 BaseActivity。
当一个活动调用另一个 (A->B) 时 currentAct 不等于 getLocalClassName() 因为第二个活动 (B) 的 onStart() 在第一个 (A) 的 onStop() 之前被调用 ( https://developer .android.com/guide/components/activities.html#CoordinatingActivities)。
当用户按下主页按钮或在应用程序之间切换时,只会调用 onStop(),然后 currentAct 等于 getLocalClassName()。
我通过实现一个利用 onResume、onPause 和 onStop 活动回调的 BaseActivity 来监控应用程序导航到后台和返回到前台。这是我的实现。
override fun onResume() {
super.onResume()
if (AppActivityState.state == AppState.ON_LAUNCHED) {
// We are in the first launch.
onLaunched()
} else {
if (AppActivityState.state == AppState.ON_BACKGROUND) {
// We came from background to foreground.
AppActivityState.state = AppState.ON_FOREGROUND
onForeground()
} else {
// We are just navigating through pages.
AppActivityState.state = AppState.RESUMED
}
}
}
override fun onPause() {
super.onPause()
// If state is followed by onStop then it means we will going to background.
AppActivityState.state = AppState.PAUSED
}
override fun onStop() {
super.onStop()
// App will go to background base on the 'pause' cue.
if (AppActivityState.state == AppState.PAUSED) {
AppActivityState.state = AppState.ON_BACKGROUND
onBackground()
}
}
创建 BaseActivity 后,您只需将此活动扩展到应用程序上的任何活动。
在这些类型的实现中,您可以准确地检测到以下内容: - onBackground > app 将转到后台 - onForeground > app 将回到前台 - onLaunch > app 刚刚打开
我希望这能帮到您 :)
我的应用程序需要在从后台返回后“重新启动” - 根据客户请求显示一系列活动。在广泛搜索如何管理背景/前景转换(iOS 和 Android 之间的处理方式非常不同)之后,我遇到了这个问题。在这里找到了非常有用的帮助,特别是来自投票最多的答案和标记为正确的答案。但是,当您考虑 UX 时,每次应用程序进入前台时都简单地重新实例化根活动看起来太烦人了。对我有用的解决方案,也是我认为最合适的解决方案 - 基于 Youtube 和 Twitter 应用程序功能 - 是结合来自 @GirishNair 和 @d60402 的答案:在应用程序修剪内存时调用计时器,如下所示:
@Override
public void onTrimMemory(int level) {
if (stateOfLifeCycle.equals("Stop")) {
startActivityTransitionTimer();
}
super.onTrimMemory(level);
}
我的计时器限制设置为 30 秒 - 我正在考虑增加一点。
private final long MAX_ACTIVITY_TRANSITION_TIME = 30000;
并且当应用程序进入前台、重新启动或应用程序被销毁时,调用该方法取消计时器。
在应用程序扩展上:
@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();
}
}
就我而言,当应用程序在最大时间之后进入前台时,会创建一个新任务,因此在应用程序扩展类中的 onActivityCreated() 或 onActivityDestroyed() 上调用 stopActivityTransitionTimer() - 无需在活动中调用该方法. 希望能帮助到你。
我正在使用这个解决方案:http: //nathanael.hevenet.com/android-dev-detecting-when-your-app-is-in-the-background-across-activities/
简而言之-构建一个专用服务,每个活动都会向他报告每个生命周期事件,并且该服务会获取有关应用程序状态的信息。
非常像@oldschool4664 解决方案,但在我看来更干净
最简单的方式(不需要额外的库)
科特林:
var handler = Handler()
var isAppInBackground = true
override fun onStop() {
super.onStop()
handler.postDelayed({ isAppInBackground = true },2000)
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
isAppInBackground = false
}
爪哇:
Handler handler = new Handler();
boolean isAppInBackground = true;
@Override
public void onStop() {
super.onStop();
handler.postDelayed(() -> { isAppInBackground = true; },2000);
}
@Override
public void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
isAppInBackground = false;
}
主要问题是当您从后台启动活动时,您必须获得特定的行为。如果您覆盖 onPause() 和 onResume() 方法,您将得到一个接近的答案,但不是解决方案。问题是 onPause() 和 onResume() 方法被调用,即使你没有最小化你的应用程序,它们可以在你开始一个活动时被调用,然后你按下后退按钮返回你的活动。要消除该问题并真正了解您的应用程序何时来自后台,您必须获取正在运行的进程并与您的进程进行比较:
private boolean isApplicationBroughtToBackground() {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasks = am.getRunningTasks(1);
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
if (!topActivity.getPackageName().equals(getPackageName())) {
return true;
}
}
return false;
}
现在你必须声明一个布尔变量:
public boolean wasPaused = false;
并询问您的活动何时进入后台:
@Override
public void onPause(){
super.onPause();
if(isApplicationBroughtToBackground())
wasPaused = true;
}
现在,当您的活动再次出现在屏幕上时,请在 onResume() 方法中询问:
@Override
public void onResume(){
super.onResume();
if(wasPaused){
lockScreen(true);
}
wasPaused = false;
}
就是这样。现在,当您的活动进入后台,然后用户将其带到前台时,将出现锁定屏幕。
如果您想对应用程序的任何活动重复此行为,则必须创建一个活动(可能是 BaseActivity),放置此方法,并且您的所有活动都必须从 BaseActivity 继承。
我希望这对你有所帮助。
问候!