注销时,清除“活动历史记录”堆栈,以防止“后退”按钮打开仅登录的活动


235

我的应用程序中的所有活动都需要用户登录才能查看。用户几乎可以退出任何活动。这是应用程序的要求。无论何时用户注销,我都想将用户发送到Login Activity。此时,我希望此活动位于历史记录堆栈的底部,以便按下“后退”按钮可使用户返回Android的主屏幕。

我已经看到这个问题在几个不同的地方问过,所有问题都得到了相似的答案(我在这里概述了),但是我想在这里提出来收集反馈。

我尝试通过设置其Intent标志FLAG_ACTIVITY_CLEAR_TOP似乎按照文档中所述进行操作来打开Login活动,但是并没有实现将Login活动放在历史记录堆栈底部并防止用户向后导航的目标。以前看过的登录活动。我也尝试android:launchMode="singleTop"在清单中用于Login活动,但这也没有实现我的目标(而且似乎也没有任何作用)。

我相信我需要清除历史记录堆栈,或完成所有先前打开的活动。

一种选择是让每个活动的onCreate检查都处于登录状态,finish()如果尚未登录则。我不喜欢此选项,因为后退按钮仍然可以使用,随着活动自身关闭,它可以返回。

下一个选项是维护LinkedList对所有打开的活动的引用,这些引用可以从任何地方静态访问(可能使用弱引用)。注销后,我将访问此列表并遍历所有先前打开的活动,并finish()在每个活动上进行调用。我可能很快就会开始实现此方法。

但是,我宁愿使用一些Intent标记技巧来完成此操作。我很高兴发现自己可以满足我的应用程序要求,而不必使用上面概述的两种方法中的任何一种。

有没有一种方法可以通过使用Intent或清单设置来实现,或者我的第二个选择是保持LinkedList打开的活动是最好的选择?还是我完全忽略了另一个选择?

Answers:


213

我可以建议您另一种恕我直言的方法。基本上,您需要向所有需要保持登录状态的活动广播注销消息。因此,您可以在所有的Actvities中使用sendBroadcast并安装BroadcastReceiver。像这样:

/** on your logout method:**/
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.package.ACTION_LOGOUT");
sendBroadcast(broadcastIntent);

接收者(安全活动):

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /**snip **/
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.package.ACTION_LOGOUT");
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("onReceive","Logout in progress");
            //At this point you should start the login activity and finish this one
            finish();
        }
    }, intentFilter);
    //** snip **//
}

27
@Christopher,每个活动在创建时都会注册进行广播。当它进入后台(即,一个新的活动进入堆栈的顶部)时,其onStop()将被调用,但它仍可以接收广播。您只需要确保在onDestroy()而不是onStop()中调用unregisterReceiver()。
罗素·戴维斯

9
如果操作系统关闭了堆栈中某处的活动以恢复内存,这是否可行?就是 发送上面的广播后,系统是否会认为它真的完成了,并且在单击后退按钮时不会重新创建它?
JanŻankowski2011年

5
尽管这似乎是一个不错的解决方案,但需要注意的是这不是同步的。
Che Jami

34
一个不错的解决方案,而不是使用上面的代码中所述的注册广播接收器,您应该使用LocalBroadcastManager.getInstance(this).registerReceiver(...)和LocalBroadcastManager.getInstance(this).unregisterReceiver(..) 。否则,您的应用程序可能会收到来自其他任何应用程序的意图(
出于

5
@Warlock你是正确的。这种方法的陷阱是系统破坏了后堆栈中的活动(可能由于各种原因而发生,例如所述的低内存情况)。在这种情况下,Activity实例将不会接收广播。但是系统仍将通过重新创建活动来导航回该活动。可以通过打开开发人员设置“请勿保持活动”来测试/复制该内容
Eric Sc​​hlenz 2014年

151

一个新的Android程序员花了一天的时间研究这个问题并阅读所有这些StackOverflow线程,这似乎是一种仪式。我现在刚起步,我在这里留下了自己的卑微经验,以帮助将来的朝圣者。

首先,根据我的研究,没有明显或直接的方法可以做到这一点。(as of September 2012).您认为您可以简单startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)不能

