按HOME后重新启动应用程序时如何返回最新启动的活动?

时间:2022-04-29 02:22:32

Familiar scenario: I have a Main activity that launches a Game activity when a button is pressed. If the user presses HOME, and then launches my application again, it should be presented with the Game activity, which is what he was doing last when using the application.

熟悉的场景:我有一个Main活动,在按下按钮时启动游戏活动。如果用户按下HOME,然后再次启动我的应用程序,则应该显示Game活动,这是他在使用该应用程序时最后所做的事情。

However, what happens instead is he gets the Main activity again. I have the feeling that Android is creating another instance of MainActivity and adding it to the stack for that application, instead of just picking whatever was on the top, because if I press BACK after relaunching the app, I get to the Game activity! And the Main.onCreate method is called every time, instead of calling GameActivity.onResume.

然而,相反的是他再次获得Main活动。我觉得Android正在创建另一个MainActivity实例并将其添加到该应用程序的堆栈中,而不是只选择顶部的任何内容,因为如果我在重新启动应用程序后按BACK,我会进入Game活动!并且每次调用Main.onCreate方法,而不是调用GameActivity.onResume。

My AndroidManifest.xml is pretty much 'bare bones':

我的AndroidManifest.xml几乎是“骨头”:

<activity android:name="MainActivity" android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="GameActivity" android:label="@string/app_name">
</activity>

As you can see, nothing too fancy.

如你所见,没有什么太花哨的。

And this is how the new activity is launched, very simple too:

这就是新活动的推出方式,也非常简单:

Intent intent = new Intent(this, GameActivity.class);
startActivity(intent);

In theory this should work in Android just "out of the box", as the answer to a very similar question says: Maintaining standard application Activity back stack state in Android (using singleTask launch mode), but it isn't.

从理论上讲,这应该在Android中起到“开箱即用”的作用,因为对一个非常相似的问题的回答是:在Android中维护标准应用程序活动后备堆栈状态(使用singleTask启动模式),但事实并非如此。

I've been reading and re-reading the documentation on Activity and Task and Stacks and browsing all related answers in SO but I can't understand why such a simple setup is not quite working as expected.

我一直在阅读和重读有关活动和任务和堆栈的文档,并在SO中浏览所有相关的答案,但我不明白为什么这么简单的设置不能按预期工作。

6 个解决方案

#1


27  

    @Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { 
        // Activity was brought to front and not created, 
        // Thus finishing this will get us to the last viewed activity 
        finish(); 
        return; 
    } 

    // Regular activity creation code... 
} 

#2


18  

Oh, I think I've found the answer.

哦,我想我找到了答案。

Because I was launching the app by using IntelliJ, it seems it launches the application in a different way than a user, clicking a home screen widget, would launch. It's explained in the answer to another SO question:

因为我是使用IntelliJ启动应用程序,所以它似乎以不同于用户的方式启动应用程序,单击主屏幕小部件,将启动。在另一个SO问题的答案中解释了这一点:

This is due to the intents being used to start the app being different. Eclipse starts an app using an intent with no action and no category. The Launcher starts an app using an intent with android.intent.action.MAIN action and android.intent.category.LAUNCHER category. The installer starts an app with the android.intent.action.MAIN action and no category.

这是因为用于启动应用程序的意图不同。 Eclipse使用没有操作且没有类别的intent启动应用程序。 Launcher使用android.intent.action.MAIN动作和android.intent.category.LAUNCHER类别的intent启动应用程序。安装程序使用android.intent.action.MAIN操作启动应用程序,没有类别。

Ref: App always starts fresh from root activity instead of resuming background state (Known Bug)

参考:应用程序始终从根活动开始新鲜而不是恢复背景状态(已知错误)

So I've manually killed the application in the phone, and relaunched it again from the home screen widget. Then opened the GameActivity, and pressed HOME. Now, when relaunching it the GameActivity is still visible and keeping its UI state as it was when I left it.

所以我手动杀死了手机中的应用程序,并从主屏幕小部件再次重新启动它。然后打开GameActivity,按下HOME。现在,重新启动时,GameActivity仍然可见,并保持其状态,就像我离开时一样。

And I guess that the reason why a new instance of the activity was created when pressing the shortcut before was due to a different Intent being used for starting the activity.

我猜想之前按下快捷键时创建活动的新实例的原因是由于使用了不同的Intent来启动活动。

#3


2  

The simplest solution is to write a preference out during onPause or serialize your state out to a persistent file and then read this file during onResume in your main entry point Activity. This activity would rebuild the application state and re-launch the correct activity.

最简单的解决方案是在onPause期间写出首选项或将状态序列化为持久文件,然后在主入口点Activity中的onResume期间读取此文件。此活动将重建应用程序状态并重新启动正确的活动。

You're seeing this because you app may be killed by the OS on exit. You never know for sure so if you want true persistence of usage, ie go back to the last activity no matter what, you need to write your state to a persistent store.

你看到这个是因为你的app可能会在退出时被操作系统杀死。你永远不知道如果你想要真正持久的使用,即无论如何回到最后一个活动,你需要将你的状态写入持久存储。

#4


2  

