Al cerrar sesión, borre la pila del historial de actividades, evitando que el botón "atrás" abra las actividades solo de inicio de sesión

235

Todas las actividades en mi aplicación requieren que un usuario inicie sesión para ver. Los usuarios pueden cerrar sesión en casi cualquier actividad. Este es un requisito de la aplicación. En cualquier momento, si el usuario cierra sesión, quiero enviarlo al inicio de sesión Activity. En este punto, quiero que esta actividad se encuentre en la parte inferior de la pila de historial para que al presionar el botón "atrás" el usuario vuelva a la pantalla de inicio de Android.

He visto esta pregunta en diferentes lugares, todos respondidos con respuestas similares (que describo aquí), pero quiero plantearla aquí para recopilar comentarios.

Intenté abrir la actividad de inicio de sesión configurando sus Intentindicadores, FLAG_ACTIVITY_CLEAR_TOPque parecen hacer lo que se describe en la documentación, pero no alcanzo mi objetivo de colocar la actividad de inicio de sesión en la parte inferior de la pila del historial y evitar que el usuario navegue hacia atrás a actividades registradas previamente vistas. También intenté usar android:launchMode="singleTop"para la actividad de inicio de sesión en el manifiesto, pero esto tampoco cumple mi objetivo (y parece no tener ningún efecto de todos modos).

Creo que necesito borrar la pila de historial o terminar todas las actividades abiertas anteriormente.

Una opción es hacer que cada actividad onCreateverifique el estado de inicio de sesión, y finish()si no está conectado. No me gusta esta opción, ya que el botón Atrás todavía estará disponible para su uso, navegando hacia atrás a medida que las actividades se cierran.

La siguiente opción es mantener una LinkedListreferencia de todas las actividades abiertas a las que está accesible estáticamente desde cualquier lugar (quizás utilizando referencias débiles). Al cerrar sesión, accederé a esta lista e iteraré sobre todas las actividades abiertas anteriormente, invocando finish()cada una. Probablemente comenzaré a implementar este método pronto.

IntentSin embargo, prefiero usar algunos trucos de bandera para lograr esto. Me encantaría descubrir que puedo cumplir con los requisitos de mi aplicación sin tener que usar ninguno de los dos métodos que describí anteriormente.

¿Hay alguna manera de lograr esto mediante el uso Intento la configuración de manifiesto, o es mi segunda opción, mantener una LinkedListde las actividades abiertas la mejor opción? ¿O hay otra opción que estoy pasando por alto por completo?

Skyler
fuente

Respuestas:

213

Te puedo sugerir otro enfoque en mi humilde opinión más robusto. Básicamente, debe transmitir un mensaje de cierre de sesión a todas sus actividades que necesiten permanecer bajo un estado de inicio de sesión. Por lo tanto, puede usar sendBroadcaste instalar a BroadcastReceiveren todas sus actividades. Algo como esto:

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

El receptor (Actividad asegurada):

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 **//
}
Francesco Laurita
fuente
27
@ Christopher, cada actividad se registra para la transmisión cuando se crea. Cuando pasa al fondo (es decir, una nueva actividad llega a la parte superior de la pila), se llamará a su OnStop (), pero aún puede recibir transmisiones. Solo necesita asegurarse de llamar a unregisterReceiver () en onDestroy () en lugar de en onStop ().
Russell Davis
99
¿Funciona esto si el sistema operativo cerró una actividad en algún lugar de la pila para recuperar memoria? Es decir. ¿El sistema lo considerará realmente terminado después de que se envíe la transmisión anterior y no lo recreará al presionar el botón Atrás?
Jan Żankowski
55
Si bien esto parece una solución elegante, es importante tener en cuenta que esto no es sincrónico.
Che Jami
34
Una buena solución, pero en lugar de utilizar un receptor Broadcast registrado como se describe en el código anterior, debe usar un LocalBroadcastManager.getInstance (this) .registerReceiver (...) y LocalBroadcastManager.getInstance (this) .unregisterReceiver (..) . De lo contrario, su aplicación puede recibir intentos de cualquier otra aplicación (preocupación de seguridad)
Uku Loskit
55
@Warlock Estás en lo correcto. El escollo de este enfoque es cuando el sistema destruye una actividad en la pila posterior (puede suceder por varias razones, como el escenario de poca memoria señalado). En ese caso, la instancia de Actividad no estará disponible para recibir la transmisión. Pero el sistema seguirá navegando hacia atrás a través de esa Actividad recreándola. Esto se puede probar / reproducir activando la configuración de desarrollador "No mantener actividades"
Eric Schlenz
151

