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

sky*_*ler 228 android activity-lifecycle android-lifecycle

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

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

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

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

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

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

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

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

Fra*_*ita 208

我可以建议你另一种方法恕我直言更强大.基本上,您需要向需要保持登录状态的所有活动广播注销消息.因此,您可以在所有Actvities中使用sendBroadcast并安装BroadcastReceiver.像这样的东西:

/** on your logout method:**/
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.package.ACTION_LOGOUT");
sendBroadcast(broadcastIntent);
Run Code Online (Sandbox Code Playgroud)

接收者(安全活动):

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 **//
}
Run Code Online (Sandbox Code Playgroud)

  • 一个不错的解决方案,但不应使用上面代码中描述的注册广播接收器,而应使用LocalBroadcastManager.getInstance(this).registerReceiver(...)和LocalBroadcastManager.getInstance(this).unregisterReceiver(..) .另外,您的应用程序可以从任何其他应用程序接收意图(安全问题) (34认同)
  • @Christopher,每个活动在创建时注册广播.当它进入后台时(即,一个新的活动到达堆栈的顶部),它的onStop()将被调用,但它仍然可以接收广播.您只需要确保在onDestroy()而不是onStop()中调用unregisterReceiver(). (27认同)
  • 如果操作系统关闭堆栈中的某个活动以恢复内存,这是否有效?IE浏览器.系统会在上面的广播发送后认为它真的完成了,并且在点击后退按钮时不会重新创建它吗? (9认同)
  • 虽然这似乎是一个优雅的解决方案,但重要的是要注意这不是同步的. (5认同)
  • @Warlock你是对的.这种方法的缺陷是当后台堆栈中的Activity被系统破坏时(可能由于各种原因发生,例如所述的低内存场景).在这种情况下,Activity实例将不会接收广播.但是系统仍然会通过重新创建它来浏览该Activity.可以通过打开开发者设置"不要保持活动"来测试/复制 (5认同)
  • 如果backstack上的活动被系统(低内存)杀死并且此活动不会接收广播,该怎么办?但是这项活动将保持在堆栈记录中.不确定这是否是最具防弹性的解决方案. (3认同)

Mik*_*ass 148

一个新的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这个帖子的答案是最好的解决方案(至少对我不起眼的看法):

/sf/answers/670604021/

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

祝好运.

  • 我想说谢谢你为这个问题编写了最全面的分析和解决方案. (24认同)
  • 好答案.大多数人建议使用的FLAG_ACTIVITY_CLEAR_TOP技巧,如果你已完成LoginActivity,则不起作用. (5认同)

tha*_*h84 114

UPDATE

超级finishAffinity()方法将有助于减少代码但实现相同.它将完成当前活动以及堆栈中的所有活动,getActivity().finishAffinity()如果您在片段中,则使用它.

finishAffinity(); 
startActivity(new Intent(mActivity, LoginActivity.class));
Run Code Online (Sandbox Code Playgroud)

原始答案

假设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();
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

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

  • 如果HomeActivity顶部有10个活动,FLAG_ACTIVITY_CLEAR_TOP标志将有助于清除所有活动. (11认同)
  • Intent.FLAG_ACTIVITY_CLEAR_TOP标志有助于清除包括HomeActivity在内的所有活动,因此当您再次启动此活动时将调用onCreate()方法. (2认同)

xba*_*esx 71

如果您使用的是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();
Run Code Online (Sandbox Code Playgroud)


小智 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();
}
Run Code Online (Sandbox Code Playgroud)


Gul*_*han 7

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

Intent intent = new Intent(activity, SignInActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
this.finish();
Run Code Online (Sandbox Code Playgroud)