Clicando no botão voltar duas vezes para sair de uma atividade

331

Eu notei esse padrão em muitos aplicativos e jogos Android recentemente: ao clicar no botão voltar para "sair" do aplicativo, Toastsurge uma mensagem semelhante a "Por favor, clique em VOLTAR novamente para sair".

Fiquei me perguntando, como eu o vejo cada vez mais frequentemente, esse é um recurso interno que você pode acessar de alguma forma em uma atividade? Eu olhei o código fonte de muitas classes, mas não consigo encontrar nada sobre isso.

Obviamente, posso pensar em algumas maneiras de obter a mesma funcionalidade com bastante facilidade (o mais fácil é provavelmente manter um booleano na atividade que indica se o usuário já clicou uma vez ...), mas eu queria saber se já existe algo aqui. .

EDIT : Como o @LAS_VEGAS mencionou, eu realmente não quis dizer "saída" no significado tradicional. (ou seja, encerrado), eu quis dizer "voltando ao que estava aberto antes do lançamento da atividade de início do aplicativo", se isso faz sentido :)

Guillaume
fonte
[Android - Confirme a saída do aplicativo com um brinde] [1]: stackoverflow.com/questions/14006461/…
resource8218
1
Eu tive o mesmo problema ao usar a Biblioteca HoloEverywhere, simplesmente você pode adicionar android: launchMode = "singleTask" à sua definição de atividade no arquivo de manifesto.
Sohayb Hassoun
Outra solução stackoverflow.com/questions/8430805/…
Webserveis

Respostas:

949

Na atividade 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);
} 

Na atividade 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)
    }

Acho que esse manipulador ajuda a redefinir a variável após 2 segundos.

