我正在尝试编写一个应用程序,它会在经过一段时间后返回到前台时执行某些特定操作。有没有办法检测应用程序何时发送到后台或带到前台?
当应用程序被带到后台并再次进入前台时,将调用onPause()
和onResume()
方法。但是,当应用程序第一次启动并且在它被杀死之前,它们也会被调用。您可以在Activity中阅读更多内容。
在后台或前台没有任何直接的方法来获取应用程序状态,但即使我遇到了这个问题,并找到了onWindowFocusChanged
和onStop
的解决方案。
考虑使用onUserLeaveHint。这只会在您的应用进入后台时调用。 onPause将有角落案件要处理,因为它可以出于其他原因被调用;例如,如果用户在您的应用中打开了另一项活动(例如您的设置页面),即使它们仍在您的应用中,您的主要活动的onPause方法也会被调用;当你可以简单地使用onUserLeaveHint回调来跟踪你所要求的内容时,跟踪进入的内容会导致错误。
在调用UserLeaveHint时,可以将boolean 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 {
...
}
ActivityLifecycleCallbacks可能是有意义的,但它没有很好的记录。
但是,如果你调用registerActivityLifecycleCallbacks(),你应该可以在创建,销毁等活动时获得回调。你可以为活动调用getComponentName()。
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
在您的应用程序中添加回调并检查root活动,方式如下:
@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();
}
}
});
}
我在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()。
使用ProcessLifecycleOwner非常容易
添加这些依赖项
implementation "android.arch.lifecycle:extensions:$project.archLifecycleVersion"
kapt "android.arch.lifecycle:compiler:$project.archLifecycleVersion"
在Kotlin:
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 })
}
我找到了一种很好的方法来检测应用程序是否进入前景或后台。这是我的code。希望这对你有所帮助。
/**
* 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();
}
编辑2:我在下面写的内容实际上不起作用。谷歌拒绝了一个包含对ActivityManager.getRunningTasks()的调用的应用程序。从the documentation可以看出,该API仅用于调试和开发目的。一旦我有时间用一个使用计时器的新方案更新下面的GitHub项目,我将立即更新这篇文章,并且几乎一样好。
编辑1:我写了一个blog post并创建了a simple GitHub repository以使其变得非常容易。
被接受且评价最高的答案都不是最好的方法。最受好评的答案的isApplicationBroughtToBackground()实现不能处理Application的主Activity对同一Application中定义的Activity产生影响的情况,但它有一个不同的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;
}
当整个应用程序进入后台/前台时,没有简单的生命周期方法可以告诉您。
我用简单的方法做到了这一点。按照以下说明检测应用程序背景/前景阶段。
通过一些解决方法,它是可能的。在这里,ActivityLifecycleCallbacks来救援。让我一步一步走。
public class App extends Application implements
Application.ActivityLifecycleCallbacks {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
}
<application android:name=".App"
。private int activityReferences = 0;
private boolean isActivityChangingConfigurations = false;
activityReferences
将保持活动数量的计数。 isActivityChangingConfigurations
是一个标志,用于指示当前活动是否正在进行配置更改,如方向切换。@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
}
}
这个怎么运作:
这是按顺序调用Lifecycle方法的方法。让我来看看一个场景。
假设用户启动了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()
然后用户按下Home按钮,
A.onPause()
A.onStop()( - activityReferences == 0)(应用程序进入后台)
如果用户按下活动B而不是后退按钮的主页按钮,它仍然是相同的,activityReferences将是0
。因此,我们可以检测到应用程序进入后台。
那么,isActivityChangingConfigurations
的作用是什么?在上面的场景中,假设活动B更改方向。回调序列将是,
B.onPause()
B.onStop()( - activityReferences == 0)(应用程序进入后台??)
B.onDestroy()
B.onCreate()
B.onStart()(++ activityReferences == 1)(App进入Foreground ??)
B.onResume()
这就是我们对isActivityChangingConfigurations
进行额外检查以避免Activity在进行Configuration更改时的情况的原因。
2018年3月更新:现在有一个更好的解决方案。见ProcessLifecycleOwner。您将需要使用新的体系结构组件1.1.0(目前最新),但它是专门为此而设计的。
有一个简单的样本提供in this answer但我写了一个sample app和一个blog post关于它。
自从我在2014年写这篇文章以来,出现了不同的解决方案。有些工作,有些被认为是有效的,但有缺陷(包括我的!)我们,作为一个社区(Android)学会了忍受后果并为特殊情况编写解决方法。
永远不要假设一个代码片段是您正在寻找的解决方案,情况不太可能;更好的是,尝试了解它的作用以及它为什么这样做。
MemoryBoss
类从来没有像我这里所描述的那样实际使用过,它只是一段伪代码,恰好起作用。
除非你有充分的理由不使用新的架构组件(并且有一些,特别是如果你的目标是超级旧的api),那么继续使用它们。他们远非完美,但ComponentCallbacks2
也不是。
更新/注释(2015年11月):人们一直在发表两条评论,首先是应该使用>=
而不是==
,因为文档说明你不应该检查确切的值。这对大多数情况都很好,但请记住,如果你只关心在应用程序进入后台时做某事,你将不得不使用==并将其与另一个解决方案(如Activity Lifecycle回调)结合使用,或者你可能达不到你想要的效果。这个例子(这件事发生在我身上)是,如果你想在后台使用密码屏幕锁定你的应用程序(如果你熟悉它就像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,因为Dialogs可以暂停一个活动,所以我打赌你不希望你的应用程序认为“它转到后台”,如果你所做的只是显示全屏对话框,但你的里程可能会有所不同。
就这样。 if块中的代码只会被执行一次,即使你去另一个活动,新的(也是extends BaseActivity
)将报告wasInBackground
是false
所以它不会执行代码,直到调用onMemoryTrimmed
并且标志是再次设置为true。
希望有所帮助。
更新/注释(2015年4月):在您对此代码进行全部复制和粘贴之前,请注意我发现了几个可能不是100%可靠的实例,必须与其他方法结合才能获得最佳结果。值得注意的是,有两个已知的实例,其中onTrimMemory
回调不能保证执行:
现在,根据您知道应用程序何时进入后台的重要性,您可能需要也可能不需要将此解决方案扩展到跟踪活动生命周期等等。
请记住以上内容并拥有一支优秀的QA团队;)
更新结束
可能会迟到,但在冰淇淋三明治(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.
}
}
为了使用它,在你的应用程序实现中(你有一个,RIGHT?),做类似的事情:
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);
就是这样。
正确答案在这里
创建名为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);
完成!现在当应用程序在后台时,我们得到log status : we are out
,当我们进入应用程序时,我们得到log status : we are out
我的解决方案的灵感来自于@ 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;
}
我在谷歌分析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) {
}
});
}
}
我知道它有点晚了,但我认为所有这些答案确实有一些问题,而我在下面这样做,这是完美的。
创建一个活动生命周期回调,如下所示:
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());
}
这似乎是Android中最复杂的问题之一(截至本文撰写时)Android没有iOS等价的applicationDidEnterBackground()
或applicationWillEnterForeground()
回调。我使用了由AppState Library组装的@jenzz。
[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 page的更多信息。
这是@ d60402答案的修改版本:https://stackoverflow.com/a/15573121/4747587
做那里提到的一切。但是,不要使用Base Activity
并将其作为每项活动的父母,而是覆盖onResume()
和onPause
,请执行以下操作:
在您的应用程序类中,添加以下行:
registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback);
这个callback
具有所有活动生命周期方法,你现在可以覆盖onActivityResumed()
和onActivityPaused()
。
看看这个要点:https://gist.github.com/thsaravana/1fa576b6af9fc8fff20acfb2ac79fa1b
你可以在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");
}
}
希望这可以帮助。
编辑作为替代方案,您现在可以使用生命周期感知架构组件。
由于我没有找到任何方法,它也处理轮换而没有检查时间戳,我想我也分享了我们现在如何在我们的应用程序中执行此操作。这个答案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)
}
而已。希望这有助于某人:)
这些答案似乎不正确。当另一个活动开始和结束时,也会调用这些方法。你可以做的是保持一个全局标志(是的,全局变量是坏:)并在每次开始一个新活动时将其设置为true。在每个活动的onCreate中将其设置为false。然后,在onPause中检查此标志。如果它是假的,你的应用程序将进入后台,否则它将被杀死。
我所做的是确保使用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;
}
以下是我设法解决这个问题的方法。它的前提是在活动转换之间使用时间参考很可能提供足够的证据证明应用程序已经“背景化”。
首先,我使用了一个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();
}
因此,当用户只是在您的应用程序的活动之间导航时,离开活动的onPause()会启动计时器,但几乎立即输入的新活动会取消计时器,然后才能达到最大转换时间。所以wasInBackground会是假的。
另一方面,当一个Activity从Launcher到达前台时,设备唤醒,结束电话呼叫等,很可能在此事件之前执行了计时器任务,因此wasInBackground被设置为true。
基本上涉及使用计时器计算所有Activity的生命周期方法,以捕获前景中当前没有活动但应用程序是(即轮换时)的情况
编辑:新的架构组件带来了一些有希望的东西:ProcessLifecycleOwner,请参阅@vokilam's answer
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
}
}
}
是。我知道很难相信这个简单的解决方案是有效的,因为我们这里有很多奇怪的解决方案。
但是有希望。
ProcessLifecycleOwner
似乎也是一个很有前景的解决方案。
ProcessLifecycleOwner将调度
ON_START
,ON_RESUME
事件,作为第一个活动在这些事件中移动。ON_PAUSE
,ON_STOP
,事件将在最后一次活动通过后延迟发送。此延迟足以保证ProcessLifecycleOwner
在由于配置更改而销毁和重新创建活动时不会发送任何事件。
实现可以很简单
public class AppLifecycleListener implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onMoveToForeground() {
// app moved to foreground
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onMoveToBackground() {
// app moved to background
}
}
// register observer
ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleListener());
根据源代码,当前延迟值为700ms
。
使用此功能还需要dependencies
:
implementation "android.arch.lifecycle:extensions:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
根据MartínMarconcinis的回答(谢谢!)我终于找到了一个可靠(非常简单)的解决方案。
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);
}
}
我们使用这种方法。它看起来太简单了,但它在我们的应用程序中经过了充分测试,实际上在所有情况下工作都非常出色,包括通过“主页”按钮,“返回”按钮或屏幕锁定后进入主屏幕。试试看。
想法是,当处于前台时,Android总是在停止前一个活动之前启动新活动。这不是保证,但它是如何工作的。 BTW,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时,如果满足以下条件,则将应用程序置于后台:
当您可以检测到应用程序已退回到后台时,很容易检测到它何时返回到前台。
创建一个扩展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
}
}