可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I\'ve noticed this pattern in a lot of Android apps and games recently: when clicking the back button to \"exit\" the application, a Toast
comes up with a message similar to \"Please click BACK again to exit\".
I was wondering, as I\'m seeing it more and more often, is that a built-in feature that you can somehow access in an activity? I\'ve looked at the source code of many classes but I can\'t seem to find anything about that.
Of course, I can think about a few ways to achieve the same functionality quite easily (the easiest is probably to keep a boolean in the activity that indicates whether the user already clicked once...) but I was wondering if there\'s something already here.
EDIT: As @LAS_VEGAS mentioned, I didn\'t really mean \"exit\" in the traditional meaning. (i.e. terminated) I meant \"going back to whatever was open before the application start activity was launched\", if that makes sense :)
回答1:
In Java Activity:
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);
}
In Kotlin Activity:
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)
}
I Think this handler helps to reset the variable after 2 second.
回答2:
Sudheesh B Nair\'s has a nice (and accepted) answer on the question, which i think should have a better alternative such as;
What\'s wrong with measuring time passed and checking if TIME_INTERVAL
miliseconds (say 2000) passed since the last back press. The following sample code uses System.currentTimeMillis();
to store the time onBackPressed()
is called;
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();
}
Back on accepted answer critique; Using a flag
to indicate if it was pressed in last TIME_INTERVAL
(say 2000) milliseconds and set - reset is via Handler
\'s postDelayed()
method was the first thing to come in my mind. But the postDelayed()
action should be cancelled when activity is closing, removing the Runnable
.
In order to remove the Runnable
, it must not be declared anonymous, and be declared as member along with the Handler
aswell. Then removeCallbacks()
method of Handler
can be called appropriately.
The following sample is the demonstration;
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);
}
Thanks to @NSouth for contributing; In order to prevent toast message appearing even after the application is closed, Toast
can be declared as a member - say mExitToast
- and can be cancelled via mExitToast.cancel();
just before super.onBackPressed();
call.
回答3:
Just thought I would share how I did it in the end, I just added in my activity:
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();
}
And it just works exactly as I want. Including the reset of the state whenever the activity is resumed.
回答4:
Process Flow Diagram:
Java Code:
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;
}
回答5:
There is very simplest way among all these answers.
Simply write following code inside onBackPressed()
method.
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();
}
You need to define back_pressed
object as long
in activity.
回答6:
Based upon the correct answer and suggestions in comments, I have created a demo which works absolutely fine and removes the handler callbacks after being used.
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);
}
}
}
I hope it will be helpful !!
回答7:
My solution using 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 and stylish.
回答8:
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);
Declare Variableprivate boolean doubleBackToExitPressedOnce = false;
Paste this in your Main Activity and this will solve your issue
回答9:
It is not a good idea to use a Runnable when exit the application, I recently figure out a much simpler way to record and compare the period between two BACK button clicks. Sample code as following:
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();
}
This will do the trick to exit the application by a double BACK button clicks within a certain delay period which is 2000 millisecond in sample.
回答10:
It\'s not a built in functionality. I think it is not even the recommended behavior. Android apps are not meant to exit:
Why dont Android applications provide an "Exit" option?
回答11:
The Accepted answer is Best one but if you are using Android Design Support Library
then you can use SnackBar
for Better Views.
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);
}
回答12:
- Declare a global Toast variable for MainActivity Class. example: Toast exitToast;
- Initialize it in onCreate view method. example:
exitToast = Toast.makeText(getApplicationContext(), \"Press back again to exit\", Toast.LENGTH_SHORT);
Finally create a onBackPressedMethod as Follows:
@Override
public void onBackPressed() {
if (exitToast.getView().isShown()) {
exitToast.cancel();
finish();
} else {
exitToast.show();
}
}
This works correctly, i have tested. and I think this is much simpler.
回答13:
Zefnus\'s answer using System.currentTimeMillis() is the best one (+1). The way I did it is not better than that, but still posting it to add to the above ideas.
If the toast is not visible when the back button is pressed, the toast is displayed, whereas, if it is visible (back has already been pressed once within the last Toast.LENGTH_SHORT
time), then it exits.
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();
}
}
回答14:
Recently, I needed to implement this back button feature in an app of mine. The answers on the original question were useful, but I had to take two more points into consideration:
- At some points in time, the back button is disabled
- The main activity is using fragments in combination with a back stack
Based on the answers and comments, I created the following code:
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();
}
}
}
}
The code above assumes that the support library is used. If you use fragments but not the support library, you want to replace getSupportFragmentManager()
by getFragmentManager()
.
Remove the first if
, if the back button is never cancelled. Remove the second if
, if you don`t use fragments or a fragment back stack
Also, it is important to be aware that the method onBackPressed
is supported since Android 2.0. Check this page for an elaborate description. To make the back press feature work on older versions as well, add the following method to your activity:
@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);
}
回答15:
I know this is a very old question, but this is the easiest way to do what you want.
@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();
}
}
I know this isn\'t the best method, but it works fine!
回答16:
This also helps when you have previous stack activity stored in stack.
I have modified Sudheesh\'s answer
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);
}
回答17:
@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);
}
回答18:
For this purpose I have implemented the following function:
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();
}
回答19:
Here is the full working code. And also don\'t forget to remove the callbacks so that it don\'t cause a memory leak in the app. :)
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);
}
}
回答20:
Here, I have generalized write the code for N tap counts. The code is similarly written for the Enable Developer option in android device phone. Even you can use this to enable features while developer testing the app.
private Handler tapHandler;
private Runnable tapRunnable;
private int mTapCount = 0;
private int milSecDealy = 2000;
onCreate(){
...
tapHandler = new Handler(Looper.getMainLooper());
}
Call askToExit() on backpress or logout option.
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();
}
回答21:
When HomeActivity contain navigation drawer and double backPressed() funtion to exit app.
(Don\'t forget to initilize global variable boolean doubleBackToExitPressedOnce = false;)
new handler after 2 sec set doubleBackPressedOnce variable to 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);
}
}
}
回答22:
In 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);
}
}
in 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)
}
}
回答23:
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);
}
回答24:
Some Improvements in Sudheesh B Nair\'s answer, i have noticed it will wait for handler even while pressing back twice immediately, so cancel handler as shown below. I have cancled toast also to prevent it to display after app exit.
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);
}
回答25:
A slightly better method than Zefnus I think. Call System.currentTimeMillis() just one time and omit 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();
}
}
回答26:
This is the same of the accepted and most voted response but this snipped used Snackbar instead of 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);
}
回答27:
In my case, I\'ve depend on Snackbar#isShown()
for better 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();
}
}
回答28:
For the activity whose is having Navigation Drawer, Use the following code for 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();
}
}
}
回答29:
I use this
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 to in event onBackPressed
@Override
public void onBackPressed() {
ExitApp.now(this,\"Press again for close\");
}
or ExitApp.now(this,R.string.double_back_pressed)
for change seconds need for close, specified miliseconds
ExitApp.now(this,R.string.double_back_pressed,5000)
回答30:
Here is another way... using the CountDownTimer method
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();
}
}