Al hacer clic dos veces en el botón Atrás para salir de una actividad

Recientemente, he notado este patrón en muchas aplicaciones y juegos de Android: al hacer clic en el botón Atrás para “salir” de la aplicación, un Toast aparece con un mensaje similar a “Haga clic en ATRÁS otra vez para salir”.

Me preguntaba, como lo veo cada vez más a menudo, ¿es una característica incorporada a la que de alguna manera se puede acceder en una actividad? Miré el código fuente de muchas clases, pero parece que no puedo encontrar nada al respecto.

Por supuesto, puedo pensar en algunas maneras de lograr la misma funcionalidad con bastante facilidad (la más fácil es probablemente mantener un booleano en la actividad que indica si el usuario ya hizo clic una vez …) pero me preguntaba si ya hay algo aquí .

EDITAR : Como se mencionó en @LAS_VEGAS, realmente no quise decir “salir” en el significado tradicional. (es decir, terminado) quise decir “volver a lo que estaba abierto antes de que se iniciara la actividad de inicio de la aplicación”, si eso tiene sentido 🙂

En la actividad de Java:

 boolean doubleBackToExitPressedOnce = false; @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce=false; } }, 2000); } 

En la actividad de Kotlin:

 private var doubleBackToExitPressedOnce = false override fun onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed() return } this.doubleBackToExitPressedOnce = true Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show() Handler().postDelayed(Runnable { doubleBackToExitPressedOnce = false }, 2000) } 

Creo que este controlador ayuda a restablecer la variable después de 2 segundos.

Sudheesh B Nair tiene una respuesta agradable (y aceptada) sobre la pregunta, que creo que debería tener una mejor alternativa como;

¿Qué pasa con la medición del tiempo transcurrido y comprobar si TIME_INTERVAL milisegundos (por ejemplo, 2000) pasó desde la última pulsación posterior? El siguiente código de ejemplo utiliza System.currentTimeMillis(); para almacenar la hora onBackPressed() se llama;

 private static final int TIME_INTERVAL = 2000; // # milliseconds, desired time passed between two back presses. private long mBackPressed; @Override public void onBackPressed() { if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) { super.onBackPressed(); return; } else { Toast.makeText(getBaseContext(), "Tap back button in order to exit", Toast.LENGTH_SHORT).show(); } mBackPressed = System.currentTimeMillis(); } 

De vuelta en la crítica de respuesta aceptada; Utilizar una flag para indicar si se presionó en la última TIME_INTERVAL (digamos 2000) milisegundos y establecer – restablecer a través del método postDelayed() Handler fue lo primero que me vino a la mente. Pero la acción postDelayed() debe cancelarse cuando se cierra la actividad, eliminando Runnable .

Para eliminar Runnable , no se debe declarar anónimo y se debe declarar como miembro junto con Handler . A removeCallbacks() método removeCallbacks() de Handler se puede llamar de forma adecuada.

La siguiente muestra es la demostración;

 private boolean doubleBackToExitPressedOnce; private Handler mHandler = new Handler(); private final Runnable mRunnable = new Runnable() { @Override public void run() { doubleBackToExitPressedOnce = false; } }; @Override protected void onDestroy() { super.onDestroy(); if (mHandler != null) { mHandler.removeCallbacks(mRunnable); } } @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show(); mHandler.postDelayed(mRunnable, 2000); } 

Gracias a @NSouth por contribuir; Para evitar que aparezca un mensaje de tostado incluso después de cerrar la aplicación, se puede declarar Toast como miembro, digamos mExitToast , y se puede cancelar a través de mExitToast.cancel(); justo antes de super.onBackPressed(); llamada.

