ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

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

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

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

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


M
Martin Konecny

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

@Christopher,每个活动在创建时都会注册广播。当它进入后台时(即一个新的活动到达栈顶),它的 onStop() 将被调用,但它仍然可以接收广播。您只需要确保在 onDestroy() 而不是 onStop() 中调用 unregisterReceiver()。
如果堆栈中某处的活动被操作系统关闭以恢复内存,这是否有效? IE。发送上述广播后系统是否认为它真的完成了,并且不会在点击返回按钮时重新创建它?
虽然这看起来确实是一个优雅的解决方案,但重要的是要注意这不是同步的。
一个不错的解决方案,但不是使用上面代码中描述的注册广播接收器,您应该使用 LocalBroadcastManager.getInstance(this).registerReceiver(...) 和 LocalBroadcastManager.getInstance(this).unregisterReceiver(..) .否则,您的应用程序可以从任何其他应用程序接收意图(安全问题)
@Warlock你是对的。这种方法的缺陷是当后台堆栈中的 Activity 被系统破坏时(可能由于各种原因而发生,例如提到的低内存情况)。在这种情况下,Activity 实例将不会收到广播。但是系统仍将通过重新创建该 Activity 来导航回它。这可以通过打开开发人员设置“不保留活动”来测试/复制
C
Community

一个新的 Android 程序员花一天时间研究这个问题并阅读所有这些 StackOverflow 线程似乎是一种成人仪式。我现在是新启蒙者,我在这里留下我卑微的经历,以帮助未来的朝圣者。

首先,根据我的研究,没有明显或直接的方法可以做到这一点(as of September 2012).您认为可以简单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。这几乎肯定不是您想要的(奇怪的行为,用户可以“返回”退出登录到前一个屏幕)。

因此,如果您以前finish() 使用过 LoginActivity,则需要采用某种机制来清除堆栈,然后开始新的 LoginActivity。似乎 @doreamon 在这个线程中的答案是最好的解决方案(至少在我谦虚的眼里):

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

我强烈怀疑你是否让 LoginActivity 活着的棘手影响导致了很多这种混乱。

祝你好运。


好答案。大多数人建议使用的 FLAG_ACTIVITY_CLEAR_TOP 技巧在您完成 LoginActivity 时不起作用。
感谢您的回答。另一种情况就像有一个会话(例如 fb),即使我们不调用登录活动,所以堆栈中没有登录活动点。那么上面提到的方法是没有用的。
t
thanhbinh84

更新

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

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


我认为您的解决方案假定用户将单击signOut 并返回到一个活动(HomeActivity)。如果堆栈上有 10 个活动怎么办?
如果 HomeActivity 顶部有 10 个活动,则 FLAG_ACTIVITY_CLEAR_TOP 标志将有助于清除所有活动。
这只有在启动 HomeActivity 时才有效,您获得了 HomeActivity 的 OnCreate。简单地开始家庭活动不一定会重新创建它,除非它已经完成或销毁。如果不需要重新创建 HomeActivity,则不会调用 OnCreate,并且在您注销后,您将坐在您的家庭活动中。
这是一种可能的解决方案,它涉及的代码少得多,只需编写一个简单的(针对用户的)功能,例如注销。
Intent.FLAG_ACTIVITY_CLEAR_TOP 标志有助于清理包括 HomeActivity 在内的所有活动,因此当您再次启动此活动时将调用 onCreate() 方法。
Z
Ziem

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

像魅力一样工作。非常感谢!当我在 min API 14 上开发时,这是唯一要实现的。
我认为在将这个解决方案用于 LoginActivity 时,我们不需要 FLAG_ACTIVITY_CLEAR_TOP。
我认为只有 finish(); 可以防止在注销后返回。设置标志需要什么?
这会创建一个异常从 Activity 上下文外部调用 startActivity() 需要 FLAG_ACTIVITY_NEW_TASK 标志
C
Community

我也为此花费了几个小时......并同意 FLAG_ACTIVITY_CLEAR_TOP 听起来像您想要的:清除整个堆栈,除了正在启动的活动,因此“后退”按钮退出应用程序。然而正如 Mike Repass 提到的,FLAG_ACTIVITY_CLEAR_TOP 仅在您启动的活动已经在堆栈中时才有效;当活动不存在时,标志不会做任何事情。

该怎么办?使用 FLAG_ACTIVITY_NEW_TASK 将正在启动的 Activity 放入堆栈中,这会使该 Activity 成为历史堆栈上新任务的开始。 然后添加 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();
}

不适用于 API<=10,因为尚未添加 FLAG_ACTIVITY_CLEAR_TASK
@christinac您在谈论 FLAG_ACTIVITY_CLEAR_TOP 并且您的代码片段具有 FLAG_ACTIVITY_CLEAR_TASK;那么哪个有效呢?
G
Gulshan

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

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

Z
Ziem

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

Y
Yekmer Simsek

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

已经好几年了,但我相信我两者都做到了。每个Activity都扩展了LoggedInActivity,它在onCreate()中检查用户的登录状态。 LoggedInActivity 还侦听“用户注销”广播,并尽其所能对此作出响应。
更有可能您应该将 checkAuthStatus 放在 onResume() 方法中。因为当您按下后退按钮时,活动更有可能恢复而不是创建。
@maysi 感谢您的建议,是的,这种方式后退按钮也可以正常工作,我更新了条目
A
AJay

有时finish()不工作

我已经解决了这个问题

finishAffinity()


Z
Ziem

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

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


它不适合我。就我而言,我使用了片段活动。不调用 Onactivityresult 方法。
N
Nil

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

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

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

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

欢迎所有建议。


t
t-gao

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

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

P
Preetansh

这对我有用:

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

您能否详细说明您的答案,添加更多关于您提供的解决方案的描述?
E
Eby

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

}

S
Surendra Kumar

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

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

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


k
kiswa

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

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

它就像一个魅力:)


Z
Ziem

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

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

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


这意味着它必须线性遍历所有活动,而不是一次广播finish()?
@Igor G. 是的,这是按顺序完成的另一种方法
R
Ricardo Villamil

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

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

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


我不想在每个活动暂停或停止时注销。此外,应用程序启动注销或登录,所以我不需要检查是否登录,真的。
@Ricardo:您的解决方案不需要广播接收器吗?