Parece un rito de pasaje que un nuevo programador de Android pasa un día investigando este problema y leyendo todos estos hilos de StackOverflow. Ahora estoy recién iniciado y dejo aquí un rastro de mi humilde experiencia para ayudar a un futuro peregrino.

Primero, no hay una manera obvia o inmediata de hacer esto según mi investigación (as of September 2012).. Pensarías que podrías ser simple startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)pero no .

PUEDE hacerlo startActivity(new Intent(this, LoginActivity.class))con FLAG_ACTIVITY_CLEAR_TOP- y esto hará que el marco busque en la pila, encuentre su instancia original anterior de LoginActivity, vuelva a crearla y elimine el resto de la pila (hacia arriba). Y dado que el inicio de sesión está presumiblemente en la parte inferior de la pila, ahora tiene una pila vacía y el botón Atrás acaba de salir de la aplicación.

PERO: esto solo funciona si previamente dejó viva esa instancia original de LoginActivity en la base de su pila. Si, como muchos programadores, eliges finish()eso LoginActivityuna vez que el usuario ha iniciado sesión con éxito, entonces ya no está en la base de la pila y la FLAG_ACTIVITY_CLEAR_TOPsemántica no se aplica ... terminas creando una nueva LoginActivityencima de la pila existente. Lo cual es casi seguro que NO es lo que desea (comportamiento extraño en el que el usuario puede 'retroceder' para iniciar sesión en una pantalla anterior).

Así que si usted previamente finish()'d la LoginActivity, que necesita para continuar algún mecanismo para la limpieza de su pila y luego iniciar una nueva LoginActivity. Parece que la respuesta @doreamonen este hilo es la mejor solución (al menos para mi humilde ojo):

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

Sospecho firmemente que las complicadas implicaciones de si dejas LoginActivity vivo están causando mucha confusión.

Buena suerte.

Mike Repass
fuente
55
Buena respuesta. El truco FLAG_ACTIVITY_CLEAR_TOP, que la mayoría de las personas recomiendan usar, simplemente no funciona si ha finalizado la Actividad de inicio de sesión.
Konsumierer
Gracias por la respuesta. Otro escenario es como cuando hay una sesión (por ejemplo, como fb), incluso si no llamamos actividad de inicio de sesión, por lo que no hay ningún punto de actividad de inicio de sesión en la pila. Entonces, el enfoque mencionado anteriormente es inútil.
Prashanth Debbadwar
118

ACTUALIZAR

El súper finishAffinity()método ayudará a reducir el código pero logrará lo mismo. Terminará la actividad actual, así como todas las actividades en la pila, úsela getActivity().finishAffinity()si está en un fragmento.

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

RESPUESTA ORIGINAL

Suponga que LoginActivity -> HomeActivity -> ... -> SettingsActivity call 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();
}

InicioActividad:

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

Esto funciona para mí, espero que también sea útil para usted. :)

thanhbinh84
fuente
1
Creo que su solución supone que el usuario hará clic en cerrar sesión y volverá a una sola actividad (HomeActivity). ¿Qué pasa si tienes 10 actividades en la pila?
IgorGanapolsky
11
Si tiene 10 actividades en la parte superior de HomeActivity, el indicador FLAG_ACTIVITY_CLEAR_TOP ayudará a limpiarlas todas.
thanhbinh84
1
Esto solo puede funcionar si al iniciar HomeActivity obtiene su OnCreate of HomeActivity. Simplemente comenzar la actividad en el hogar no necesariamente lo recreará a menos que ya esté terminado o destruido. Si no es necesario recrear HomeActivity, no se llamará a OnCreate y, después de cerrar sesión, se concentrará en la actividad de su hogar.
topwik
1
Esta es una posible solución y que implica mucho menos codificar una función simple (para el usuario) como cerrar sesión.
Subin Sebastian
2
El indicador Intent.FLAG_ACTIVITY_CLEAR_TOP ayuda a limpiar todas las actividades, incluida HomeActivity, por lo que se llamará al método onCreate () cuando vuelva a iniciar esta actividad.
thanhbinh84
73

Si está utilizando API 11 o superior, puede intentar esto: FLAG_ACTIVITY_CLEAR_TASKparece estar abordando exactamente el problema que está teniendo. Obviamente, la multitud anterior a API 11 tendría que usar alguna combinación de hacer que todas las actividades verifiquen un extra, como sugiere @doreamon, o algún otro truco.