Solo pensé en compartir cómo lo hice al final, acabo de agregar en mi actividad:

 private boolean doubleBackToExitPressedOnce = false; @Override protected void onResume() { super.onResume(); // .... other stuff in my onResume .... this.doubleBackToExitPressedOnce = false; } @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Toast.makeText(this, R.string.exit_press_back_twice_message, Toast.LENGTH_SHORT).show(); } 

Y simplemente funciona exactamente como yo quiero. Incluyendo el reinicio del estado cada vez que se reanude la actividad.

Diagtwig de flujo del proceso: Presione nuevamente para salir.

Código de Java:

 private long lastPressedTime; private static final int PERIOD = 2000; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { switch (event.getAction()) { case KeyEvent.ACTION_DOWN: if (event.getDownTime() - lastPressedTime < PERIOD) { finish(); } else { Toast.makeText(getApplicationContext(), "Press again to exit.", Toast.LENGTH_SHORT).show(); lastPressedTime = event.getEventTime(); } return true; } } return false; } 

Hay una manera muy simple entre todas estas respuestas.

Simplemente escriba el siguiente código dentro del onBackPressed() .

 long back_pressed; @Override public void onBackPressed() { if (back_pressed + 1000 > System.currentTimeMillis()){ super.onBackPressed(); } else{ Toast.makeText(getBaseContext(), "Press once again to exit!", Toast.LENGTH_SHORT) .show(); } back_pressed = System.currentTimeMillis(); } 

back_pressed definir el objeto back_pressed long back_pressed actividad.

En base a la respuesta correcta y las sugerencias en los comentarios, he creado una demostración que funciona absolutamente bien y elimina las devoluciones de llamadas del manejador después de ser utilizado.

