清除整个历史栈并在Android上启动新活动

375

有没有可能在堆栈上启动一个活动,清除整个历史记录?

情况

我有一个活动堆栈,要么是A->B->C,要么是B->C(屏幕A选择用户令牌,但许多用户只有一个令牌)。

在屏幕C中,用户可能会执行某些操作,使得屏幕B无效,因此应用程序希望将其带到屏幕A,而不管它是否已经在堆栈中。在我的应用中,屏幕A应该是堆栈上唯一的项。

注意事项

有许多类似的问题,但我没有找到任何回答这个确切问题的东西。我尝试调用getParent().finish() - 这总是导致空指针异常。FLAG_ACTIVITY_CLEAR_TOP仅在活动已在堆栈上时才起作用。

14个回答

733
在API级别11中,添加了一个新的Intent Flag,专门用于此目的:Intent.FLAG_ACTIVITY_CLEAR_TASK 仅为澄清起见,请使用以下内容:

Java

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

Kotlin

intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK

很不幸,对于API级别<= 10,我尚未找到清晰的解决方案。 “DontHackAndroidLikeThis”的解决方案确实是纯粹的hackery,请勿使用:)

编辑: 根据@Ben Pearson的评论,现在可以针对API级别<=10使用IntentCompat类进行相同的操作。 可以使用IntentCompat.FLAG_ACTIVITY_CLEAR_TASK标志来清除任务,因此您也可以支持API级别11之前的版本。


26
为了澄清,使用以下代码: intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 该代码旨在设置intent的标志位,以便在启动activity时创建一个新的任务,并同时清除任何现有的任务。 - user123321
2
没有使用 Intent.FLAG_ACTIVITY_NEW_TASK 标志,应用程序在 Android 4 上有时会自动关闭。 - max4ever
22
IntentCompat现在也有一个清除任务的标志,因此您可以支持API级别11之前的版本 -http://developer.android.com/reference/android/support/v4/content/IntentCompat.html#FLAG_ACTIVITY_CLEAR_TASK - Ben Pearson
10
在API级别小于10的设备上,IntentCompat.FLAG_ACTIVITY_CLEAR_TASK将被忽略。 (意思是:当使用IntentCompat.FLAG_ACTIVITY_CLEAR_TASK标志时,如果设备的API级别低于10,则该标志不会生效。) 详细信息请参阅:http://developer.android.com/reference/android/support/v4/content/IntentCompat.html#FLAG_ACTIVITY_CLEAR_TASK - David
7
IntentCompat的标志只是为了避免崩溃,但并不像@David所说的那样有任何作用。 - Sloy
显示剩余6条评论

55

情况1:仅有两个活动A和B:

在这种情况下,活动流程是A->B。从B点击返回按钮时,我们需要关闭应用程序,然后在从A启动活动B时只需调用finish()即可,这将防止Android将活动A存储到后台堆栈中。例如,对于活动A是应用程序的加载/闪屏屏幕。

Intent newIntent = new Intent(A.this, B.class);
startActivity(newIntent);
finish();

情况二:存在多于两个活动:

如果存在一个如A->B->C->D->B的流程,并且在从Activity D返回Activity B时单击了返回按钮。在这种情况下,我们应该使用。

Intent newIntent = new Intent(D.this,B.class);
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(newIntent);

由于Intent.FLAG_ACTIVITY_CLEAR_TOP和Intent.FLAG_ACTIVITY_NEW_TASK清除了堆栈并将其置于顶部,因此此处会从后堆栈中启动Activity B而不是新实例。因此,当我们按下返回按钮时,整个应用程序将被终止。


2
这对我有用。我在所有活动中都加入了这些标志。在这些活动中,返回按钮完美地返回到上一个活动,在主活动中使用 Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish();整个应用程序被关闭,但仍然在内存中,没有活动,如果您重新启动应用程序,则会进入闪屏屏幕 :) - Rako
这应该是最佳答案。如果有人和我有相同的情况:A->B->C->D->E->(B)从 E->B 应该得到以下结果:A->B - Shem Alexis Chavez

54

在Android的新版本(API 16及以上)中,请使用finishAffinity()方法。

该方法适用于API版本16及以上。

Intent mIntent = new Intent(mContext,MainActivity.class);
finishAffinity();
startActivity(mIntent);
  • 它与启动新Activity并清除所有堆栈相同。
  • 或重新启动MainActivity/FirstActivity。

1
这个方法很管用,对于我来说,4.x.x上的标志没有起作用,但这个方法完美解决了问题!谢谢。 - Jonathan Aste
1
如果您的目标是完成当前活动及其以下所有活动,并在它们自己的任务中启动新活动,那么这似乎是正确的答案。 - ToBe

26

我也花了几个小时研究这个问题...并且认为FLAG_ACTIVITY_CLEAR_TOP听起来像是你想要的:清除整个堆栈,除了被启动的活动,这样返回按钮就会退出应用程序。但正如Mike Repass所提到的,只有当你要启动的活动已经在堆栈中时,FLAG_ACTIVITY_CLEAR_TOP才起作用;当活动不在堆栈中时,该标志不起作用。