I would highly recommend using isTaskRoot to check whether the activity should be finished instead of the FLAG_ACTIVITY_BROUGHT_TO_FRONT flag.

我强烈建议使用isTaskRoot检查是否应该完成活动而不是FLAG_ACTIVITY_BROUGHT_TO_FRONT标志。

I liked Sachin's answer, but sometimes it gave me false-positives and I would finish the activity when I shouldn't. The reliable way I found to reproduce such a false positive is:

我喜欢Sachin的答案,但有时它会给我误报,我会在不应该的时候完成这项活动。我发现重现这种假阳性的可靠方法是:

  1. Launch the app from homescreen
  2. 从主屏幕启动应用程序

  3. Press home
  4. Launched the app from home screen again
  5. 再次从主屏幕启动应用程序

  6. Press "back" to return to home screen
  7. 按“返回”返回主屏幕

  8. Returned to the app from "recent apps"
  9. 从“最近的应用”返回应用

I started looking for ways to inspect my task's activity backstack and found answers using ActivityManager. This seemed like it could work, but this answer pointed out the existence of isTaskRoot, which is a simple, elegant way of detecting this which doesn't give false positives, doesn't require a special permission and doesn't use methods that docuemtation says are intended for debugging and task management apps and which are unsupported for this type of use.

我开始寻找方法来检查我的任务的活动backstack并使用ActivityManager找到答案。这似乎可以起作用,但是这个答案指出isTaskRoot的存在,这是一种简单,优雅的方法来检测它,不会产生误报,不需要特殊许可,也不使用docuemtation方法say适用于调试和任务管理应用程序,并且不支持此类用途。

#5


1  

I understand where you are coming from but I think the os doesn't want to make any assumptions. Plus there is the issue where your app has been reclaimed because memory is need for something else. In that cause it would be starting over from your MainActivity.

我知道你来自哪里,但我认为操作系统不想做任何假设。此外,您的应用程序已被回收,因为内存需要其他内容。在那个原因中,它将从您的MainActivity开始。

Depending on how complicated your game is your can either save state in SharedPreferences or a SqlLite database in the onPause() method of your activity. Then onResume() you can restore the user to the last state, which may include "re-starting" your GameActivity.

根据游戏的复杂程度,您可以在SharedPreferences中保存状态,也可以在活动的onPause()方法中保存SqlLite数据库。然后onResume()您可以将用户恢复到最后一个状态,其中可能包括“重新启动”您的GameActivity。

Hope this helps!

希望这可以帮助!

Edit: And what I am saying is that's our responsibility as developers to show / guarantee to the user that the correct activity is displayed when coming back to the application. You application's memory always has the potential of being reclaimed and then android is going to re-start the activity that you have labeled with MAIN and LAUNCHER intents. By saving the state (the activity id) you can redirect them to that activity from main...

编辑:我所说的是,作为开发人员,我们有责任向用户显示/保证在返回应用程序时显示正确的活动。你的应用程序的内存总是有可能被回收,然后android将重新启动你用MAIN和LAUNCHER意图标记的活动。通过保存状态(活动ID),您可以将它们从主要重定向到该活动...

#6


1  

I solve the problem using the following activity stack in my application:

我在我的应用程序中使用以下活动堆栈来解决问题:

LaunchActivity -> MainActivtiy -> SomeSubActivtiy

LaunchActivity - > MainActivtiy - > SomeSubActivtiy

LaunchActivtiy only verifies some parameters and launches MainActivtiy. It turned out, that MainActivity must be launched with

LaunchActivtiy仅验证某些参数并启动MainActivtiy。事实证明,必须启动MainActivity

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

That combination of flags on MainActivity gives desired behaviour.

MainActivity上的标志组合提供了所需的行为。

#1


27  

    @Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { 
        // Activity was brought to front and not created, 
        // Thus finishing this will get us to the last viewed activity 
        finish(); 
        return; 
    } 

    // Regular activity creation code... 
} 

#2


18  

Oh, I think I've found the answer.

哦,我想我找到了答案。

Because I was launching the app by using IntelliJ, it seems it launches the application in a different way than a user, clicking a home screen widget, would launch. It's explained in the answer to another SO question:

因为我是使用IntelliJ启动应用程序,所以它似乎以不同于用户的方式启动应用程序,单击主屏幕小部件,将启动。在另一个SO问题的答案中解释了这一点:

This is due to the intents being used to start the app being different. Eclipse starts an app using an intent with no action and no category. The Launcher starts an app using an intent with android.intent.action.MAIN action and android.intent.category.LAUNCHER category. The installer starts an app with the android.intent.action.MAIN action and no category.

这是因为用于启动应用程序的意图不同。 Eclipse使用没有操作且没有类别的intent启动应用程序。 Launcher使用android.intent.action.MAIN动作和android.intent.category.LAUNCHER类别的intent启动应用程序。安装程序使用android.intent.action.MAIN操作启动应用程序,没有类别。

Ref: App always starts fresh from root activity instead of resuming background state (Known Bug)

参考:应用程序始终从根活动开始新鲜而不是恢复背景状态(已知错误)