(También tenga en cuenta: para usar esto debe pasar 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();
xbakesx
fuente
1
Trabajando como un encanto. ¡Muchas gracias! A medida que desarrollo en min API 14, eso es lo único que implementar.
AndyB
1
Creo que no necesitamos FLAG_ACTIVITY_CLEAR_TOP cuando usamos esta solución para LoginActivity.
Bharat Dodeja
Creo que simplemente finish();haré el trabajo para evitar volver después de cerrar sesión. ¿Cuál es la necesidad de configurar banderas?
Pankaj
Esto crea una excepción Llamar a startActivity () desde fuera de un contexto de Actividad requiere el indicador FLAG_ACTIVITY_NEW_TASK
Aman
31

También pasé unas horas en esto ... y estoy de acuerdo en que FLAG_ACTIVITY_CLEAR_TOP suena como lo que quieres: borra toda la pila, excepto la actividad que se inicia, por lo que el botón Atrás sale de la aplicación. Sin embargo, como mencionó Mike Repass , FLAG_ACTIVITY_CLEAR_TOP solo funciona cuando la actividad que está iniciando ya está en la pila; cuando la actividad no está allí, la bandera no hace nada.

¿Qué hacer? Coloque la actividad que se inicia en la pila con FLAG_ACTIVITY_NEW_TASK , lo que hace que esa actividad sea el inicio de una nueva tarea en la pila del historial. Luego agregue el indicador FLAG_ACTIVITY_CLEAR_TOP.

Ahora, cuando FLAG_ACTIVITY_CLEAR_TOP vaya a buscar la nueva actividad en la pila, estará allí y se levantará antes de que se borre todo lo demás.

Aquí está mi función de cierre de sesión; El parámetro Ver es el botón al que se adjunta la función.

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();
}
christinac
fuente
1
No trabajo sobre API <= 10 como FLAG_ACTIVITY_CLEAR_TASKse ha añadido todavía no
Youans
1
@christinac Estás hablando de FLAG_ACTIVITY_CLEAR_TOP y tu fragmento de código tiene FLAG_ACTIVITY_CLEAR_TASK; ¿Cuál es válido entonces?
Marian Paździoch
10

Muchas respuestas Puede ser este también ayudará

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

Versión Kotlin

Intent(this, SignInActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also { startActivity(it) }
finish()
Gulshan
fuente
4

Use esto, debería ser útil para usted. Respuesta ligeramente modificada de 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);
Mohamed Ibrahim
fuente
4

La solución aceptada no es correcta, tiene problemas ya que el uso de un receptor de difusión no es una buena idea para este problema. Si su actividad ya ha llamado al método onDestroy (), no obtendrá el receptor. La mejor solución es tener un valor booleano en sus preferencias compartidas y verificarlo en el método onCreate () de su actividad. Si no se debe llamar cuando el usuario no ha iniciado sesión, finalice la actividad. Aquí hay un código de muestra para eso. Tan simple y funciona para todas las condiciones.

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;
}
Yekmer Simsek
fuente
1
Han pasado años, pero creo que hice las dos cosas. Cada actividad extendió LoggedInActivity, que verificó el estado de inicio de sesión del usuario en onCreate (). LoggedInActivity también escuchó la transmisión de "usuario desconectado" e hizo todo lo necesario para responder a esto.
skyler
2
más probablemente debería poner el checkAuthStatus en el onResume()método. Porque cuando presiona el botón Atrás, es más probable que la actividad se reanude en lugar de crearse.
maysi
1
@maysi Gracias por su sugerencia, sí, este botón de retroceso también funcionará correctamente, actualicé la entrada
Yekmer Simsek
4

Algún tiempo finish() no funciona

He resuelto ese problema con

finishAffinity()

AJay
fuente
3

Sugeriría un enfoque diferente a esta pregunta. Quizás no sea el más eficiente, pero creo que es el más fácil de aplicar y requiere muy poco código. Escribir el siguiente código en su primera actividad (actividad de inicio de sesión, en mi caso) no permitirá que el usuario regrese a las actividades iniciadas anteriormente después de cerrar sesión.

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

Supongo que LoginActivity finaliza justo después de que el usuario inicie sesión, para que no pueda volver más tarde presionando el botón Atrás. En cambio, el usuario debe presionar un botón de cerrar sesión dentro de la aplicación para cerrar sesión correctamente. Lo que implementaría este botón de cerrar sesión es una simple intención de la siguiente manera:

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

