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

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

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

我已经尝试通过将登录活动的Intent标志设置为FLAG_ACTIVITY_CLEAR_TOP来打开登录活动,这似乎是在文档中列出的,但没有实现我的目标,即将登录活动置于历史堆栈的底部,并阻止用户导航回以前看到的登录活动。我还尝试在清单中使用android:launchMode="singleTop"作为登录活动,但这也没有实现我的目标(而且似乎无论如何都没有效果)。

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

一种选择是让每个活动的onCreate检查登录状态,如果没有登录则检查finish()。我不喜欢这个选项,因为返回按钮仍然可以使用,当活动关闭时返回。

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

然而,我宁愿使用一些Intent标志来实现这一点。我非常高兴地发现,我可以满足我的应用程序的需求,而不必使用我上面概述的两种方法中的任何一种。

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

128501 次浏览

一种选择是让每个活动的onCreate检查登录状态,如果没有登录则检查finish()。我不喜欢这个选项,因为返回按钮仍然可以使用,当活动关闭时返回。

你要做的是在你的onStop()或onPause()方法上调用logout()和finish()。这将迫使Android在活动恢复时调用onCreate(),因为它将不再在其活动堆栈中。然后按你说的做,在onCreate()检查登录状态,如果没有登录,转发到登录屏幕。

你可以做的另一件事是在onResume()中检查登录状态,如果没有登录,finish()并启动登录活动。

依我看,我可以建议你另一种更可靠的方法。 基本上,您需要向所有需要保持登录状态的活动广播注销消息。所以你可以使用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 **//
}

开始你的活动与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);


}

点击登出,你可以称之为

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

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

更新

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();
}

HomeActivity:

@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();
}

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

对于一个新的Android程序员来说,花一天时间研究这个问题并阅读所有这些StackOverflow线程似乎是一种必经之路。我现在是新入会的,我在这里留下我卑微的经历来帮助未来的朝圣者。

首先,根据我的研究,没有明显或直接的方法来做到这一点。你会认为你可以简单地startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK),但没有

你可以用FLAG_ACTIVITY_CLEAR_TOP执行startActivity(new Intent(this, LoginActivity.class))——这将导致框架向下搜索堆栈,找到你早期的LoginActivity的原始实例,重新创建它并清除堆栈的其余部分(向上)。由于Login可能位于堆栈的底部,所以现在有一个空堆栈,Back按钮只是退出应用程序。

但是——这只在你之前在你的栈的底部留下LoginActivity的原始实例时有效。如果像许多程序员一样,在用户成功登录后选择finish()LoginActivity,则它不再位于堆栈的底部,并且FLAG_ACTIVITY_CLEAR_TOP语义不适用…你最终会在现有堆栈的顶部创建一个新的LoginActivity。这几乎肯定不是你想要的(奇怪的行为,用户可以“返回”登录到前一个屏幕)。

因此,如果你之前已经__abc0d了LoginActivity,你需要寻求一些机制来清除堆栈,然后开始一个新的LoginActivity。在这个线程中@doreamon的答案似乎是最好的解决方案(至少在我看来):

https://stackoverflow.com/a/9580057/614880 < a href = " https://stackoverflow.com/a/9580057/614880 " > < / >

我强烈怀疑,是否保留LoginActivity这一棘手的问题导致了很多这种混乱。

祝你好运。

如果你正在使用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();

我也在这上面花了几个小时……并且同意FLAG_ACTIVITY_CLEAR_TOP听起来像你想要的:清除整个堆栈,除了正在启动的活动,所以后退按钮退出应用程序。然而,正如迈克再经过所提到的,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();
}

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

在我的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);
}

最后,在处理任何其他Activity中的注销后:

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,并在处理登出后正确地重新创建LoginActivity。

@doreamon提供的解决方案适用于所有情况,除了一种情况:

如果登录后,kill login屏幕用户直接导航到中间屏幕。如。 在a ->B->C流程中,按如下方式进行导航:Login ->B->C ->按“快捷键”返回主页。使用FLAG_ACTIVITY_CLEAR_TOP只清除C活动,因为Home(A)不在堆栈历史上。 在屏幕上按下Back键将会返回到b

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

用这个应该对你有帮助。稍微修改了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);

这可以通过在SharedPreferences或Application Activity中管理一个标志来实现。

在应用程序启动时(在启动画面上)设置标志= false;登出时单击事件,将标志设置为true,在每个活动的OnResume()中,检查标志是否为true,然后调用finish()。

它的工作就像一个魅力:)

所选的答案既巧妙又巧妙。我是这样做的:

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);

这招对我很管用:

     // 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);

接受的解决方案是不正确的,它有问题,因为使用广播接收器不是一个好主意,这个问题。如果你的活动已经调用了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;
}

答案很多。也许这个也会有帮助-

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

芬兰湾的科特林版-

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

有时finish()不工作

我已经解决了这个问题

finishAffinity()

我建议用另一种方法来解决这个问题。也许它不是最有效的,但我认为它是最容易应用的,需要很少的代码。在您的第一个活动(在我的情况下是登录活动)中编写下一个代码将不会让用户在注销后返回到先前启动的活动。

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

我假设LoginActivity在用户登录之后就完成了,这样他以后就不能通过按返回按钮回到它。相反,用户必须按下应用程序内部的注销按钮才能正确注销。这个退出按钮实现的目的很简单,如下所示:

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

欢迎提出任何建议。