点击两次返回按钮退出一个活动界面。

389
我最近注意到许多Android应用程序和游戏中出现了这种模式:当点击返回按钮以“退出”应用程序时,会弹出一个类似于“请再次点击返回键退出”的消息Toast

我想知道,随着我越来越经常看到它,这是一种内置的功能,你可以在活动中以某种方式访问它吗?我查看了许多类的源代码,但似乎没有找到任何相关信息。

当然,我可以考虑几种很容易实现相同功能的方法(最简单的可能是在活动中保留一个布尔值,指示用户是否已经点击了一次...)但我想知道是否已经有相关的功能存在。

编辑:正如@LAS_VEGAS所提到的那样,我的意思并不是传统意义上的“退出”。(即终止)我指的是“回到应用程序启动活动之前打开的内容”,如果这有意义的话 :)

【Android - 使用Toast确认应用程序退出】在Android中,当用户按下返回按钮时,应用程序将被销毁并退出。如果您想要在用户按下返回按钮时显示一个Toast消息来确认退出,请使用以下代码:private static long back_pressed; @Override public void onBackPressed() { if (back_pressed + 2000 > System.currentTimeMillis()) super.onBackPressed(); else Toast.makeText(getBaseContext(), "再按一次退出", Toast.LENGTH_SHORT).show(); back_pressed = System.currentTimeMillis(); }这将在用户第一次按下返回按钮时显示一个Toast消息,告诉他们再按一次退出应用程序。如果用户在2秒内再次按下返回按钮,则应用程序将被销毁并退出。 - resource8218
1
当我使用HoloEverywhere库时,也遇到了同样的问题,简单地将android:launchMode="singleTask"添加到清单文件中的Activity定义即可解决。 - Sohayb Hassoun
其他解决方案:https://dev59.com/-Woy5IYBdhLWcg3wnfUi - Codelaby
1
可能是点击Android返回按钮两次退出应用程序的重复问题。 - Prabh deep
48个回答

1063

在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(Looper.getMainLooper()).postDelayed(new Runnable() {
        
        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;                       
        }
    }, 2000);
} 
在 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(Looper.getMainLooper()).postDelayed(Runnable { doubleBackToExitPressedOnce = false }, 2000)
    }

我认为这个处理程序可以在2秒后重置变量。


50
最佳答案!如果(doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount() != 0)的话,您还可以添加条件,在基于Fragment的添加情况下。 - Anton Derevyanko
2
我同意,这绝对是最好的答案,应该被接受。 - BruceHill
3
退出应用程序时,您应该删除 Runnable。 - Wayne
请检查我的答案。我修改了Sudheesh B Nair的答案以涵盖上述建议的评论。 - Mehul Joisar
22
这是一个不错的快速解决方案/答案,但我不同意它是最好的解决方案。对于那些认为这将是最佳答案的人,我也不能同意。这些解决方案会导致泄漏,并需要额外的处理工作。请查看下面的答案以获取更多细节。 - Saro Taşciyan
显示剩余6条评论

255

Sudheesh B Nair对该问题有一个不错的(并且被接受的)答案,我认为还应该有更好的替代方案,例如:

使用时间差来检测自上次按下返回键以来是否已经过了TIME_INTERVAL毫秒(比如2000毫秒)。以下示例代码使用System.currentTimeMillis();来存储调用onBackPressed()时的时间。

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

回到接受答案的批判;使用一个标记来指示上次在最后TIME_INTERVAL(比如2000)毫秒内是否有按下,并且通过HandlerpostDelayed()方法进行设置 - 重置是我想到的第一件事。但是当活动关闭时,postDelayed()操作应该被取消,删除Runnable

为了删除Runnable,它不能被声明为匿名,必须与Handler一起声明为成员。然后可以适当调用HandlerremoveCallbacks()方法。

以下示例是演示;

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);
}
感谢@NSouth的贡献;为了防止应用程序关闭后仍出现“toast消息”,可以将Toast声明为成员变量 - 比如说mExitToast,并且可以在调用super.onBackPressed()之前通过mExitToast.cancel();来取消它。

