应用程序重新启动而不是恢复原来的状态。

222

希望有人可以帮我找出原因,最好是提供解决方法或者对这种行为作出解释。

问题:

在某些设备上,点击启动器图标会导致当前任务被恢复,而在其他设备上,它会导致初始启动意图被触发(有效地重新启动应用程序)。这是为什么?

详细信息:

当您按下“启动器图标”时,应用程序会正常启动。也就是说,我认为会发起一个Intent,其中包含第一个Activity的名称及其操作为android.intent.action.MAIN,类别为android.intent.category.LAUNCHER。但是,情况并非总是如此:

在大多数设备上,如果您在应用程序已经运行后按下启动器图标,则会恢复该进程中当前正在运行的Activity(而不是初始Activity)。它的恢复方式与您从OS菜单中的“最近使用的任务”中选择它的方式相同。这是我想要在所有设备上都拥有的行为。

但是,在某些其他设备上,会出现不同的行为:

  • 在Motorola Xoom上,当您按下启动器图标时,无论当前正在运行什么内容,应用程序都会始终启动初始启动的Activity。我认为启动器图标总是会启动“LAUNCHER”意图。

  • 在Samsung Tab 2上,当您按下启动器图标时,如果您刚刚安装应用程序,则它将始终启动初始Activity(与Xoom相同);但是,在安装后重新启动设备后,启动器图标将恢复应用程序。我认为这些设备会在设备启动时将“已安装应用程序”添加到查找表中,以便启动器图标可以正确地恢复正在运行的任务?

我读了许多类似于我的问题的答案,但仅仅添加 android:alwaysRetainTaskState="true" 或者使用 launchMode="singleTop"Activity 并不是解决之道。

编辑:

在最近一次启动该应用程序后,我们发现在第一次重启后所有设备上都开始出现此行为。这对我来说看起来很疯狂,但是通过查看重启过程,我实际上找不到问题所在。


2
这可能看起来是一个微不足道的问题,但是您在Xoom的开发选项中将“不保留活动”设置为true了吗? - Andrew Schuster
不是的(我希望是! :)) - 我已经记录了每个Activity的生命周期以及后台中仍然可用的Activities(它们已停止 - 未被销毁)。在某些情况下,操作系统似乎会在重新启动第一个Activity而不是恢复它们时调用finish() - Graeme
1
如果您按下主页按钮,然后点击启动器图标,则恢复行为对于 Android 来说是默认行为,您可能已经知道。但是,如果您按下返回按钮返回到主屏幕,大多数手机将 finish() 应用程序。无论您使用什么方法退出应用程序,在不同的设备上是否存在差异?您可以记录 onKeyUpEvent 并检查某些设备是否奇怪地处理了硬/软键。 - Nick Cardoso
2
不是这个问题,我对上面所述的问题非常确定。使用Home键将应用程序放入后台(而不是返回键,因为你是正确的,返回键会finish() Activity)。在Xoom上,可以从任务列表中恢复应用程序(但不能从启动器中),因此返回堆栈肯定没有被清除。 - Graeme
给一个不起作用的问题设置赏金?每个人都会被踩。 - danny117
1
回答中提供的赏金是解决问题的方法。我将自己的答案标记为“正确”,因为尽管有时问题是由启动器中的应用程序错误引起的(如他的答案中所述),但我的特定问题是由任务切换引起的。他的解决方案可以解决这两个问题。 - Graeme
10个回答

