当 activity 被销毁时 activity 的 Backstack 的不当行为

Misbehaviour of Backstack of activity when activity destroyed

我有两个活动;假设 A 和 B。在 activity A 中注册了一个广播接收器,用于侦听将完成 activity A 的特定事件。我正在 onCreate() 中注册广播接收器并销毁它在 onDestroy()activity A

为简单起见,activity B中有一个button,名为"Destroy Activity A"。当用户点击 button 时,activity A 应该被销毁。

通常这一切都运行顺利,没有任何问题,但问题出现在以下情况:

1) 假设我在 activity B 并按 Home 键将应用程序移至后台,然后如果我使用其他资源密集型应用程序,Android 系统将终止我的应用程序以免费内存。然后如果我从最近的任务中打开我的应用程序,activity B 将被恢复,并且它的 onCreate()onResume() 等方法将被调用。现在我按button销毁activity A,但是activityA已经销毁了,所以activity AonCreate()onResume()等方法会除非我按 back button 转到 activity A,否则不会被调用。因此 broadcast receiver 没有注册监听事件。

2) 当用户在设备设置的开发者选项中选择 "Don't keep activities" 时会出现同样的问题。

我一直在寻找解决这个问题的方法,但我找不到合适的答案。处理这种情况的最佳方法是什么?这是 Android 错误吗?这个问题应该有一些解决方案。

请帮助我。

我不知道是否可以 "proper" 方式处理这个问题。

我想到的是以某种方式标记 A activity。您不能使用 startActivityForResult(),因为您将在调用 onResume() 之前收到结果,即 UI 已经膨胀。

如果您使用 Otto,您可以尝试使用粘性事件。否则,您将需要一个单例来处理标志或将其保存到共享首选项。

在调用 setContentView() 之前,您必须在 onCreate() 方法上检查该标志,如果该标志为真,则只需完成 activity.

我想到了很多解决方案,但由于您没有提供太多关于您的应用程序的信息,所以我认为这通常应该可行。

不用发射广播杀死Activity A,只要在Activity B."Kill Activity A"按钮按下时执行下面的代码即可。

        Intent intent = new Intent(getApplicationContext(),
                ActivityA.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        intent.putExtra("EXIT", true);
        startActivity(intent);

在activityA中添加如下代码

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (intent.getBooleanExtra("EXIT", false)) {
        finish();
    }
}