Todas las sugerencias son bienvenidas.

Nulo
fuente
2

La respuesta seleccionada es inteligente y engañosa. Así es como lo hice:

LoginActivity es la actividad raíz de la tarea, configure android: noHistory = "true" en Manifest.xml; Supongamos que desea cerrar sesión desde SettingsActivity, puede hacerlo de la siguiente manera:

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);
t-gao
fuente
2

Aquí está la solución que se me ocurrió en mi aplicación.

En mi LoginActivity, después de procesar con éxito un inicio de sesión, comienzo el siguiente de manera diferente según el nivel de 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);
}

Luego, en mi método onActivityForResult de LoginActivity:

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
        requestCode == REQUEST_LOGIN_GINGERBREAD &&
        resultCode == Activity.RESULT_CANCELED) {
    moveTaskToBack(true);
}

Finalmente, después de procesar un cierre de sesión en cualquier otra Actividad:

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

Cuando estoy en Gingerbread, lo hace así si presiono el botón Atrás de MainActivity, LoginActivity se oculta inmediatamente. En Honeycomb y versiones posteriores, acabo de finalizar LoginActivity después de procesar un inicio de sesión y se vuelve a crear correctamente después de procesar un cierre de sesión.

seastland
fuente
No está funcionando para mi. En mi caso usé fragmentactividad. No se llama al método de resultado de actividad.
Mohamed Ibrahim
1

Esto funcionó para mí:

     // 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);
Preetansh
fuente
¿Podría por favor elaborar más su respuesta agregando un poco más de descripción sobre la solución que proporciona?
abarisone
0

Comience su actividad con StartActivityForResult y mientras cierra la sesión, configure su resultado y, según su resultado, finalice su actividad

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

}
Eby
fuente
0

La solución proporcionada por @doreamon funciona bien para todos los casos, excepto uno:

Si después de iniciar sesión, el usuario de la pantalla de inicio de sesión de Killing navegó directamente a una pantalla central. Por ejemplo, en un flujo de A-> B-> C, navegue como: Iniciar sesión -> B -> C -> Presione acceso directo a inicio. El uso de FLAG_ACTIVITY_CLEAR_TOP borra solo la actividad C, ya que la página de inicio (A) no está en el historial de la pila. Presionando Atrás en la pantalla A nos llevará de regreso a B.

Para abordar este problema, podemos mantener una pila de actividades (Arraylist) y cuando se presiona el inicio, tenemos que eliminar todas las actividades en esta pila.

Surendra Kumar
fuente
0

Es posible administrando un indicador en SharedPreferences o en Application Activity.

Al iniciar la aplicación (en la pantalla de bienvenida) establezca la bandera = falso; Al cerrar sesión, haga clic en el evento solo establezca el indicador verdadero y en OnResume () de cada actividad, verifique si el indicador es verdadero y luego llame a terminar ().

Funciona a las mil maravillas :)

MrDumb
fuente
0

al hacer clic en Cerrar sesión, puede llamar a esto

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

onActivityResult () de la actividad anterior, vuelva a llamar a este código anterior hasta que finalice todas las actividades.

Mak
fuente
1
¿Eso significa que tiene que atravesar todas las actividades linealmente, en lugar de transmitir el acabado () de una vez?
IgorGanapolsky
@ Igor G. sí, es la forma alternativa de terminar secuencialmente
Mak
-1

Una opción es hacer que cada actividad onCreate verifique el estado de inicio de sesión y finalice () si no ha iniciado sesión. No me gusta esta opción, ya que el botón Atrás todavía estará disponible para su uso, navegando hacia atrás a medida que las actividades se cierran.

Lo que desea hacer es llamar a logout () y finish () en sus métodos onStop () o onPause (). Esto obligará a Android a llamar a onCreate () cuando la actividad se vuelva a activar ya que ya no la tendrá en la pila de su actividad. Luego haga lo que dice, en onCreate () verifique el estado de inicio de sesión y reenvíe a la pantalla de inicio de sesión si no está conectado.

Otra cosa que puede hacer es verificar el estado de inicio de sesión en onResume () y, si no está conectado, finalizar () e iniciar la actividad de inicio de sesión.

Ricardo Villamil
fuente
Prefiero no cerrar sesión en cada actividad pausar o detener. Además, la aplicación inicia el cierre de sesión o el inicio de sesión, por lo que no necesito verificar si realmente está conectado.
skyler
@ Ricardo: ¿su solución no requiere un BroadcastReceiver?
IgorGanapolsky