当 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 A
的onCreate()
、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);
这将完成 B
和 A
,并将意图传递给 Z
。你可能还需要 FLAG_ACTIVITY_SINGLE_TOP
标志,密切注意 FLAG_ACTIVITY_CLEAR_TOP 的描述,你应该考虑一些技巧。
我有两个活动;假设 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 A
的onCreate()
、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 AIf 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添加到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);
这将完成 B
和 A
,并将意图传递给 Z
。你可能还需要 FLAG_ACTIVITY_SINGLE_TOP
标志,密切注意 FLAG_ACTIVITY_CLEAR_TOP 的描述,你应该考虑一些技巧。