protected void onCreate(Bundle savedInstanceState) {
    //Ideally, there should not be anything before this
    super.onCreate(savedInstanceState);
    if(getIntent().getBooleanExtra("EXIT", false)){
        finish();
        return;
    }

在清单中为 activity A.

设置 "singleTop" 启动模式
<activity
    android:name=".ActivityA"
    ...
    android:launchMode="singleTop" />

这将产生以下后果:

  • 如果 Activity A 已经是 运行 它将被带到 activity 堆栈的前面并完成,从而将其从堆栈中删除。
  • 如果 Activity A 已被销毁但仍存在于 activity 堆栈中(将在按下后退按钮时启动),它将被启动,带到前面并完成,从而删除它来自 activity 堆栈。
  • 如果ActivityA已经被销毁,不在activity栈中,你仍然按下"Remove Activity A"按钮,它会被启动,被带到前面并完成。

一般来说,您应该看不到任何闪烁。

基于这个想法,您可以为您的特定应用构建性能更好的解决方案。例如,您可以使用 FLAG_ACTIVITY_CLEAR_TOP 并在 Activity B.

的 onBackPressed() 中完成 Activity A

If your Activity A has destroyed by Android OS itself then there are no way to track.

有些人建议通过在 onDestroy 方法中列出事件来跟踪 Activity A 但是如果你的 Activity 被系统 OS 杀死然后注意这里它不会调用那些方法 .

根据您提供的信息,您在Activity B 的onCreate 中检查是否已注册广播后注册该广播如何。如果在您提到的任何一种情况下调用了 Activity A 的 onDestroy,那么将调用广播的注销。所以在那种情况下,你可以在 Activity B 的 onCreate 中注册你的广播,这样你就可以收听它,即使你的后台只有 Activity B。

您是否考虑过使用 Sticky Broadcast? 您也可以在应用程序级别(在清单中)注册您的接收器并监听此事件,而不管 Activity A 状态如何。

但是,正如已经说过的 Youssef,从后台终止活动不是正确的方法。您应该认真考虑更改导航逻辑。

在保持您当前的广播逻辑的情况下无法解决此问题。

在我看来,从后台终止活动不是正确的方法。您应该认真考虑更改导航逻辑。

但是如果你的项目很大,时间紧迫,重构是不可能的,A.J。的方法有效,但您提到您有很多活动需要终止,他的解决方案实施起来非常棘手。

我的建议如下。这可能不是最好的主意,但我想不出另一个。所以也许这会有所帮助。

您应该具备以下条件:

  • 适合您所有活动的基地 Activity。
  • 应用程序级别的 ArrayList<String> activitiesToKill 对象。 (如果你没有扩展 Application 你可以把它作为静态变量

首先我们必须确保当 OS 在低内存中杀死应用程序时 activitiesToKill 不会丢失。在 BaseActivity 中,我们在 onSaveInstanceState 期间保存列表并在 onRestoreInstanceState

中恢复它
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("activitiesToKill", activitiesToKill);
}

private void onRestoreInstanceState(Bundle state) {
    if (state != null) {
        activitiesToKill = (ArrayList<String>) state.getSerializable("activitiesToKill");
    super.onRestoreInstanceState(state); 
}

}

这里的想法是通过使用它们的名称来保存应该在列表中终止的活动。

逻辑如下:

假设您有活动 A、B、C、D 和 E

来自Activity E,你按下按钮,你想杀死B和D

当您按下 E 中的按钮时,您将 B 和 D 的名称添加到 activitiesToKill 对象。

activitiesToKill.add(B.class.getSimpleName()
activitiesToKill.add(D.class.getSimpleName()

在BaseActivity的onCreate方法中,我们必须检查

if(savedInstanceState != null)
{
    //The activity is being restored. We check if the it is in the lest to Kill and we finish it                
    if(activitiesToKill.contains(this.getClass().getSimpleName()))
    {
        activitiesToKill.remove(this.getClass().getSimpleName())
        finish();
    }
}

如果 activity 通过广播被杀死,请务必删除它的名字。

基本上每个场景都会发生这种情况。

如果应用程序 运行 正常,并且您单击按钮,将发送广播,B 和 D 将被杀死。确保从 activitiesToKill

中删除 B 和 D

如果应用程序被杀死并恢复,你按下按钮,广播将没有效果,但你已经将B和D添加到activitiesToKill对象。因此,当您单击返回时,将创建 activity 并且 savedInstanceState 不为空,activity 已完成。

这种方法认为 activity E 知道它必须杀死哪些活动。

如果您不知道要从 E 中终止哪些活动,则必须稍微修改此逻辑:

不使用 ArrayList,而是使用 HashMap<String, bool>

当 Activity B 创建时,它会自行将其注册到 hashmap 中:

activitiesToKill.put(this.class.getSimpleName(), false)

然后从Activity E开始,你所要做的就是将所有条目设置为true

然后在基础 activity 的创建中,您必须检查此 activity 是否已在 activitiesToKill 中注册(散列映射包含密钥)并且布尔值是 true你杀了它(不要忘记 return 它为假,或删除密钥)

这确保每个 activity 将自己注册到 HashMap 并且 Activity E 不会让 top 知道所有要杀死的活动。并且不要忘记删除它们以防广播杀死它们。

这种方法还确保 activity 在从 intent 正常打开时不会被杀死,因为在这种情况下 onSaveInstanceState 在 onCreate 中将为 null,因此什么也不会发生。

如果您有需要通过不同条件(不仅仅是单击按钮)终止的活动组,则可以完成更高级的检查,因此您可以使用 HashMap 的 HashMap 将它们分类。

另请注意,如果您有多个名称相同但捆绑包不同的活动,则可以使用 getName 而不是 getSimpleName。

我希望我的解释足够清楚,因为我是从头开始写的,如果有任何地方不清楚,请告诉我。

祝你好运

Activities 的主要规则之一是 除了前景 activity,你不能指望任何 activity 活着。您尝试用广播做的事情与后台堆栈无关——后台堆栈不能保证所有活动始终处于活动状态,但它会确保在需要进入前台时重新创建它们。

在您的示例中(如果我理解您的目标),您需要导航到 A 下面的内容——比如说,Activity Z,然后堆栈看起来像这样:Z-A-[B]。在正常的事件过程中,你点击 back 然后它会带你到 A,然后再点击一次 - 到 Z 但在某些情况下(比如按下按钮)你想要回到 Z 绕过 A -- 这是使用 FLAG_ACTIVITY_CLEAR_TOP 并显式启动 Z 的经典案例:

Intent intent = new Intent(this, ActivityZ.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

这将完成 BA,并将意图传递给 Z。你可能还需要 FLAG_ACTIVITY_SINGLE_TOP 标志,密切注意 FLAG_ACTIVITY_CLEAR_TOP 的描述,你应该考虑一些技巧。