276
您所遇到的问题是在 Android 系统的某些启动器上自API 1以来存在的一个问题导致的。您可以在此处找到有关该错误以及可能解决方案的详细信息:https://code.google.com/p/android/issues/detail?id=2373
这是三星设备以及其他使用自定义启动器/皮肤的制造商相对常见的问题。我没有看到在原生 Android 启动器上出现此问题。
基本上,应用程序实际上并没有完全重新启动,但是当启动器恢复应用程序时,您的启动活动会被启动并添加到活动堆栈的顶部。您可以通过在恢复应用程序并显示启动活动时单击返回按钮来确认这一点。然后,您应该被带到恢复应用程序时期望显示的活动中。
我选择实施的解决此问题的解决方法是检查启动初始活动的意图中是否存在 Intent.CATEGORY_LAUNCHER 类别和 Intent.ACTION_MAIN 操作。如果这两个标志存在且该活动不在任务的根部(即应用程序已经在运行),则在初始活动上调用 finish() 方法。该解决方案可能并不完全适用于您,但类似的解决方案应该有效。
以下是我在初始/启动活动的 onCreate() 方法中执行的操作:
    if (!isTaskRoot()
            && getIntent().hasCategory(Intent.CATEGORY_LAUNCHER)
            && getIntent().getAction() != null
            && getIntent().getAction().equals(Intent.ACTION_MAIN)) {

        finish();
        return;
    }

5
到目前为止,这对我来说一直有效,没有任何负面副作用。基于逻辑假设,我认为它是合理有效的,没有理由不是。 - javahead76
3
我认为这是处理此错误的适当方式。适用于我。 - Sokolov
4
对我来说做得很好,在大约8个不同的设备上进行了验证。非常感谢! - shaya ajzner
3
哇!我的问题解决了,我一直在寻找解决方法已经花费了2个小时。 - Jean Raymond Daher
2
谢谢@starkej2。非常顺利地完成了工作。 - Rajeev Sahu
显示剩余21条评论

64
这个问题在2016年仍然很重要。今天,一位QA测试员报告说我的一个应用在Android M中从“库存启动器”重新启动而不是恢复。

实际上,系统将启动的活动添加到当前任务堆栈中,但“对用户来说”,它似乎发生了重新启动并且他们失去了他们的工作。序列是:

  1. 从Play商店下载(或侧载apk)
  2. 从Play商店对话框中启动应用程序:出现活动A [任务堆栈:A]
  3. 导航到活动B [任务堆栈:A -> B]
  4. 按“主屏幕”按钮
  5. 从应用程序抽屉中启动应用程序:出现活动A! [任务堆栈:A -> B -> A](用户可以按“返回”按钮从此处进入活动“B”)
注意:此问题在通过ADB部署的debug APK中不会出现,仅在从Play商店下载或侧载的APK中出现。在后一种情况下,步骤5中的启动意图包含标志Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT,但在debug情况下没有。一旦应用程序从启动器被冷启动,问题就会消失。我怀疑任务使用了一个格式错误(更准确地说是非标准的)的Intent来阻止正确的启动行为,直到任务完全清除为止。
我尝试了各种activity launch modes,但这些设置偏离了用户期望的标准行为:在B活动中恢复任务。请参见Tasks and Back Stack指南中对“Starting a Task”页面底部预期行为的定义:

这种类型的意图过滤器会导致应用程序启动器中显示该活动的图标和标签,使用户能够在启动后的任何时间内启动该活动并返回它创建的任务。

我发现这个答案很相关,于是将以下内容插入到我的根活动(A)的“onCreate”方法中,以便在用户打开应用程序时适当地恢复。
                    /**
     * Ensure the application resumes whatever task the user was performing the last time
     * they opened the app from the launcher. It would be preferable to configure this
     * behavior in  AndroidMananifest.xml activity settings, but those settings cause drastic
     * undesirable changes to the way the app opens: singleTask closes ALL other activities
     * in the task every time and alwaysRetainTaskState doesn't cover this case, incredibly.
     *
     * The problem happens when the user first installs and opens the app from
     * the play store or sideloaded apk (not via ADB). On this first run, if the user opens
     * activity B from activity A, presses 'home' and then navigates back to the app via the
     * launcher, they'd expect to see activity B. Instead they're shown activity A.
     *
     * The best solution is to close this activity if it isn't the task root.
     *
     */

    if (!isTaskRoot()) {
        finish();
        return;
    }

更新:将此解决方案从解析意图标志移动到直接查询活动是否在任务的根目录。意图标志很难预测并测试所有不同的打开MAIN活动的方式(从主屏幕启动,从“向上”按钮启动,从Play Store启动等)。