您可以startActivity(new Intent(this, LoginActivity.class))使用FLAG_ACTIVITY_CLEAR_TOP-进行操作,这将使框架向下搜索堆栈,找到您先前的LoginActivity原始实例,重新创建它并清除(向上)堆栈的其余部分。而且由于“登录”大概位于堆栈的底部,因此您现在拥有一个空堆栈,并且“后退”按钮仅退出应用程序。

但是-仅当您之前将LoginActivity的原始实例保留在堆栈的基础上时,此方法才有效。如果像许多程序员一样finish()LoginActivity一旦用户成功登录后,您选择了这种方式,那么它就不再基于堆栈,并且FLAG_ACTIVITY_CLEAR_TOP语义也不再适用...您最终LoginActivity将在现有堆栈之上创建一个新的堆栈。几乎可以肯定这不是您想要的(奇怪的行为,用户可以“退出”登录界面,退出上一个屏幕)。

所以,如果你有以前finish()倒是的LoginActivity,你需要追求某种机制来清除栈,然后开始新的LoginActivity。似乎@doreamon此线程的答案是最好的解决方案(至少对我不起眼):

https://stackoverflow.com/a/9580057/614880

我强烈怀疑是否让LoginActivity保持生命力的棘手含义会导致很多此类混乱。

祝好运。


5
好答案。大多数人建议使用的FLAG_ACTIVITY_CLEAR_TOP技巧,如果您已完成LoginActivity,就无法使用。
Konsumierer 2012年

谢谢您的回答。另一种情况是有会话时(例如fb),即使我们不调用Login活动,因此堆栈中也没有Login活动的意义。那么上述方法是没有用的。
Prashanth Debbadwar

118

更新

super finishAffinity()方法将有助于减少代码,但可以实现相同的效果。它将完成当前活动以及堆栈中的所有活动,getActivity().finishAffinity()如果您在一个片段中,请使用。

finishAffinity(); 
startActivity(new Intent(mActivity, LoginActivity.class));

原始答案

假设LoginActivity-> HomeActivity-> ...-> SettingsActivity调用signOut():

void signOut() {
    Intent intent = new Intent(this, HomeActivity.class);
    intent.putExtra("finish", true);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // To clean up all activities
    startActivity(intent);
    finish();
}

家庭活动:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    boolean finish = getIntent().getBooleanExtra("finish", false);
    if (finish) {
        startActivity(new Intent(mContext, LoginActivity.class));
        finish();
        return;
    }
    initializeView();
}

这对我有用,希望对您也有帮助。:)


1
我认为您的解决方案假定用户将单击signOut并返回到一个活动(HomeActivity)。如果您有10个活动,该怎么办?
IgorGanapolsky'3

11
如果HomeActivity顶部有10个活动,则FLAG_ACTIVITY_CLEAR_TOP标志将帮助清除所有活动。
thanhbinh84 2012年

1
仅当启动HomeActivity时获得HomeActivity的OnCreate时,此方法才有效。除非已经完成或销毁,否则仅开始家庭活动并不一定会重新创建它。如果不需要重新创建HomeActivity,则不会调用OnCreate,并且在注销后您将坐在自己的家庭活动上。
topwik 2012年

1
这是一种可能的解决方案,并且涉及到对诸如注销之类的简单(针对用户)功能的编码要少得多。
Subin Sebastian

2
Intent.FLAG_ACTIVITY_CLEAR_TOP标志有助于清除包括HomeActivity在内的所有活动,因此,当您再次启动此活动时,将调用onCreate()方法。
thanhbinh84 2014年

73

如果您使用的是API 11或更高版本,则可以尝试以下操作:FLAG_ACTIVITY_CLEAR_TASK-它似乎正好解决了您遇到的问题。显然,API 11之前的人群将不得不使用某种组合,如@doreamon所建议的那样,对所有活动进行额外检查,或者采取其他措施。