12
对于那些认为Sudheesh B Nair所说的话和这个相同的人:同样的功能,更好的性能。因此+1。 - Bedir Yilmaz
4
我喜欢这个答案,我认为它是最好的。我的意思是,我不认为它是最好的,它确实是最好的答案,因为前面提到的原因。希望你能得到更多的赞。不过有一个评论:没有人觉得应用程序关闭后提示信息还会持续几秒钟有些奇怪吗?也没有人想取消提示信息吗?我知道这可能是一个小细节,但我认为应该发生这种情况。你们觉得呢? - acrespo
2
@NSouth 第二个代码块是使用mHandlers的示例,以展示它需要更多的努力。我建议您考虑使用第一个代码块,它不使用处理程序。 - Saro Taşciyan
1
@acrespo,我想现在我们有了一个可靠的解决方案,可以在应用关闭后保留_toast消息_。 - Saro Taşciyan
1
@MehulJoisar,你有读完整个答案吗?我想澄清一下,我建议的解决方案是第一个代码块,没有处理程序。没有处理程序的解决方案与您的解决方案在任何方面都不相同,因为它使用处理程序-这对于该问题来说既不是简短也不是简单的解决方案。您的答案也没有解释处理程序的缺点以及为什么要将其删除。 - Saro Taşciyan
显示剩余13条评论

31

最终,我想分享一下我的做法,我只是在我的活动中添加了:

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

它完全按照我的期望工作。包括在恢复活动时重置状态。


20
使用这种解决方案,两次“返回”按键之间的时间可以是任意的。因此,你可以先按一次“返回”,然后一分钟后再按第二次“返回”,应用程序就会退出。但这不是用户期望的行为。 - BruceHill
1
我认为这是一个品味问题。在这种情况下,您只需通知用户一次,这样用户就知道他处于主活动中,再按一次返回键将退出应用程序(可能在用户按几次返回键返回到主屏幕后)。如果稍后用户再次按下返回键,则我们可以假设他想退出应用程序(除非他已经导航到其他活动并且可能迷失了自己的深度)。在上面的接受答案中,您考虑到用户可能会忘记自己已经在主屏幕中。两者都可以,具体取决于您的需求或考虑。 - Ferran Maylinch
4
@FerranMaylinch - 我不同意你的看法。这不仅仅是品味问题。如果经过了相当长的时间,我们应该假设用户已经采取了其他行动,并不再考虑他之前所做的并选择不继续执行。事实上,除非是最特别的用户,否则几乎没有人会想起自己以前曾经这样做过。如果没有时间限制,你让应用程序处于一种用户无法知晓的隐形模式。我认为这绝对是糟糕的用户界面设计。用户们会感到惊讶。 - ToolmakerSteve
在我看来,更好的变体在这里和这里。 - ToolmakerSteve

28

在所有答案中,有一种非常简单的方法。

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

你需要在活动(activity)中将back_pressed对象定义为long类型。


27

流程图: 再次按下退出。

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

1
我最初投票认为这很有帮助。问题是,由于某些原因,这在一些手机上不起作用。onBackPressed方法效果最好,但是你就没有时间戳了,所以你需要像被接受的答案所述那样使用handlers。 - ravemir

18

您可以使用 Snackbar 代替 Toast,这样您可以依赖它们的可见性来决定是否关闭应用程序。看看这个例子:

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

3
感谢这个解决方案。简单、有效、没有风险。 - ping li
4
开箱即用的解决方案。(鼓掌) - Hitesh Dhamshaniya
简单而伟大!但这会影响所有的导航返回按钮! - C.F.G
简单而伟大!但是这会影响所有导航返回按钮! - C.F.G

13

根据正确答案和评论中的建议,我创建了一个演示,它完全正常运行并在使用后删除处理程序回调。

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

}

希望这会有所帮助!


你确定在 onBackPressed() 中移除处理程序可以解决 内存泄漏 问题吗? - Saro Taşciyan
@Zefnus:据我所知,它会被修复。如果我错了,请纠正我。你是如何追踪上述代码的内存问题的? - 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);

声明变量private boolean doubleBackToExitPressedOnce = false;

把这段代码复制到你的主活动中,就能解决你的问题。


10

在退出应用程序时使用Runnable不是一个好主意,最近我发现了一种更简单的方法来记录和比较两次“后退”按钮点击之间的时间间隔。示例代码如下:

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

在示例中,通过在一定延迟期间内双击“后退”按钮,即可完成退出应用程序的操作。


8

接受的答案是最好的,但如果您正在使用Android Design Support Library,那么您可以使用SnackBar来获得更好的视图。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接