6
“一旦从启动器重新启动应用程序,问题就会消失。”这是最奇怪的部分,我也注意到了 - 在第一次启动后杀掉应用程序后,它再次开始正常工作。这是一个非常奇怪的错误。感谢您对此进行彻底分析并提供解决方案。 - Oded
11
注意:您可以通过使用应用程序的Google Play页面中的“打开”选项,即使通过Android Studio安装了APK,也可以在最初清除问题后再次重现问题(如您所描述的“冷启动”)。我发现这对于验证修复是否有效非常有用。 - Oded
谢谢你的解释 :) - AndroidEnthusiast
2
很多应用都会出现这种情况。我测试过的一款主要应用是Google相册。 - Raghubansh Mani
只是指出这似乎在Pixel XL(9.0.0)上无法工作。 - behelit
显示剩余4条评论

22

哎呀!(简单来说请看下面加粗的陈述)

我找到问题所在了......我想。

那么,我先假设一下。当你按启动器时,它要么启动默认的 Activity ,要么如果之前启动的一个 Task 仍处于打开状态,则将其置于前台。换句话说 - 如果在导航的任何阶段都创建新的 Task 并完成旧的任务,那么启动器现在将不再恢复您的应用程序。

如果这个假设是正确的,那么我相信这应该是一个 bug,因为每个 Task 都在同一个进程中,并且与第一个创建的任务一样有效的恢复候选对象?

我的问题是通过从一些 Intents 中删除这些标志来解决的:

i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK );

虽然很明显FLAG_ACTIVITY_NEW_TASK会创建一个新的Task,但我并没有意识到上述假设实际上是在生效。我曾考虑过这个是否有问题并将其移除以进行测试,但我仍然遇到了问题,所以我排除了这种可能性。然而,我仍然存在以下条件:

i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
我的启动画面使用上述标志在我的应用程序中启动了“main”Activity。毕竟,如果我“重新启动”了应用程序并且Activity仍在运行,则我更希望保留其状态信息。
你会注意到,在文档中没有提到启动新的Task
如果设置,并且要启动的活动已经在当前任务中运行,则除了启动该活动的新实例之外,它上面的所有其他活动也将关闭,并将此Intent作为一个新Intent传递给(现在位于顶部的)旧活动。例如,考虑由活动A、B、C、D组成的任务。如果D使用解决活动B的组件的Intent调用startActivity(),则C和D将被结束,B将接收给定的Intent,结果栈现在是:A、B。
以上示例中正在运行的活动B将在其onNewIntent()方法中接收您在此处启动的新Intent,或者它本身完成并使用新意图重新启动。如果它声明其启动模式为“多个”(默认),并且您没有在同一意图中设置FLAG_ACTIVITY_SINGLE_TOP,则它将被完成并重新创建;对于所有其他启动模式或如果设置了FLAG_ACTIVITY_SINGLE_TOP,则此Intent将传递到当前实例的onNewIntent()中。
此启动模式也可以与FLAG_ACTIVITY_NEW_TASK一起使用,以在任务的根活动处启动它将任何当前运行的该任务实例带到前台,然后清除为其根状态。例如,从通知管理器启动活动时,这特别有用。
所以,我有如下所述的情况:
  • A 使用FLAG_ACTIVITY_CLEAR_TOP启动了BA完成。
  • B希望重新启动服务,因此将用户发送到具有服务重启逻辑和UI的A(没有标志)。
  • A使用FLAG_ACTIVITY_CLEAR_TOP启动了BA完成。
此时,第二个FLAG_ACTIVITY_CLEAR_TOP标志正在重新启动堆栈中的B。我假设这必须销毁Task并启动一个新的Task,导致我的问题,如果你问我,这是非常难以发现的情况!
因此,如果我的所有假设都是正确的:
  • 启动器只恢复最初创建的任务
  • FLAG_ACTIVITY_CLEAR_TOP将,如果重新启动唯一剩余的Activity,也会重新创建一个新的Task

