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

问题描述 投票:221回答:18

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

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

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

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

一种选择是让每个活动的onCreate检查登录状态,如果没有登录,则检查finish()。我不喜欢这个选项,因为后退按钮仍可供使用,当活动靠近时自动导航。

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

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

有没有办法通过使用Intent或清单设置来实现这一点,或者是我的第二个选择,保持开放活动的LinkedList是最佳选择?或者我还有另一种选择吗?

android android-lifecycle activity-lifecycle
18个回答
205
投票

我可以建议你另一种方法恕我直言更健壮。基本上,您需要向需要保持登录状态的所有活动广播注销消息。因此,您可以使用sendBroadcast并在所有Actvities中安装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 **//
}

1
投票

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

在我的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,并在处理注销后正确地重新创建。


1
投票

有时finish()不工作

我已经解决了这个问题

finishAffinity()


1
投票

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

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

我假设LoginActivity刚刚在用户登录后完成,因此他以后不能通过按后退按钮返回到它。相反,用户必须按下应用程序内的注销按钮才能正确注销。这个注销按钮将实现的是一个简单的意图如下:

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

欢迎所有建议。


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提供的解决方案适用于所有情况,除了一个:

如果登录后,Killing Login屏幕用户直接导航到中间屏幕。例如在A-> B-> C的流程中,导航如:登录 - > B - > C - >按快捷键返回主页。使用FLAG_ACTIVITY_CLEAR_TOP仅清除C活动,因为主页(A)不在堆栈历史记录中。按回屏幕将返回B.

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


0
投票

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

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

它像一个魅力:)


0
投票

点击退出您可以调用它

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

之前的Activity的onActivityResult()再次调用上面的代码,直到您完成所有活动。


0
投票

这对我有用:

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

-1
投票

一个选项是让每个活动的onCreate检查登录状态,如果没有登录则完成()。我不喜欢这个选项,因为后退按钮仍可供使用,当活动靠近时自动导航。

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

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


147
投票

一个新的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原始实例,重新创建它并清除其余的(向上)堆栈。由于Login可能位于堆栈的底部,因此您现在有一个空堆栈,而Back按钮就会退出应用程序。

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

因此,如果你以前有finish()LoginActivity,你需要寻求一些机制来清理你的堆栈,然后开始一个新的LoginActivity。似乎@doreamon在这个帖子中的答案是最好的解决方案(至少对我不起眼的看法):

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

我强烈怀疑你是否将LoginActivity保持活着这一棘手的问题导致了很多这种混乱。

祝好运。


108
投票

UPDATE

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

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


68
投票

如果您使用的是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();

30
投票

我也花了几个小时...同意FLAG_ACTIVITY_CLEAR_TOP听起来像你想要的:清除整个堆栈,除了正在启动的活动,所以Back按钮退出应用程序。然而正如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();
}

5
投票

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

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

3
投票

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

3
投票

接受的解决方案不正确,它有问题,因为使用广播接收器对这个问题不是一个好主意。如果您的活动已经调用了onDestroy()方法,那么您将无法获得接收器。最佳解决方案是在共享首选项上使用布尔值,并在您的activty的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
投票

选择的答案很聪明,也很棘手。我是这样做的:

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);
© www.soinside.com 2019 - 2024. All rights reserved.