So I've manually killed the application in the phone, and relaunched it again from the home screen widget. Then opened the GameActivity, and pressed HOME. Now, when relaunching it the GameActivity is still visible and keeping its UI state as it was when I left it.

所以我手动杀死了手机中的应用程序,并从主屏幕小部件再次重新启动它。然后打开GameActivity,按下HOME。现在,重新启动时,GameActivity仍然可见,并保持其状态,就像我离开时一样。

And I guess that the reason why a new instance of the activity was created when pressing the shortcut before was due to a different Intent being used for starting the activity.

我猜想之前按下快捷键时创建活动的新实例的原因是由于使用了不同的Intent来启动活动。

#3


2  

The simplest solution is to write a preference out during onPause or serialize your state out to a persistent file and then read this file during onResume in your main entry point Activity. This activity would rebuild the application state and re-launch the correct activity.

最简单的解决方案是在onPause期间写出首选项或将状态序列化为持久文件,然后在主入口点Activity中的onResume期间读取此文件。此活动将重建应用程序状态并重新启动正确的活动。

You're seeing this because you app may be killed by the OS on exit. You never know for sure so if you want true persistence of usage, ie go back to the last activity no matter what, you need to write your state to a persistent store.

你看到这个是因为你的app可能会在退出时被操作系统杀死。你永远不知道如果你想要真正持久的使用,即无论如何回到最后一个活动,你需要将你的状态写入持久存储。

#4


2  

I would highly recommend using isTaskRoot to check whether the activity should be finished instead of the FLAG_ACTIVITY_BROUGHT_TO_FRONT flag.

我强烈建议使用isTaskRoot检查是否应该完成活动而不是FLAG_ACTIVITY_BROUGHT_TO_FRONT标志。

I liked Sachin's answer, but sometimes it gave me false-positives and I would finish the activity when I shouldn't. The reliable way I found to reproduce such a false positive is:

我喜欢Sachin的答案,但有时它会给我误报,我会在不应该的时候完成这项活动。我发现重现这种假阳性的可靠方法是:

  1. Launch the app from homescreen
  2. 从主屏幕启动应用程序

  3. Press home
  4. Launched the app from home screen again
  5. 再次从主屏幕启动应用程序

  6. Press "back" to return to home screen
  7. 按“返回”返回主屏幕

  8. Returned to the app from "recent apps"
  9. 从“最近的应用”返回应用

I started looking for ways to inspect my task's activity backstack and found answers using ActivityManager. This seemed like it could work, but this answer pointed out the existence of isTaskRoot, which is a simple, elegant way of detecting this which doesn't give false positives, doesn't require a special permission and doesn't use methods that docuemtation says are intended for debugging and task management apps and which are unsupported for this type of use.

我开始寻找方法来检查我的任务的活动backstack并使用ActivityManager找到答案。这似乎可以起作用,但是这个答案指出isTaskRoot的存在,这是一种简单,优雅的方法来检测它,不会产生误报,不需要特殊许可,也不使用docuemtation方法say适用于调试和任务管理应用程序,并且不支持此类用途。

#5


1  

I understand where you are coming from but I think the os doesn't want to make any assumptions. Plus there is the issue where your app has been reclaimed because memory is need for something else. In that cause it would be starting over from your MainActivity.

我知道你来自哪里,但我认为操作系统不想做任何假设。此外,您的应用程序已被回收,因为内存需要其他内容。在那个原因中,它将从您的MainActivity开始。

Depending on how complicated your game is your can either save state in SharedPreferences or a SqlLite database in the onPause() method of your activity. Then onResume() you can restore the user to the last state, which may include "re-starting" your GameActivity.

根据游戏的复杂程度,您可以在SharedPreferences中保存状态,也可以在活动的onPause()方法中保存SqlLite数据库。然后onResume()您可以将用户恢复到最后一个状态,其中可能包括“重新启动”您的GameActivity。

Hope this helps!

希望这可以帮助!

Edit: And what I am saying is that's our responsibility as developers to show / guarantee to the user that the correct activity is displayed when coming back to the application. You application's memory always has the potential of being reclaimed and then android is going to re-start the activity that you have labeled with MAIN and LAUNCHER intents. By saving the state (the activity id) you can redirect them to that activity from main...

编辑:我所说的是,作为开发人员,我们有责任向用户显示/保证在返回应用程序时显示正确的活动。你的应用程序的内存总是有可能被回收,然后android将重新启动你用MAIN和LAUNCHER意图标记的活动。通过保存状态(活动ID),您可以将它们从主要重定向到该活动...

#6


1  

I solve the problem using the following activity stack in my application:

我在我的应用程序中使用以下活动堆栈来解决问题:

LaunchActivity -> MainActivtiy -> SomeSubActivtiy

LaunchActivity - > MainActivtiy - > SomeSubActivtiy

LaunchActivtiy only verifies some parameters and launches MainActivtiy. It turned out, that MainActivity must be launched with

LaunchActivtiy仅验证某些参数并启动MainActivtiy。事实证明,必须启动MainActivity

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

That combination of flags on MainActivity gives desired behaviour.

MainActivity上的标志组合提供了所需的行为。