FLAG_ACTIVITY_CLEAR_TOP不会在只剩下一个Activity时创建新任务或重新启动您要启动的Activity。如果设置了此标志,并且要启动的Activity已在当前任务中运行,则不会启动该Activity的新实例,而是将其上面的所有其他Activity关闭,并将此Intent作为新Intent传递给(现在位于顶部的)旧Activity。 - starkej2
2
我意识到这并不是本意,但通过反复试验,在我的情况下确实是这样。移除这些标志可以解决问题。 - Graeme
这并不能在所有设备和条件下创建完美的简历。 - danny117
移除标志解决了我的问题。谢谢。 - Sealer_05
我有启动屏幕/主屏幕的场景,但我没有使用任何标志从启动到主屏幕进行过渡,但问题仍然可以在我的设备上重现 - 所以这个解决方案对我来说不起作用。 - ror

17

我在三星设备上遇到了同样的问题。经过大量搜索,这些答案都不适用于我。我发现在AndroidManifest.xml文件中,launchMode被设置为singleInstance (android:launchMode="singleInstance")。移除launchMode属性修复了我的问题。


实际上,这对我也起了作用。我发现另一个SO问题的答案很有帮助:https://dev59.com/tmQo5IYBdhLWcg3wV-Mx#21622266。还有这篇关于不同类型`launchMode`值的文章:https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en - Joshua Pinter
这个修复对我起作用了!将此添加到活动属性而不是清单文件中的主要活动类中。 - Calin Vlasin
@CalinVlasin,你能给我展示一下你如何使用launchMode吗?你把它放在哪里了?目前我的代码是这样的,但是它导致了问题:<activity android:name=".UI.landing.MyActivity" android:configChanges="locale|layoutDirection" android:launchMode="singleTop" android:windowSoftInputMode="stateAlwaysHidden|adjustResize"> - j2emanue
这也是我的问题。我认为应该与已接受的答案一起使用(!isTaskRoot... - behelit

6

在我的Cat S60手机上,我启用了"不保留活动"选项,这个选项可以在开发者选项中找到。禁用它后,我可以在切换应用程序时不丢失应用程序的状态...


1
不知道怎么回事,但这在我的设备上是开启的。 - realPro

1
这个解决方案对我有效:
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            Intent startMain = new Intent(Intent.ACTION_MAIN);
            startMain.addCategory(Intent.CATEGORY_HOME);
            startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(startMain);
            return false;
        }
        else
            return super.onKeyUp(keyCode, event);
    }

信用: 我需要在按下返回按钮时最小化Android应用程序

可能不适用于所有设备,但成功地在按下返回按钮时创建主页按钮行为,从而停止活动而不是结束它。


有趣的技巧,但并不能解决所述的问题。不过对于其他问题/疑问确实非常有用。 - Graeme
1
返回按钮有特定的用途,用户期望它能够按照预期的方式工作。以任何方式覆盖它都是错误的,而且在我看来非常不专业。 - Bugs Happen

0
在Android文件夹中的`android/app/src/main/AndroidManifest.xml`中删除该行。
android:noHistory="true"

-1

我遇到了同样的问题,原因是:

(在MainActivity中的Kotlin代码)

override fun onBackPressed() {
    finish()
}

因此,当从我的登录活动导航到我的主活动时,我使用以下代码:

    val intent = Intent(this, MainActivity::class.java)
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    startActivity(intent)

当使用这些标志时,我不必在MainActivity中拥有一个onBackPressed()函数,它将在后退点击时自然退出应用程序。当按下Home按钮并返回应用程序时,它不会重新启动。


-3

针对那些不懂编程但在他们的安卓手机上遇到此问题的人提供解决方案。

这种情况通常是由于升级安卓版本(仅是我的猜测)而发生的。升级后,所有应用程序都会被优化以使用更少的电池。但是,这反过来会减慢您的设备。

如何解决

