退出登录时清除活动历史记录堆栈,防止“返回”按钮打开仅限登录的活动页面。

243
我的应用中所有的活动都需要用户登录才能查看。用户可以在几乎任何活动中退出。这是应用程序的要求。如果用户在任何时候退出登录,我想将用户发送到“登录 Activity”。此时,我希望该活动位于历史堆栈的底部,以便按下“返回”按钮返回用户到Android的主屏幕。
我已经在几个不同的地方看到了这个问题,都有类似的答案(我在这里概述),但我想在这里提出这个问题以收集反馈。
我尝试通过将“登录 Activity”的 Intent 标志设置为 FLAG_ACTIVITY_CLEAR_TOP 打开它,这似乎按照文档所述进行操作,但不能实现我的目标,即将“登录 Activity”放置在历史堆栈的底部,并防止用户导航回先前浏览过的已登录活动。我还尝试在清单中为“登录 Activity”使用 android:launchMode="singleTop",但这也无法实现我的目标(而且似乎没有任何效果)。
我认为我需要清除历史堆栈或完成所有先前打开的活动。
一种选择是让每个活动的 onCreate 检查登录状态,并在未登录时 finish()。我不喜欢这个选项,因为返回按钮仍然可用,当活动关闭时会导航回去。
下一个选项是维护对所有打开的活动的引用的 LinkedList,该 LinkedList 是从任何地方静态访问的(可能使用弱引用)。在注销时,我将访问此列表并迭代所有先前打开的活动,在每个活动上调用 finish()。我可能很快开始实施这种方法。
然而,我宁愿使用一些 Intent 标志技巧来完成这个目标。如果能够不使用我上面概述的两种方法之一就能满足应用程序的要求,那么我将非常高兴。有没有一种通过使用Intent或清单设置来实现这一点的方法,还是我的第二个选项,维护一个打开活动的LinkedList,是最好的选择?或者还有其他我完全忽略的选项吗?
18个回答

215

我认为有一个更加健壮的方法可以推荐给您。 基本上,您需要向所有需要保持登录状态的活动广播注销消息。因此,您可以使用sendBroadcast并在所有活动中安装BroadcastReceiver。 像这样:

/** on your logout method:**/
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.package.ACTION_LOGOUT");
sendBroadcast(broadcastIntent);
接收器(安全的Activity):
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()方法中调用unregisterReceiver(),而不是在onStop()中调用即可。 - Russell Davis
10
如果堆栈中的某个活动因操作系统需要回收内存而被关闭,这种方法是否有效?也就是说,如果发送了上述广播,系统会认为它已经完成,不会在按下返回按钮时重新创建它吗? - Jan Żankowski
5
虽然这看起来是一个优雅的解决方案,但重要的是要注意它并不同步。 - Che Jami
34
一个好的解决方案,但是不要像上面的代码一样注册Broadcast接收器,而应该使用LocalBroadcastManager.getInstance(this).registerReceiver(...)和LocalBroadcastManager.getInstance(this).unregisterReceiver(..)。否则,你的应用程序可能会收到来自任何其他应用程序的意图(安全问题)。 - Uku Loskit
5
@Warlock 你说得对。这种方法的陷阱在于,当后台活动由系统销毁时(可能由于低内存情况等各种原因),活动实例将不再存在以接收广播。但是系统仍将通过重新创建它来返回到那个活动。可以通过打开开发人员选项“不保留活动”来测试/复制此操作。 - Eric Schlenz
显示剩余12条评论

154

似乎,每一个新的Android程序员在研究这个问题并阅读所有这些StackOverflow线程的一天都是必要的。现在我已经成为了一个新手,并留下了我的谦逊经验,以帮助未来的朝圣者。

首先,根据我的研究(截至2012年9月),没有明显或立即的方法来实现这个目标。你会认为你可以简单地使用 startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK),但不行

你可以使用 FLAG_ACTIVITY_CLEAR_TOPstartActivity(new Intent(this, LoginActivity.class)) - 这将导致框架向下搜索堆栈,找到早期的原始实例化LoginActivity,重新创建它并清除其余(向上)的栈。由于登录页面通常位于堆栈底部,所以现在您有一个空堆栈且“返回”按钮仅退出应用程序。