那怎么办呢?使用FLAG_ACTIVITY_NEW_TASK将要启动的活动放入堆栈中,这将使该活动成为历史堆栈上的新任务的开始。然后添加FLAG_ACTIVITY_CLEAR_TOP标志。

现在,当FLAG_ACTIVITY_CLEAR_TOP在堆栈中查找新活动时,它将在那里,并在清除其他所有内容之前被拉出。

这是我的注销函数;View参数是附加到该函数的按钮。

public void onLogoutClick(final View view) {
    Intent i = new Intent(this, Splash.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(i);
    finish();
}

1
你的意思是使用CLEAR_TASK而不是CLEAR_TOP吗? - Andrew

13

在启动一个新活动(Activity)时,使用 startActivity 方法后,确保调用 finish() 方法,以便当前活动不会被堆叠在新活动的后面。


+1 不错的解决方案,可以防止在某些情况下将仅一个活动放入历史堆栈中。 - marsbear
29
如果栈中有多个活动,则finish()方法将只清除之前的活动而不是其他活动,这种情况下该方法无法起作用。 - Necronet

12

不应该改变堆栈。Android返回按钮应该像Web浏览器中的一样工作。

我可以想到一种方法来实现它,但这是一个相当“hack”的方式。

  • 通过将其添加到AndroidManifest,使您的活动成为singleTask

    示例:

<activity android:name=".activities.A"
          android:label="@string/A_title"
          android:launchMode="singleTask"/>

<activity android:name=".activities.B"
          android:label="@string/B_title"
          android:launchMode="singleTask"/>
  • 扩展Application,它将保存决定应该去哪里的逻辑。

  • 示例:

    public class DontHackAndroidLikeThis extends Application {
    
      private Stack<Activity> classes = new Stack<Activity>();
    
      public Activity getBackActivity() {
        return classes.pop();
      }
    
      public void addBackActivity(Activity activity) {
        classes.push(activity);
      }
    }
    
    从A到B:
    DontHackAndroidLikeThis app = (DontHackAndroidLikeThis) getApplication();
    app.addBackActivity(A.class); 
    startActivity(this, B.class);
    

    从B到C:

    DontHackAndroidLikeThis app = (DontHackAndroidLikeThis) getApplication();
    app.addBackActivity(B.class); 
    startActivity(this, C.class);
    

    在C语言中:

    If ( shouldNotGoBackToB() ) {
      DontHackAndroidLikeThis app = (DontHackAndroidLikeThis) getApplication();
      app.pop();
    }
    

    并且处理后退按钮以从堆栈中弹出(pop())。

    再次强调,你不应该这样做 :)


    1
    最终,我决定保留堆栈的完整性,并告诉用户他们当前的屏幕无效。 - Casebash
    1
    非常令人沮丧的是,安卓系统尚不能让我们以这种方式管理活动堆栈。我会考虑在未来的安卓应用中使用这个解决方案。 - Cephron
    4
    为了澄清为什么不应该使用这个方法:这是制造内存泄漏的一种好方法。 在某些时候,操作系统可能决定终止后台活动,但由于“Application”占用了它们的实例,操作系统将无法释放从销毁的活动中剩余的RAM。 - Vit Khudenko
    1
    @Navin 是的,使用弱引用可以避免内存泄漏,但是如果在垃圾回收后没有活动的Activity引用,那么整个方法就毫无意义了。再次强调 - 不要这样做,这是一种错误的Android方法。 - Vit Khudenko
    我认为最好、最简单的解决方案是在寻找答案之前先搜索三个主题。 - David
    显示剩余2条评论

    10

    高级可重用Kotlin:

    您可以使用setter方法直接设置标志。在 Kotlin 中,or 是 Java 位或 |替代品

    intent.flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
    

    如果您计划经常使用此功能,请创建一个意图扩展函数。
    fun Intent.clearStack() {
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
    }
    

    在启动意图之前,您可以直接调用此函数。

    intent.clearStack()
    

    如果您需要在其他情况下添加附加标志的选项,请向扩展函数添加可选参数。

    fun Intent.clearStack(additionalFlags: Int = 0) {
        flags = additionalFlags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
    }
    

    6
    尝试以下代码:
    Intent intent = new Intent(ManageProfileActivity.this, LoginActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|
                    Intent.FLAG_ACTIVITY_CLEAR_TASK| 
                    Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
    

    如果我这样使用,活动再次更新时调用API,但之前存在的所有堆栈都将被清除。 - Harsha

    5

    试试这个:

    Intent logout_intent = new Intent(DashboardActivity.this, LoginActivity.class);
    logout_intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    logout_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    logout_intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
    startActivity(logout_intent);
    finish();
    

    5

    对我来说,以上任何一种方法都不起作用。

    只需执行以下操作即可清除所有先前的活动

    finishAffinity() // if you are in fragment use activity.finishAffinity()
    Intent intent = new Intent(this, DestActivity.class); // with all flags you want
    startActivity(intent)
    

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