MainActivity.java

 package com.mehuljoisar.d_pressbacktwicetoexit; import android.os.Bundle; import android.os.Handler; import android.app.Activity; import android.widget.Toast; public class MainActivity extends Activity { private static final long delay = 2000L; private boolean mRecentlyBackPressed = false; private Handler mExitHandler = new Handler(); private Runnable mExitRunnable = new Runnable() { @Override public void run() { mRecentlyBackPressed=false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onBackPressed() { //You may also add condition if (doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount() != 0) // in case of Fragment-based add if (mRecentlyBackPressed) { mExitHandler.removeCallbacks(mExitRunnable); mExitHandler = null; super.onBackPressed(); } else { mRecentlyBackPressed = true; Toast.makeText(this, "press again to exit", Toast.LENGTH_SHORT).show(); mExitHandler.postDelayed(mExitRunnable, delay); } } } 

Espero que sea util !!

  public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce=false; } }, 2000); 

Declarar variable private boolean doubleBackToExitPressedOnce = false;

Pega esto en tu actividad principal y esto resolverá tu problema

`

Mi solución usando snackbar:

 LinearLayout mLayout; Snackbar mSnackbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLayout = findViewById(R.id.layout_main); mSnackbar = Snackbar.make(mLayout, R.string.press_back_again, Snackbar.LENGTH_SHORT); } @Override public void onBackPressed() { if (mSnackbar.isShown()) { super.onBackPressed(); } else { mSnackbar.show(); } } 

Simple y con estilo.

No es una buena idea usar un Runnable cuando salgo de la aplicación. Recientemente descubrí una manera mucho más simple de grabar y comparar el período entre dos clics del botón BACK. Código de muestra de la siguiente manera:

 private static long back_pressed_time; private static long PERIOD = 2000; @Override public void onBackPressed() { if (back_pressed_time + PERIOD > System.currentTimeMillis()) super.onBackPressed(); else Toast.makeText(getBaseContext(), "Press once again to exit!", Toast.LENGTH_SHORT).show(); back_pressed_time = System.currentTimeMillis(); } 

Esto hará el truco para salir de la aplicación con un doble clic en el botón ATRÁS dentro de un cierto período de retraso, que es de 2000 milisegundos en la muestra.

No es una funcionalidad integrada. Creo que ni siquiera es el comportamiento recomendado. Las aplicaciones de Android no están destinadas a salir:

¿Por qué las aplicaciones de Android no ofrecen una opción de “Salir”?

La respuesta aceptada es La mejor, pero si usa la Android Design Support Library , puede usar SnackBar para obtener mejores vistas.

  boolean doubleBackToExitPressedOnce = false; @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Snackbar.make(findViewById(R.id.photo_album_parent_view), "Please click BACK again to exit", Snackbar.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce=false; } }, 2000); } 
  1. Declare una variable global de Toast para MainActivity Class. ejemplo: Toast exitToast;
  2. Inicialízalo en el método de vista Crear. ejemplo: exitToast = Toast.makeText (getApplicationContext (), “Presiona nuevamente para salir”, Toast.LENGTH_SHORT);
  3. Finalmente crea un método onBackPressed como sigue:

     @Override public void onBackPressed() { if (exitToast.getView().isShown()) { exitToast.cancel(); finish(); } else { exitToast.show(); } } 

Esto funciona correctamente, lo he probado. y creo que esto es mucho más simple.

La respuesta de Zefnus usando System.currentTimeMillis () es la mejor (+1). La forma en que lo hice no es mejor que eso, pero todavía lo publico para agregar a las ideas anteriores.

Si la tostada no es visible cuando se presiona el botón Atrás, se muestra la tostada, mientras que, si está visible (la parte posterior ya se ha pulsado una vez dentro del último tiempo Toast.LENGTH_SHORT ), se cierra.

 exitToast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT); . . @Override public void onBackPressed() { if (exitToast.getView().getWindowToken() == null) //if toast is currently not visible exitToast.show(); //then show toast saying 'press againt to exit' else { //if toast is visible then finish(); //or super.onBackPressed(); exitToast.cancel(); } } 

Recientemente, necesité implementar esta función de botón de retroceso en una aplicación mía. Las respuestas sobre la pregunta original fueron útiles, pero tuve que tomar dos puntos más en consideración:

  1. En algunos momentos, el botón Atrás está desactivado
  2. La actividad principal es usar fragmentos en combinación con una stack de respaldo

En base a las respuestas y comentarios, creé el siguiente código:

 private static final long BACK_PRESS_DELAY = 1000; private boolean mBackPressCancelled = false; private long mBackPressTimestamp; private Toast mBackPressToast; @Override public void onBackPressed() { // Do nothing if the back button is disabled. if (!mBackPressCancelled) { // Pop fragment if the back stack is not empty. if (getSupportFragmentManager().getBackStackEntryCount() > 0) { super.onBackPressed(); } else { if (mBackPressToast != null) { mBackPressToast.cancel(); } long currentTimestamp = System.currentTimeMillis(); if (currentTimestamp < mBackPressTimestamp + BACK_PRESS_DELAY) { super.onBackPressed(); } else { mBackPressTimestamp = currentTimestamp; mBackPressToast = Toast.makeText(this, getString(R.string.warning_exit), Toast.LENGTH_SHORT); mBackPressToast.show(); } } } } 

El código anterior supone que se usa la biblioteca de soporte. Si usa fragmentos pero no la biblioteca de soporte, desea reemplazar getSupportFragmentManager() por getFragmentManager() .

Quite el primero if , si el botón Atrás nunca se cancela. Elimina el segundo if , si no usas fragmentos o un fragmento de la stack de respaldo

Además, es importante tener en cuenta que el método onBackPressed es compatible desde Android 2.0. Consulte esta página para obtener una descripción elaborada. Para que la función de retroceso funcione también en las versiones anteriores, agregue el siguiente método a su actividad:

 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR && keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { // Take care of calling this method on earlier versions of // the platform where it doesn't exist. onBackPressed(); } return super.onKeyDown(keyCode, event); } 

Sé que esta es una pregunta muy antigua, pero esta es la forma más fácil de hacer lo que quieres.

 @Override public void onBackPressed() { ++k; //initialise k when you first start your activity. if(k==1){ //do whatever you want to do on first click for example: Toast.makeText(this, "Press back one more time to exit", Toast.LENGTH_LONG).show(); }else{ //do whatever you want to do on the click after the first for example: finish(); } } 

Sé que este no es el mejor método, ¡pero funciona bien!

Esto también ayuda cuando tienes actividad de stack previa almacenada en la stack.

He modificado la respuesta de Sudheesh

 boolean doubleBackToExitPressedOnce = false; @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { //super.onBackPressed(); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//***Change Here*** startActivity(intent); finish(); System.exit(0); return; } this.doubleBackToExitPressedOnce = true; Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce=false; } }, 2000); } 
 @Override public void onBackPressed() { Log.d("CDA", "onBackPressed Called"); Intent intent = new Intent(); intent.setAction(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); startActivity(intent); } 

Para este propósito he implementado la siguiente función:

 private long onRecentBackPressedTime; @Override public void onBackPressed() { if (System.currentTimeMillis() - onRecentBackPressedTime > 2000) { onRecentBackPressedTime = System.currentTimeMillis(); Toast.makeText(this, "Please press BACK again to exit", Toast.LENGTH_SHORT).show(); return; } super.onBackPressed(); } 

Aquí está el código de trabajo completo. Y también no olvide eliminar las devoluciones de llamada para que no provoque una pérdida de memoria en la aplicación. 🙂

 private boolean backPressedOnce = false; private Handler statusUpdateHandler; private Runnable statusUpdateRunnable; public void onBackPressed() { if (backPressedOnce) { finish(); } backPressedOnce = true; final Toast toast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT); toast.show(); statusUpdateRunnable = new Runnable() { @Override public void run() { backPressedOnce = false; toast.cancel(); //Removes the toast after the exit. } }; statusUpdateHandler.postDelayed(statusUpdateRunnable, 2000); } @Override protected void onDestroy() { super.onDestroy(); if (statusUpdateHandler != null) { statusUpdateHandler.removeCallbacks(statusUpdateRunnable); } } 

Aquí, he generalizado escribir el código para N conteos de tap. El código está escrito de manera similar para la opción Habilitar desarrollador en el teléfono con dispositivo Android. Incluso puedes usar esto para habilitar las funciones mientras el desarrollador prueba la aplicación.

  private Handler tapHandler; private Runnable tapRunnable; private int mTapCount = 0; private int milSecDealy = 2000; onCreate(){ ... tapHandler = new Handler(Looper.getMainLooper()); } 

Llame a askToExit () en la opción de retroceder o cerrar la sesión.

 private void askToExit() { if (mTapCount >= 2) { releaseTapValues(); /* ========= Exit = TRUE ========= */ } mTapCount++; validateTapCount(); } /* Check with null to avoid create multiple instances of the runnable */ private void validateTapCount() { if (tapRunnable == null) { tapRunnable = new Runnable() { @Override public void run() { releaseTapValues(); /* ========= Exit = FALSE ========= */ } }; tapHandler.postDelayed(tapRunnable, milSecDealy); } } private void releaseTapValues() { /* Relase the value */ if (tapHandler != null) { tapHandler.removeCallbacks(tapRunnable); tapRunnable = null; /* release the object */ mTapCount = 0; /* release the value */ } } @Override protected void onDestroy() { super.onDestroy(); releaseTapValues(); } 

Cuando HomeActivity contiene el cajón de navegación y la función double backPressed () para salir de la aplicación. (No te olvides de iniciar la variable global boolean doubleBackToExitPressedOnce = false;) new handler después de 2 segundos establece la variable doubleBackPressedOnce en false

 @Override public void onBackPressed() { DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.END)) { drawer.closeDrawer(GravityCompat.END); } else { if (doubleBackToExitPressedOnce) { super.onBackPressed(); moveTaskToBack(true); return; } else { this.doubleBackToExitPressedOnce = true; Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce = false; } }, 2000); } } } 

En esta situación, Snackbar es la mejor opción que Toast para mostrar la acción de dejar de fumar. Aquí está el método con snackbar que funciona.

 @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Snackbar.make(this.getWindow().getDecorView().findViewById(android.R.id.content), "Please click BACK again to exit", Snackbar.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce=false; } }, 2000); } 

En java

 private Boolean exit = false; if (exit) { onBackPressed(); } 

  @Override public void onBackPressed() { if (exit) { finish(); // finish activity } else { Toast.makeText(this, "Press Back again to Exit.", Toast.LENGTH_SHORT).show(); exit = true; new Handler().postDelayed(new Runnable() { @Override public void run() { exit = false; } }, 3 * 1000); } } 

en kotlin

  private var exit = false if (exit) { onBackPressed() } 

  override fun onBackPressed(){ if (exit){ finish() // finish activity }else{ Toast.makeText(this, "Press Back again to Exit.", Toast.LENGTH_SHORT).show() exit = true Handler().postDelayed({ exit = false }, 3 * 1000) } } 
 boolean doubleBackToExitPressedOnce = false; @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Snackbar.make(findViewById(R.id.photo_album_parent_view), "Please click BACK again to exit", Snackbar.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce=false; } }, 2000); } 

Algunas mejoras en la respuesta de Sudheesh B Nair, me he dado cuenta de que esperará al controlador incluso mientras presiona dos veces inmediatamente, así que cancele el controlador como se muestra a continuación. También hice tostadas para evitar que se muestre después de la salida de la aplicación.

  boolean doubleBackToExitPressedOnce = false; Handler myHandler; Runnable myRunnable; Toast myToast; @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { myHandler.removeCallbacks(myRunnable); myToast.cancel(); super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; myToast = Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT); myToast.show(); myHandler = new Handler(); myRunnable = new Runnable() { @Override public void run() { doubleBackToExitPressedOnce = false; } }; myHandler.postDelayed(myRunnable, 2000); } 

Un método un poco mejor que Zefnus , creo. Llame a System.currentTimeMillis () solo una vez y omita return; :

 long previousTime; @Override public void onBackPressed() { if (2000 + previousTime > (previousTime = System.currentTimeMillis())) { super.onBackPressed(); } else { Toast.makeText(getBaseContext(), "Tap back button in order to exit", Toast.LENGTH_SHORT).show(); } } 

Esta es la misma respuesta aceptada y más votada, pero esta opción cortó Snackbar utilizado en lugar de Toast.

 boolean doubleBackToExitPressedOnce = false; @Override public void onBackPressed() { if (doubleBackToExitPressedOnce) { super.onBackPressed(); return; } this.doubleBackToExitPressedOnce = true; Snackbar.make(content, "Please click BACK again to exit", Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce=false; } }, 2000); } 

En mi caso, he Snackbar#isShown() para una mejor UX .

 private Snackbar exitSnackBar; @Override public void onBackPressed() { if (isNavDrawerOpen()) { closeNavDrawer(); } else if (getSupportFragmentManager().getBackStackEntryCount() == 0) { if (exitSnackBar != null && exitSnackBar.isShown()) { super.onBackPressed(); } else { exitSnackBar = Snackbar.make( binding.getRoot(), R.string.navigation_exit, 2000 ); exitSnackBar.show(); } } else { super.onBackPressed(); } } 

Para la actividad que tiene Cajón de navegación , use el siguiente código para OnBackPressed ()

 boolean doubleBackToExitPressedOnce = false; @Override public void onBackPressed() { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { if (doubleBackToExitPressedOnce) { if (getFragmentManager().getBackStackEntryCount() ==0) { finishAffinity(); System.exit(0); } else { getFragmentManager().popBackStackImmediate(); } return; } if (getFragmentManager().getBackStackEntryCount() ==0) { this.doubleBackToExitPressedOnce = true; Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { doubleBackToExitPressedOnce = false; } }, 2000); } else { getFragmentManager().popBackStackImmediate(); } } } 

yo uso esto

 import android.app.Activity; import android.support.annotation.StringRes; import android.widget.Toast; public class ExitApp { private static long lastClickTime; public static void now(Activity ctx, @StringRes int message) { now(ctx, ctx.getString(message), 2500); } public static void now(Activity ctx, @StringRes int message, long time) { now(ctx, ctx.getString(message), time); } public static void now(Activity ctx, String message, long time) { if (ctx != null && !message.isEmpty() && time != 0) { if (lastClickTime + time > System.currentTimeMillis()) { ctx.finish(); } else { Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show(); lastClickTime = System.currentTimeMillis(); } } } } 

usar para evento onBackPressed

 @Override public void onBackPressed() { ExitApp.now(this,"Press again for close"); } 

o ExitApp.now(this,R.string.double_back_pressed)

para los segundos de cambio necesitan milisegundos cercanos y específicos

ExitApp.now(this,R.string.double_back_pressed,5000)