但是-只有当您之前将那个原始的LoginActivity实例保留在堆栈底部时,这种方法才有效。如果像很多程序员一样,在用户成功登录后选择了finish()LoginActivity,那么它将不再在堆栈底部,FLAG_ACTIVITY_CLEAR_TOP语义将不再适用......您最终会在现有堆栈顶部创建一个新的LoginActivity。这几乎肯定不是您想要的(出现奇怪的行为,用户可以通过“返回”从登录页面进入以前的屏幕)。

因此,如果您之前使用了finish()关闭了LoginActivity,则需要寻找一些机制来清除堆栈,然后启动新的LoginActivity。似乎在这个帖子中,@doreamon的答案是最好的解决方案(至少对我而言):

https://dev59.com/iXA75IYBdhLWcg3w9-Ox#9580057

我强烈怀疑是否保留LoginActivity实例的复杂影响正在引起很多混乱。

祝你好运。


5
好的答案。大多数人建议使用的FLAG_ACTIVITY_CLEAR_TOP技巧,如果您已经完成了LoginActivity,那么这种方法就不起作用了。 - Konsumierer
谢谢你的回答。另一种情况是,当存在一个会话(例如像Facebook一样),即使我们不调用登录活动,也没有在堆栈中登录活动的意义。那么上述方法就无用了。 - Prashanth Debbadwar

124

更新

使用超级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
我认为你的解决方案假设用户会点击“登出”然后返回到一个活动(HomeActivity)。但是如果栈中有10个活动呢? - IgorGanapolsky
11
如果您在HomeActivity的顶部有10个活动,则FLAG_ACTIVITY_CLEAR_TOP标志将有助于清除所有这些活动。 - thanhbinh84
1
只有在启动HomeActivity时,才能使其正常工作,并且需要获取HomeActivity的OnCreate。仅仅启动主页活动并不一定会重新创建它,除非它已经被结束或销毁。如果HomeActivity不需要重新创建,则不会调用OnCreate,在您注销后,您将停留在主页活动上。 - topwik
1
这是一个可能的解决方案,它涉及到更少的编码,对于用户来说,像注销这样的简单功能。 - C--
2
Intent.FLAG_ACTIVITY_CLEAR_TOP标志有助于清除所有活动,包括HomeActivity,因此当您再次启动此活动时,将调用onCreate()方法。 - thanhbinh84
显示剩余8条评论

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

1
非常好用。非常感谢!由于我在开发时使用的是min API 14,这是唯一需要实现的事情。 - AndyB
1
我认为在使用这种解决方案的时候,我们不需要FLAG_ACTIVITY_CLEAR_TOP。 - Bharat Dodeja
我认为只需要 finish(); 就可以防止在注销后返回上一页。设置标志的必要性是什么? - Pankaj
这会创建一个异常:从Activity上下文之外调用startActivity()需要使用FLAG_ACTIVITY_NEW_TASK标志。 - Aman

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

1
由于“FLAG_ACTIVITY_CLEAR_TASK”尚未添加,因此无法在API <= 10上运行。 - Youans
1
@christinac 你在谈论FLAG_ACTIVITY_CLEAR_TOP,而你的代码片段中有FLAG_ACTIVITY_CLEAR_TASK;这是有效的吗? - Marian Paździoch

18

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

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

被接受的解决方案并不正确,它存在问题,因为使用广播接收器对于这个问题来说并不是一个好主意。如果您的活动已经调用了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
已经过了好几年,但我相信我两个都做了。每个Activity都扩展了LoggedInActivity,它在onCreate()中检查用户的登录状态。LoggedInActivity还侦听“用户注销”广播,并执行必要的操作以响应此事件。 - skyler
2
更可能的是,您应该将checkAuthStatus放在“onResume()”方法中。因为当您按下返回按钮时,活动更有可能被恢复而不是创建。 - maysi
1
@maysi 感谢您的建议,是的,这种方式返回按钮也会正常工作,我已经更新了条目。 - Yekmer Simsek

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
有时候,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();

欢迎提出所有建议。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接