242

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

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

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

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

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

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

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

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

4

18 回答 18

216

我可以建议你另一种更强大的方法恕我直言。基本上,您需要向所有需要保持登录状态的活动广播注销消息。因此,您可以在所有活动中使用sendBroadcast并安装 a 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 **//
}
于 2010-06-09T18:24:44.013 回答
152

一个新的 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 活着的棘手影响导致了很多这种混乱。

祝你好运。

于 2012-09-17T23:06:54.327 回答
122

更新

superfinishAffinity()方法将有助于减少代码,但实现相同。它将完成当前活动以及堆栈中的所有活动,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();
}

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

于 2012-03-06T08:16:06.757 回答
75

如果您使用的是 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();
于 2013-02-04T22:54:47.147 回答
32

我也花了几个小时...并同意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();
}
于 2013-06-19T14:15:06.867 回答
17

很多答案。可能这个也有帮助-

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()
于 2015-07-22T09:38:41.770 回答
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);
于 2014-06-25T08:37:45.467 回答
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;
}
于 2015-07-09T16:32:55.093 回答
4

有时finish()不工作

我已经解决了这个问题

finishAffinity()

于 2017-08-31T04:43:49.927 回答
3

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

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

于 2013-11-04T22:56:30.123 回答
3

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

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

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

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

欢迎所有建议。

于 2018-02-12T12:07:14.897 回答
2

选择的答案既聪明又棘手。我是这样做的:

LoginActivity是任务的根Activity,在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);
于 2014-12-18T13:19:14.647 回答
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);
于 2015-06-24T12:14:29.443 回答
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);

}
于 2010-12-15T12:50:34.487 回答
0

单击注销时,您可以调用它

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

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

于 2012-02-23T08:21:27.550 回答
0

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

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

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

于 2014-06-20T11:19:00.517 回答
0

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

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

它就像一个魅力:)

于 2014-07-31T12:47:49.377 回答
-1

一种选择是让每个活动的 onCreate 检查登录状态,如果未登录,则使用 finish()。我不喜欢这个选项,因为后退按钮仍然可以使用,在活动自行关闭时导航回来。

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

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

于 2010-06-09T17:52:09.097 回答