(还请注意:要使用此功能,您必须传递FLAG_ACTIVITY_NEW_TASK

Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("finish", true); // if you are checking for this in your other Activities
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | 
                Intent.FLAG_ACTIVITY_CLEAR_TASK |
                Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();

1
像魅力一样工作。非常感谢你!当我在最小API 14上开发时,这是唯一要实现的东西。
AndyB

1
我认为将这种解决方案用于LoginActivity时不需要FLAG_ACTIVITY_CLEAR_TOP。
巴拉特·多德贾

我认为只是finish();这样做会防止注销后再返回。设置标志有什么需要?
Pankaj

这将创建一个异常从Activity上下文外部调用startActivity()需要FLAG_ACTIVITY_NEW_TASK标志
Aman

31

我也花了几个小时...并且同意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
无法使用FLAG_ACTIVITY_CLEAR_TASK尚未添加的API <= 10进行工作
Youans 2013年

1
@christinac您正在谈论FLAG_ACTIVITY_CLEAR_TOP,并且您的代码段中包含FLAG_ACTIVITY_CLEAR_TASK;那哪个有效呢?
玛丽安(MarianPaździoch)2015年

10

很多答案。可能这也将有助于

Intent intent = new Intent(activity, SignInActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
this.finish();

Kotlin版本-

Intent(this, SignInActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also { startActivity(it) }
finish()

4

使用它应该对您有帮助。稍微修改了xbakesx答案。

Intent intent = new Intent(this, LoginActivity.class);
if(Build.VERSION.SDK_INT >= 11) {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
} else {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
startActivity(intent);

4

接受的解决方案不正确,存在问题,因为使用广播接收器不是解决此问题的好主意。如果您的活动已经调用过onDestroy()方法,则不会获得接收者。最好的解决方案是在共享的首选项上设置一个布尔值,然后在活动的onCreate()方法中对其进行检查。如果在用户未登录时不应调用它,则请完成活动。这是该示例代码。非常简单,适用于所有条件。

protected void onResume() {
  super.onResume();
  if (isAuthRequired()) {
    checkAuthStatus();
  }
}

private void checkAuthStatus() {
  //check your shared pref value for login in this method
  if (checkIfSharedPrefLoginValueIsTrue()) {
    finish();
  }
}

boolean isAuthRequired() {
  return true;
}

1
已经好几年了,但我相信我都做到了。每个活动都扩展了LoggedInActivity,后者在onCreate()中检查了用户的登录状态。LoggedInActivity还收听“用户已注销”广播,并尽其所能来对此做出响应。
skyler 2015年

2
您可能应该将checkAuthStatus放在onResume()方法中。因为当您按下“后退”按钮时,更有可能恢复而不是创建活动。
maysi

1
@maysi感谢您的建议,是的,返回按钮也可以正常工作,我更新了条目
Yekmer Simsek 2015年

4

有时finish()不工作

我已经解决了这个问题

finishAffinity()


3

对于这个问题,我建议采取另一种方法。也许它不是最有效的一种,但我认为它是最简单的应用程序,只需要很少的代码。在第一个活动(在我的情况下为登录活动)中编写下一个代码将使用户注销后无法返回到先前启动的活动。

@Override
public void onBackPressed() {
    // disable going back to the MainActivity
    moveTaskToBack(true);
}

我假设LoginActivity在用户登录后立即完成,因此他以后无法通过按后退按钮返回到它。相反,用户必须按应用程序内的注销按钮才能正确注销。此注销按钮将实现的目的很简单,如下所示:

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

欢迎所有建议。


2

选择的答案很聪明而且很棘手。这是我的做法:

LoginActivity是任务的根活动,在Manifest.xml 中将android:noHistory =“ true”设置为该任务;假设您要从SettingsActivity注销,可以按照以下步骤进行操作:

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);

2

这是我在应用程序中想到的解决方案。

在我的LoginActivity中,成功处理登录后,根据API级别以不同的方式启动下一个。

Intent i = new Intent(this, MainActivity.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    startActivity(i);
    finish();
} else {
    startActivityForResult(i, REQUEST_LOGIN_GINGERBREAD);
}

然后在我的LoginActivity的onActivityForResult方法中:

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
        requestCode == REQUEST_LOGIN_GINGERBREAD &&
        resultCode == Activity.RESULT_CANCELED) {
    moveTaskToBack(true);
}

最后,在其他任何活动中处理注销后:

Intent i = new Intent(this, LoginActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);

当使用Gingerbread时,如果我按MainActivity中的“后退”按钮,则LoginActivity将立即隐藏。在Honeycomb和更高版本上,我只是在处理登录后完成LoginActivity,并且在处理注销后可以正确地重新创建它。


它对我不起作用。就我而言,我使用了fragactivity。不调用Onactivityresult方法。
Mohamed Ibrahim 2015年

1

这为我工作:

     // After logout redirect user to Loing Activity
    Intent i = new Intent(_context, MainActivity.class);
    // Closing all the Activities
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

    // Add new Flag to start new Activity
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    // Staring Login Activity
    _context.startActivity(i);

您能否详细说明您的答案,并提供有关您提供的解决方案的更多说明?
abarisone 2015年

0

使用StartActivityForResult开始活动,注销时设置结果,并根据结果完成活动

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivityForResult(intent, BACK_SCREEN);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case BACK_SCREEN:
        if (resultCode == REFRESH) {
            setResult(REFRESH);
            finish();
        }
        break;
    }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        AlertDialog alertDialog = builder.create();

        alertDialog
                .setTitle((String) getResources().getText(R.string.home));
        alertDialog.setMessage((String) getResources().getText(
                R.string.gotoHome));
        alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {

                        setResult(REFRESH);
                        finish();
                    }

                });

        alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "No",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {
                    }
                });
        alertDialog.show();
        return true;
    } else
        return super.onKeyDown(keyCode, event);

}