进入设置>>应用程序>>应用程序设置(在屏幕上任何位置查找设置标志-在不同的设备上不同)>>电池优化(或类似的选项)>>将所有应用程序移动到“未优化”状态(必须手动逐个执行-在某些手机上可能允许/禁止)。您的启动器应用程序需要是“未优化”的(在我的情况下是Zen UI启动器-我想这就是罪魁祸首-如果您有时间,可以尝试优化/不优化并重新启动不同的应用程序)。现在重新启动您的手机。(无需重置数据/安全模式或任何麻烦)

现在尝试多任务处理。 :) 按启动器图标应该会导致当前任务被恢复。 :) 您的设备将变得不用担心电池,它无论如何都会耗尽。


-8

对于用户来说是无价的。即使在最近使用的应用程序列表中停留了几周,也能呈现完美的简历。

它看起来像是用户的简历,但实际上是一个完整的启动。

背景: 在主活动中未启动任务的应用程序使用的内存易于回收。操作系统可以简单地使用传递给 onCreate 的原始捆绑包重新启动应用程序。但是,您可以在 onSaveInstanceState 中添加到原始捆绑包中,因此当操作系统重新启动您的应用程序时,您可以恢复实例状态,而没有人会知道应用程序是重新启动还是恢复。以经典地图程序为例。用户移动到地图上的某个位置,然后按下主页键。两周后,这个映射应用程序仍然在最近使用的应用程序列表中,与 Facebook、Pandora 和 Candy Crush 一起。操作系统不仅保存最近使用的应用程序的名称,还保存用于启动应用程序的原始捆绑包。然而,程序员已编写了 onSaveInstanceState 方法,因此原始捆绑包现在包含构建应用程序所需的所有材料和信息,因此它看起来像是恢复了应用程序。

示例: 在 onSaveInstanceState 中保存当前相机位置,以防应用程序被卸载并且需要从最近使用的应用列表中重新启动数周后。

@Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        // save the current camera position;
        if (mMap != null) {
            savedInstanceState.putParcelable(CAMERA_POSITION,
                    mMap.getCameraPosition());
        }
    }



@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // get the exact camera position if the app was unloaded.
        if (savedInstanceState != null) {
            // get the current camera position;
            currentCameraPosition = savedInstanceState
                    .getParcelable(CAMERA_POSITION);
        }

注意:你也可以使用onRestoreInstanceState方法,但我发现在onCreate中恢复实例更容易。

很可能这就是你的应用程序中正在发生的事情。在某些设备上,为了释放内存,你的应用程序会被卸载。是的,有一些标志可以帮助,但这些标志不能捕捉到你的应用程序的每一个细节,而且这些标志不会像onSaveInstanceState那样让你保持活动状态数周之久。你必须编写完美的两周后恢复代码。对于复杂的应用程序来说,这并不是一项容易的任务,但我们支持你,并愿意提供帮助。

祝你好运!


这不是由于常见的内存问题或默认的“暂停状态”条件引起的。我已经在许多设备上尝试了我的应用程序(一些具有大内存,一些具有较低内存)。 - Graeme
我理解Graeme的问题。在某些设备上,恢复应用程序会调用onCreate。这听起来就像我的HTC EVO(姜饼)一样,它会杀死应用程序,只是为了旋转屏幕。查看文档,它说应用程序可以在onCreate中恢复:http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState%28android.os.Bundle%29 - danny117
@danny117 这是正确的,但他遇到的问题与应用程序恢复时重新创建单个活动无关(这是预期的),而是启动了错误的活动。 - starkej2
1
@danny117 当特定 Activity 被恢复时,onSaveInstanceState 只在 Activity 级别上有用。他遇到了一个应用级别的问题 - “在某些设备上,按下启动器图标会导致当前任务被恢复,而在其他设备上则会触发初始启动意图(有效地重新启动应用程序)”。 - starkej2
我不在乎一些踩票。传递给onCreate的bundle可以使用。你可以在onSaveInstanceState中填充该bundle。 - danny117
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接