Sudheesh B Nair
fonte
43
Melhor resposta! Você também pode adicionar condição if (doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount ()! = 0) {no caso de adição baseada em fragmento
Anton Derevyanko
2
Concordo, esta é definitivamente a melhor resposta e deve ser a resposta aceita.
22413 Bruce Bruce Local:
3
Você deve remover o Runnable ao sair do aplicativo.
22413 Wayne
Verifique minha resposta. Modifiquei a resposta dada por Sudheesh B Nairpara cobrir os comentários sugeridos acima.
Mehul Joisar
18
É uma solução / resposta rápida e agradável, mas não concordo que seja a melhor solução . E para aqueles que pensam que essa será a melhor resposta novamente, não posso concordar. Essa solução causa vazamentos e requer um esforço extra para o manuseio. Verifique as perguntas abaixo para obter mais detalhes.
Saro Taşciyan
226

Sudheesh B Nair tem uma resposta legal (e aceita) sobre a pergunta, que eu acho que deveria ter uma alternativa melhor como;

O que há de errado em medir o tempo passado e verificar se os TIME_INTERVALmilissegundos (por exemplo, 2000) passaram desde a última pressão de retorno. O seguinte código de exemplo usa System.currentTimeMillis();para armazenar a hora em que onBackPressed()é chamada;

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 volta à crítica de resposta aceita ; Usar a flagpara indicar se ele foi pressionado nos últimos TIME_INTERVALmilisegundos (por exemplo, 2000) e o método set-reset is via foi a primeira coisa que me veio à mente. Mas a ação deve ser cancelada quando a atividade estiver sendo fechada, removendo o .HandlerpostDelayed()postDelayed()Runnable

Para remover o Runnable, ele não deve ser declarado anônimo e ser declarado como membro junto com o Handlertambém. Então o removeCallbacks()método de Handlerpode ser chamado apropriadamente.

A amostra a seguir é a demonstração;

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

Agradecemos a @NSouth por contribuir; Para impedir que a mensagem do brinde apareça mesmo após o fechamento do aplicativo, Toastpode ser declarada como membro - por exemplo mExitToast- e pode ser cancelada via mExitToast.cancel();pouco antes da super.onBackPressed();chamada.

Saro Taşciyan
fonte
9
Para aqueles que pensam que é o mesmo que Sudheesh B Nair disse: mesma funcionalidade, melhor desempenho. então +1.
BedirYilmaz
4
Gosto desta resposta e acho que é a melhor. Quero dizer, não acho que é, é a melhor resposta, pelas razões expostas acima. Espero que você receba mais votos por este. Porém, um comentário: ninguém acha estranho que a torrada persista alguns segundos após o fechamento do aplicativo? Ninguém se importa em cancelar a torrada? Eu sei que pode ser um pequeno detalhe, mas acho que isso deve acontecer. O que é que vocês acham?
acrespo
1
@joonty obrigado pela edição, essa palavra-chave int está ausente há algum tempo. Compilará agora (:
Saro Taşciyan
2
O bloco de código @NSouth Second foi uma amostra usando mHandlers para mostrar que era necessário mais esforço. Eu sugiro que você considere usar o primeiro bloco de código, que não usa um manipulador.
Saro Taşciyan
1
@acrespo Suponho que tenhamos uma solução sólida para a mensagem do brinde persistindo após o fechamento do aplicativo agora.
Saro Taşciyan
30

Apenas pensei em compartilhar como fiz no final, acabei de adicionar minha atividade:

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

E funciona exatamente como eu quero. Incluindo a redefinição do estado sempre que a atividade é retomada.

Guillaume
fonte
15
Com esta solução, as duas contrapressão podem ter um período arbitrário de tempo entre elas. Assim, você pode pressionar Backuma vez e pressionar Backnovamente um minuto depois, e o aplicativo será encerrado. Este não é o comportamento que o usuário esperará.
22713 BruceHill
1
Eu acho que é uma questão de gosto. Nesse caso, você notifica o usuário apenas uma vez, para que o usuário saiba que ele está na atividade principal e outra contrapressão sairá do aplicativo (provavelmente após o usuário ter pressionado algumas vezes para voltar à tela principal). Se, mais tarde, o usuário pressionar novamente, podemos supor que ele queira sair do aplicativo (a menos que ele tenha navegado para outras atividades e provavelmente tenha perdido a noção de quão profundo ele chegou). Na resposta aceita acima, você considera que o usuário pode esquecer que ele já está na tela principal. Ambos estão bem, dependendo do que você deseja ou considera.
Ferran Maylinch
4
@FerranMaylinch - eu discordo. Isso não é apenas uma questão de gosto. Se já passou um tempo significativo, devemos assumir que o usuário executou outras ações nesse meio tempo e não considera mais o que fez anteriormente e optou por não continuar com a inscrição. De fato, todos, exceto o usuário mais excepcional, nem mesmo terão em mente que ele já fez isso anteriormente. Sem limite de tempo, você deixou o aplicativo em um modo invisível que o usuário não tem como saber. Considero absolutamente que o design da interface do usuário é ruim. Os usuários ficarão surpresos.
precisa
IMHO, melhores variantes sobre isso aqui e aqui .
precisa saber é o seguinte
26

Diagrama de fluxo de processo: Pressione novamente para sair.

Código 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;
}
vComo
fonte
1
Votei inicialmente como útil. O problema é que, por algum motivo, isso não funciona em alguns aparelhos. O método onBackPressed funciona melhor, mas você não possui os carimbos de data e hora, portanto, você precisa dos manipuladores conforme a resposta aceita.
Ravemir
23

Existe uma maneira muito mais simples entre todas essas respostas.

Basta escrever o seguinte código dentro do onBackPressed()método.

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

Você precisa definir um back_pressedobjeto como longem atividade.

Chintan Rathod
fonte
16

Minha solução usando snackbar:

Snackbar mSnackbar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final LinearLayout layout = findViewById(R.id.layout_main);
    mSnackbar = Snackbar.make(layout, R.string.press_back_again, Snackbar.LENGTH_SHORT);
}

@Override
public void onBackPressed() {
    if (mSnackbar.isShown()) {
        super.onBackPressed();
    } else {
        mSnackbar.show();
    }
}

Simples e elegante.