0

@doreamon提供的解决方案适用于除以下情况以外的所有情况:

如果登录后,“杀死登录”屏幕用户直接导航到中间屏幕。例如,在A-> B-> C的流程中,导航方式如下:登录-> B-> C->按下返回首页的快捷方式。使用FLAG_ACTIVITY_CLEAR_TOP仅清除C活动,因为Home(A)不在堆栈历史记录中。在屏幕上按返回将使我们返回到B。

为了解决这个问题,我们可以保留一个活动堆栈(Arraylist),当按下home键时,我们必须杀死该堆栈中的所有活动。


0

通过管理SharedPreferences或Application Activity中的标志,这是可能的。

启动应用程序时(在启动屏幕上)将标志设置为false;在注销Click事件上,只需将标志设置为true,然后在每个活动的OnResume()中,检查标志是否为true,然后调用finish()。

它就像一个魅力:)


0

单击注销,您可以称之为

private void GoToPreviousActivity() {
    setResult(REQUEST_CODE_LOGOUT);
    this.finish();
}

先前活动的onActivityResult()再次调用上述代码,直到完成所有活动。


1
这意味着它必须线性遍历所有活动,而不是一次全部广播finish()吗?
IgorGanapolsky'3

@Igor G.是的,这是依次完成的另一种方式
Mak,

-1

一种选择是让每个活动的onCreate检查登录状态,如果未登录,则完成()。我不喜欢此选项,因为后退按钮仍将可用,随着活动自身的关闭而返回。

您要做的是在onStop()或onPause()方法上调用logout()和finish()。这将迫使Android在重新启动活动时调用onCreate(),因为它将不再在活动堆栈中包含该活动。然后按照您所说的做,在onCreate()中检查登录状态,如果未登录,则转到登录屏幕。

您可以做的另一件事是检查onResume()中的登录状态,如果未登录,请完成()并启动登录活动。


我希望不要在每次活动暂停或停止时注销。另外,该应用程序会启动注销或登录,因此我实际上不需要检查是否已登录。
skyler 2010年

@Ricardo:您的解决方案是否不需要BroadcastReceiver?
IgorGanapolsky'3
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.