两次按返回键退出可以有效避免误触返回键导致的不正常退出,提供了良好的用户体验,在Android应用中大行其道。作为Android编程人员,不可不了解其实现方法。话说路歌在某个奇迹般早早睡醒的清晨,窝在被子里细细想了一下,大概有五种方法可以实现两次按返回键退出。这其中,有的方法是非常实用的方法,可以直接放在应用中使用;而有的方法,就纯粹是孔乙己嘴中“回”字的九种写法了。但是,对于学习Android编程来说,了解这五种方法非常有助于学习Android的多线程编程技术。于是乎,路歌在随后某个吃撑了的日子,又一一验证了这五种方法。再然后,经过一番抓耳挠腮,拼凑出了此文。正文开始之前,路歌要先提醒诸位小伙伴,写这许多文字所花时间、所费心思,远比写出验证五种方法的代码的多,所以,如果哪位小伙伴心爱转载,烦请注明出处,以励路歌一向脆弱的写字热情。
天也不早,人也不少!光说不练假把式,光练不说呆把式,又练又说真把式!接下来,诸位就听路歌说说这五种实现方法。
既然是两次按返回键退出,那么最直接的想法是在用户第一次按返回键后监听是不是在预设时间内有第二次按返回键。如果按了,退出;如果没按,放弃第一次按键操作。既然是要监听按键操作,那么很自然就会想到用线程来实现。对,这就是最先想到的方法:定义一个二次按返回键的标记,在按返回键处理事件中判断:如果此标记为false,说明是第一次按键,设置此标记为true,同时启动一个延时子线程(延时长度就是预设的两次按返回键的最大间隔)将按键标记复位;如果此标记为true,说明是第二次按键,直接退出。于是乎,“回”字的第一种写法隆重出炉!实现代码如下:
public classMainActivity extends Activity {
private final static long TWICE_CLICK_INTERVAL= 1000; // 两次按返回键的最大间隔
private final static int EXIT_MSG_CODE= 100; // 处理按键的消息标记
private Boolean isSecondClick = false; // 标记下次按返回键是否退出程序
// 如果超出预设时间没有再次按返回键,handler自动将isSecondClick复位
private Handler handler = new Handler() {
public void handleMessage(Messagemsg) {
if (msg.what == EXIT_MSG_CODE){
isSecondClick = false;
}
};
};
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onBackPressed() {
// super.onBackPressed();
if (isSecondClick) {
finish();
System.exit(0);
}
else {
// 第一次按返回键,显示提示信息
isSecondClick = true;
Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
// 延时发送特定what值空消息,如果用户在预设时间内没有再次按返回键,
// 则handler任务将isSecondClick复位
handler.sendEmptyMessageDelayed(EXIT_MSG_CODE,TWICE_CLICK_INTERVAL);
}
}
}
点评:用Handler处理延时消息,这个是最容易想到的办法,但是,却不是最佳办法,毕竟,动用了多线程编程,很多小伙伴会被“多线程”三个字吓到。
既然第一种方法用子线程没问题,那很自然就想到也可以用异步任务来实现啦。于是乎,“回”字的第二种写法就这么出来了!实现代码如下:
public classAsyncTaskActivity extends Activity {
private final static long TWICE_CLICK_INTERVAL= 1000; // 两次按返回键的最大间隔
private Boolean isSecondClick = false; // 标记下次按返回键是否退出程序
private class MyAsyncTask extends AsyncTask<Void,Void, Void> {
@Override
protected VoiddoInBackground(Void... params) {
try {
// 线程休眠以给用户留出再次按键时间
Thread.sleep(TWICE_CLICK_INTERVAL);
}
catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
isSecondClick = false;
return null;
}
}
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.async_task);
}
@Override
public void onBackPressed() {
// super.onBackPressed();
if (isSecondClick) {
finish();
}
else {
// 第一次按返回键,显示提示信息
isSecondClick = true;
Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
// 启动异步任务监测用户是否第二次按返回键,如果在预设时间内没有再次按返回键,
// 则在异步任务中将isSecondClick复位
newMyAsyncTask().execute();
}
}
}
点评:用异步任务来实现,相对更简洁的Handler处理消息来说,乍一看似乎是脱裤子放屁——多此一举,但是,事务总有好坏两方面(大学马哲里学的),异步任务还是有点儿好处的:如果需要,可以借助onProgressUpdate()方便地实现自定义倒计时,提醒用户及时二次按键或做其它处理。具体代码嘛,路歌偷懒了,有兴趣的小伙伴自己搞搞吧,反正有思路了就不是啥难事儿了。
以上两种方法,反复提到“延时”,路歌自然就想到了定时器Timer,嗯,可以用Timer定时器来做二次按键标记的自动复位工作嘛,就是这样!于是乎,“回”字的第三种写法有了!实现代码如下:
public classTimerTaskActivity extends Activity {
private final static long TWICE_CLICK_INTERVAL= 1000; // 两次按返回键的最大间隔
private Boolean isSecondClick = false; // 标记下次按返回键是否退出程序
private Timer timer=new Timer();
// 如果超出预设时间没有再次按返回键,TimerTask将isSecondClick复位
private class MyTimerTask extends TimerTask {
@Override
public void run() {
isSecondClick = false;
}
};
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.timer_task);
};
@Override
public void onBackPressed() {
// super.onBackPressed();
if (isSecondClick) {
finish();
}
else {
isSecondClick=true;
Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
// 延时启动定时器任务将isSecondClick复位
timer.schedule(new MyTimerTask(), TWICE_CLICK_INTERVAL);
}
}
}
点评:用Timer实现之后,路歌发现,这次的代码量最少!纳尼?!这难道是最佳方法?!一阵儿心跳加速后,猛然想到:出于线程安全的考虑,Android鼓励使用ScheduledThreadPoolExecutor来代替Timer!一盆凉水当头倒呀……好吧,那我就用推荐的ScheduledThreadPoolExecutor来代替Timer试试……于是乎,“回”字的第四种写法来了!实现代码如下:
public classThreadPoolExecutorActivity extends Activity {
private final static long TWICE_CLICK_INTERVAL= 1000; // 两次按返回键的最大间隔
private Boolean isSecondClick = false; // 标记下次按返回键是否退出程序
privateScheduledThreadPoolExecutor executor = newScheduledThreadPoolExecutor(1); // 定义计划线程池
// 如果超出预设时间没有再次按返回键,此任务将isSecondClick复位
private class MyScheduledTask implements Runnable {
public void run() {
isSecondClick = false;
}
};
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.thread_pool_executor);
}
@Override
public void onBackPressed() {
// super.onBackPressed();
if (isSecondClick) {
finish();
}
else {
isSecondClick = true;
Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
// 执行延时任务将isSecondClick 复位
executor.schedule(new MyScheduledTask(), TWICE_CLICK_INTERVAL,TimeUnit.MILLISECONDS);
}
}
}
点评:用ScheduledThreadPoolExecutor来代替Timer,代码真心没有增加多少,而且符合Android线程安全的要求,不失为很好的方法。但是,又见但是……线程池哦,尼玛,多少小伙伴一听到就头大了哦!
真的没有更简单的方法了吗?!怎么可能!想到很久很久以前(路歌那时还在.net上跟着微软忙活),在Winform编程中也有类似问题,曾经在网上看到过的一个让当时还是骚年的路歌佩服不已的解决方法。于是,路歌隆重推出最佳解决方案——直接使用时间戳搞定!路歌推荐的“回”字写法来了!实现代码如下:
public classClickTimeStampActivity extends Activity {
private final static long TWICE_CLICK_INTERVAL=1000;// 两次按返回键的最大间隔
private long firstClickTime=0; // 记录第一次按键的时间,如没有按键,则为0
protected voidonCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.click_time_stamp);
firstClickTime=TWICE_CLICK_INTERVAL*(-1); //确保不会在万一窗口快速打开时误按返回键一次退出
};
@Override
public void onBackPressed() {
// super.onBackPressed();
if ( (System.currentTimeMillis()-firstClickTime)>TWICE_CLICK_INTERVAL){
// 第一次按返回键,设置时间戳并显示提示信息
firstClickTime=System.currentTimeMillis();
Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
}
else {
// 两次按返回键时间差小于预设值,则正常退出
finish();
System.exit(0);
}
}
}
点评:最巧妙、最简洁、最纯粹、最值得使用的方法!这赶脚,泪里回头千百次,发现美妞竟在猪身旁呀!多么巧妙的解决方案,神马线程呀、定时器呀……都是浮云呀!哥对当年想出这个解决方法的大虾至今佩服不已!
不管是基于.net还是JVM,抑或是iOS或者Android的编程,也不管是C#还是Java或者其它编程语言,很多方法和经验是通用的。不管什么技术,多学一点儿总是有益处的。未来在你们手里,努力吧,骚年!老夫继续搬砖去了……