Hugo Passos
fonte
2
Obrigado por esta solução. Simples, eficaz, sem riscos.
ping li
2
Solução pronta para uso. (clap)
Hitesh Dhamshaniya
13

Com base nas respostas e sugestões corretas nos comentários, criei uma demonstração que funciona absolutamente bem e remove os retornos de chamada do manipulador após serem utilizados.

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 seja útil !!

Mehul Joisar
fonte
Você está certo de remover o manipulador na onBackPressed()correção do problema de vazamento de memória ?
Saro Taşciyan
@ Zefnus: Vai consertar até onde eu sei. Corrija-me se eu estiver errada. Como você rastreou um problema de memória com o código acima?
Mehul Joisar
11
 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 variávelprivate boolean doubleBackToExitPressedOnce = false;

Cole isso na sua Atividade principal e isso resolverá o seu problema

Rakshith
fonte
10

Não é uma boa idéia usar um Runnable ao sair do aplicativo. Recentemente, descobri uma maneira muito mais simples de registrar e comparar o período entre dois cliques no botão VOLTAR. Código de exemplo da seguinte maneira:

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

Isso fará o truque para sair do aplicativo clicando duas vezes no botão VOLTAR dentro de um certo período de atraso que é de 2000 milissegundos na amostra.

Max Lee
fonte
8

A resposta aceita é a melhor, mas se você estiver usando Android Design Support Library, poderá usar SnackBarpara melhores visualizações.

   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);
    }
Pratik Butani
fonte
7

Não é uma funcionalidade incorporada. Eu acho que nem é o comportamento recomendado. Os aplicativos Android não devem sair:

Por que os aplicativos Android não oferecem a opção "Sair"?

Caner
fonte
Ponto tomado. Ao sair, eu quis dizer "de volta à tela inicial vai"
Guillaume
3
Ainda não é uma funcionalidade incorporada. No entanto, não conheço nenhuma orientação contra isso. Como usuário do Android, eu gosto dessa funcionalidade.
Caner
6
  1. Declare uma variável global Toast para a classe MainActivity. exemplo: Toast exitToast;
  2. Inicialize-o no método de exibição onCreate. exemplo: exitToast = Toast.makeText (getApplicationContext (), "Pressione novamente para sair", Toast.LENGTH_SHORT);
  3. Por fim, crie um onBackPressedMethod da seguinte maneira:

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

Isso funciona corretamente, eu testei. e acho que isso é muito mais simples.

Supto
fonte
5

A resposta de Zefnus usando System.currentTimeMillis () é a melhor (+1). O jeito que eu fiz isso não é melhor do que isso, mas ainda estou postando para adicionar às idéias acima.

Se a torrada não estiver visível quando o botão Voltar for pressionado, a torrada será exibida, enquanto, se estiver visível (a parte traseira já foi pressionada uma vez na última Toast.LENGTH_SHORTvez), ela será encerrada.

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();
   }
}
Kartik
fonte
1
É uma boa maneira de fazer isso, mas se você tiver mais mensagens de brinde, não é.
Boldijar Paul
@BoldijarPaul Não, esse código verifica apenas o status dessa torrada específica. Portanto, qualquer outro brinde não afetará o comportamento.
precisa saber é
5

Recentemente, eu precisava implementar esse recurso de botão voltar em um aplicativo meu. As respostas da pergunta original foram úteis, mas eu tive que levar mais dois pontos em consideração:

  1. Em alguns momentos, o botão Voltar está desativado
  2. A atividade principal é usar fragmentos em combinação com uma pilha traseira

Com base nas respostas e comentários, criei o seguinte 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();
            }
        }
    }
}

O código acima supõe que a biblioteca de suporte seja usada. Se você usa fragmentos, mas não a biblioteca de suporte, deseja substituir getSupportFragmentManager()por getFragmentManager().

Remova a primeira if, se o botão de volta nunca é cancelada. Remova o segundo if, se você não usar fragmentos ou uma pilha traseira de fragmentos

Além disso, é importante estar ciente de que o método onBackPressedé suportado desde o Android 2.0. Verifique esta página para uma descrição elaborada. Para que o recurso de contrapressão também funcione em versões mais antigas, adicione o seguinte método à sua atividade:

@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);
}
Tomasz Nguyen
fonte
5

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

    }
}

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

        }
    }
Praveen
fonte
4

Sei que essa é uma pergunta muito antiga, mas é a maneira mais fácil de fazer o que você deseja.

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

Sei que esse não é o melhor método, mas funciona bem!

Chris Rohit Brendan
fonte
3
Este não é o comportamento geral de "clicar no botão voltar duas vezes para sair". Como o comentário de BruceHill em resposta aceita aponta, sua resposta não lidar com questão do tempo também
inankupeli
mas com isso você pode clicar em voltar, ele mostrará a mensagem e, em seguida, você poderá esperar um pouco mais, voltar e fechar o aplicativo, além de não lidar com o comportamento do tempo de retrocesso duplo
Gastón Saillén
3

Para esse fim, implementei a seguinte função:

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();
}
Mas eu
fonte
3

Isso também ajuda quando você tem uma atividade anterior da pilha armazenada na pilha.

Modifiquei a resposta 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);
} 
Arpit Patel
fonte
2
@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);
}
Sai Aditya
fonte
1
Como isso lida com o cenário de dupla impressão? Assim que eu revido, isso inicia uma atividade.
precisa saber é
2

Um método um pouco melhor que o Zefnus, eu acho. Chame System.currentTimeMillis () apenas uma vez e 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();
    }
}
k8C
fonte
2

Aqui está o código completo de trabalho. E também não se esqueça de remover os retornos de chamada para que não causem vazamento de memória no aplicativo. :)

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);
    }
}
Saket Kumar
fonte
2

Aqui, generalizei o código para N contagens de toques. O código é escrito da mesma forma para a opção Ativar desenvolvedor no telefone do dispositivo Android. Até você pode usar isso para ativar os recursos enquanto o desenvolvedor testa o aplicativo.

 private Handler tapHandler;
 private Runnable tapRunnable;
 private int mTapCount = 0;
 private int milSecDealy = 2000;

onCreate(){
 ...
tapHandler = new Handler(Looper.getMainLooper());

 }

Chame askToExit () na opção backpress ou logout.

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();
  }
Chintan Khetiya
fonte
2

Eu uso isso

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

}

use para no eventoonBackPressed

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

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

para que os segundos de mudança precisem de milissegundos próximos e especificados

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

Webserveis
fonte
2

Quando o HomeActivity contém gaveta de navegação e função backPressed () dupla para sair do aplicativo. (Não esqueça de iniciar a variável global booleana doubleBackToExitPressedOnce = false;) novo manipulador após 2 s defina a variável doubleBackPressedOnce como 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);
        }
    }
}
Abhishek S
fonte
1
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);
}
maniix
fonte
1

Algumas melhorias na resposta de Sudheesh B Nair, notei que ele aguardará o manipulador, mesmo pressionando duas vezes imediatamente imediatamente, então cancele o manipulador conforme mostrado abaixo. Cancled torradas também para impedir que seja exibido após a saída do aplicativo.

 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);
        }
Divyang Panchal
fonte
1

É o mesmo da resposta aceita e mais votada, mas isso recortou o Snackbar usado em vez do 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);
    }
MobileOS
fonte
1

No meu caso, eu dependo Snackbar#isShown()para melhor 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();
    }
}
Kasim Rangwala
fonte
1

Para a atividade que possui a Gaveta de Navegação , use o código a seguir 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();
            }
        }
    }
Priya Rajan
fonte
1

Aqui está outra maneira ... usando o método CountDownTimer

private boolean exit = false;
@Override
public void onBackPressed() {
        if (exit) {
            finish();
        } else {
            Toast.makeText(this, "Press back again to exit",
                    Toast.LENGTH_SHORT).show();
            exit = true;
            new CountDownTimer(3000,1000) {

                @Override
                public void onTick(long l) {

                }

                @Override
                public void onFinish() {
                    exit = false;
                }
            }.start();
        }

    }
Anuj Sain
fonte