首页 文章

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

提问于
浏览
214

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

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

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

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

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

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

不过,我宁愿使用一些 Intent 标志技巧来实现这一目标 . 我不需要使用上面概述的两种方法中的任何一种 .

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

18 回答

  • 146

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

    一个新的Android程序员花了一天时间研究这个问题并阅读所有这些StackOverflow线程似乎是一种成熟的仪式 . 我现在是新成立的,我离开这里看看我的谦逊经历,以帮助未来的朝圣者 .

    首先,根据我的研究,没有明显或直接的方法可以做到这一点 (as of September 2012). 你认为你可以简单 startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK) 但是 no .

    您可以使用 FLAG_ACTIVITY_CLEAR_TOP 执行 startActivity(new Intent(this, LoginActivity.class)) - 这将导致框架向下搜索堆栈,找到您之前的LoginActivity原始实例,重新创建它并清除其余的(向上)堆栈 . 由于Login可能位于堆栈的底部,因此您现在有一个空堆栈,而Back按钮就会退出应用程序 .

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

    因此,如果您之前有 LoginActivity ,那么您需要寻求一些机制来清除堆栈,然后启动新的 LoginActivity . 似乎 @doreamon 在这个帖子中的答案是最好的解决方案(至少对我不起眼的看法):

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

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

    祝好运 .

  • 0

    UPDATE

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

    finishAffinity(); 
    startActivity(new Intent(mActivity, LoginActivity.class));
    

    ORIGINAL ANSWER

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

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

  • 1

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

    一世这也花了几个小时...并且同意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();
    }
    
  • 1

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

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

    使用它,它应该对您有所帮助 . 稍微修改了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);
    
  • 0

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

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

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

    最后,在处理任何其他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,并在处理注销后正确地重新创建 .

  • 105

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

    欢迎所有建议 .

  • 67

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

    @doreamon提供的解决方案适用于所有情况,除了一个:

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

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

  • 1

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

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

    它像一个魅力:)

  • 0

    点击Logout,您可以调用它

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

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

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

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

相关问题